Changeset 48:a98ffb64f066

Show
Ignore:
Timestamp:
05/31/08 07:40:26 (7 months ago)
Author:
Diggory Hardy <diggory.hardy@gmail.com>
branch:
default
convert_revision:
3d6a857b4b3c6c515c6d68b84e4d142103cd4a0b
Message:

Implemented font rendering (grayscale only; i.e. non-LCD).

FontTexture? creates a texture and caches glyphs.
Font supports multiple styles/faces, loaded from config file (should probably be loaded via Options instead).
TextBlock? cache for glyph placement within a string.

committer: Diggory Hardy <diggory.hardy@gmail.com>

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • codeDoc/jobs.txt

    r47 r48  
    44 
    55In progress: 
    6 Implementing font rendering 
    7 Reading ft tutorial 
    8 Use cartesian coordinates? Or not? 
    96 
    107 
     
    5552 
    5653Done (for git log message): 
     54Implemented font rendering (grayscale only; i.e. non-LCD). 
     55FontTexture creates a texture and caches glyphs. 
     56Font supports multiple styles/faces, loaded from config file (should probably be loaded via Options instead). 
     57TextBlock cache for glyph placement within a string. 
  • data/conf/options.mtt

    r43 r48  
    1010<bool|noFrame=false> 
    1111<bool|resizable=true> 
    12 <bool|hardware=true> 
     12<bool|hardware=false> 
    1313<bool|fullscreen=false> 
    1414<int|screenW=1280> 
  • mde/gl/basic.d

    r45 r48  
    2626void glSetup () { 
    2727    glDisable(GL_DEPTH_TEST); 
    28     glEnable (GL_TEXTURE_2D); 
     28    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); 
     29    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 
     30    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 
     31    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
     32    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); 
     33    glEnable(GL_TEXTURE_2D); 
     34    glShadeModel(GL_FLAT); 
    2935     
    3036    glClearColor (0.0f, 0.0f, 0.0f, 0.0f); 
     
    3238    glMatrixMode(GL_MODELVIEW); 
    3339    glLoadIdentity(); 
     40     
     41    // Used for font rendering: 
     42    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
     43    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 
     44    //NOTE: wrap mode may have an effect, but shouldn't be noticed... 
    3445} 
    3546 
     
    5667} 
    5768void drawBox (int x, int y, int w, int h) { 
     69    glDisable(GL_TEXTURE_2D); 
    5870    glRecti(x, y+h, x+w, y); 
    5971} 
  • mde/gl/draw.d

    r45 r48  
    2727import tango.util.log.Log : Log, Logger; 
    2828 
     29import mde.resource.font; 
    2930private Logger logger; 
    3031static this () { 
     
    3839     
    3940    gui.draw (); 
     41    FontStyle.drawTexture; 
    4042     
    4143    GLenum err = glGetError(); 
  • mde/gui/widget/Ifaces.d

    r46 r48  
    4545     
    4646    /** Returns the window's gui. */ 
    47     //NOTE: was going to remove this, but it's used more than I thought 
    4847    IGui gui (); 
    4948     
  • mde/gui/widget/miscWidgets.d

    r45 r48  
    115115    void draw () { 
    116116        super.draw(); 
    117         if (font is null) font = Font.get("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf"); 
    118         font.drawStr (x,y, "Text Widget"); 
     117        if (font is null) font = FontStyle.get("default"); 
     118        font.textBlock (x,y, "|−|−| .,.,.", textCache); // test new-lines and unicode characters 
     119        //old string: "Text Widget\nαβγ − ΑΒΓ" 
    119120    } 
    120121     
    121122protected: 
    122     static Font font; 
     123    TextBlock textCache; 
     124    static FontStyle font; 
    123125} 
  • mde/resource/exception.d

    r44 r48  
    1818import mde.exception; 
    1919 
    20 /// Thrown when initialising freetype fails. 
     20/// Thrown when initialisation fails 
    2121class fontException : mdeException { 
    2222    char[] getSymbol () { 
     
    3131/// Thrown when loading a freetype font fails. 
    3232class fontLoadException : fontException { 
     33    this (char[] msg) { 
     34        super(msg); 
     35    } 
     36} 
     37 
     38/// Thrown when problems occur with glyphs (rendering, etc.) 
     39class fontGlyphException : fontException { 
    3340    char[] getSymbol () { 
    34         return super.getSymbol ~ ".resource.font"; 
     41        return super.getSymbol ~ ".glyph"; 
    3542    } 
    3643     
  • mde/resource/font.d

    r45 r48  
    1717module mde.resource.font; 
    1818 
     19import mde.resource.FontTexture; 
    1920import mde.resource.exception; 
     21 
     22import mde.mergetag.Reader; 
     23import mde.mergetag.DataSet; 
     24import mde.mergetag.exception; 
     25import mde.resource.paths; 
    2026 
    2127import derelict.freetype.ft; 
    2228import derelict.opengl.gl; 
    2329 
     30import tango.scrapple.text.convert.parseTo : parseTo; 
    2431import tango.stdc.stringz; 
     32import Util = tango.text.Util; 
    2533import tango.util.log.Log : Log, Logger; 
     34 
     35// "Publically import" this symbol: 
     36alias mde.resource.FontTexture.TextBlock TextBlock; 
    2637 
    2738private Logger logger; 
     
    3041} 
    3142 
    32 /** Font class. 
     43/** FontStyle class. 
    3344 * 
    34  * Particular to a font and size. (Maybe not size?) 
     45 * Particular to a font and size, and any other effects like bold/italic if ever implemented. 
    3546 *  
    3647 * Note: it is not currently intended to be thread-safe. */ 
    37 class Font 
     48class FontStyle : IDataSection 
    3849{ 
    3950    //BEGIN Static: manager 
    4051    static { 
     52        debug void drawTexture() { 
     53            if (fontTex !is null) 
     54                fontTex.drawTexture; 
     55        } 
     56         
    4157        /** Load the freetype library. */ 
    4258        void initialize () { 
     59            if (!confDir.exists (fileName)) 
     60                throw new fontException ("No font settings file (fonts.[mtt|mtb])"); 
     61             
    4362            if (FT_Init_FreeType (&library)) 
    4463                throw new fontException ("error initialising the FreeType library"); 
    45         } 
    46          
    47         //FIXME: don't use GC for Font resources 
     64             
     65            // Check version 
     66            FT_Int maj, min, patch; 
     67            FT_Library_Version (library, &maj, &min, &patch); 
     68            if (maj != 2 || min != 3) { 
     69                char[128] tmp; 
     70                logger.warn (logger.format (tmp, "Using an untested FreeType version: {}.{}.{}", maj, min, patch)); 
     71            } 
     72             
     73            /* Load font settings 
     74             * 
     75             * Each mergetag section corresponds to a font; each is loaded whether used or not 
     76             * (however the actual font files are only loaded on use). A fallback id must be 
     77             * provided in the header which must match a loaded font name; if a non-existant font 
     78             * is requested a warning will be logged and this font returned. */ 
     79            char[] fallbackName; 
     80            try { 
     81                IReader reader; 
     82                reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH, null, true); 
     83                reader.dataSecCreator = delegate IDataSection(ID id) { 
     84                    auto f = new FontStyle; 
     85                    fonts[id] = f; 
     86                    return f; 
     87                }; 
     88                reader.read; 
     89                 
     90                // get fallback name 
     91                char[]* p = "fallback" in reader.dataset.header.Arg!(char[]).Arg; 
     92                if (p is null) 
     93                    throw new fontException ("No fallback font style specified"); 
     94                fallbackName = *p; 
     95            } 
     96            catch (MTException e) { 
     97                throw new fontException ("Mergetag exception: "~e.msg); 
     98            } 
     99             
     100            // Find the fallback 
     101            FontStyle* p = fallbackName in fonts; 
     102            if (p is null) 
     103                throw new fontException ("Fallback font style specified is not found"); 
     104            fallback = *p; 
     105            // Load the fallback now, to ensure it's available. 
     106            // Also note that get() doesn't make sure the fallback is loaded before returning it. 
     107            fallback.load; 
     108        } 
     109        private const fileName = "fonts"; 
     110         
     111        //FIXME: don't use GC for FontStyle resources 
    48112        /** Cleanup: delete all fonts. */ 
    49113        void cleanup () { 
    50             if (font) 
    51                 delete font; 
    52              
    53114            FT_Done_FreeType (library); 
    54115        } 
    55116         
    56         /** Get a font. 
    57          * 
    58          * Later specify font/size. 
    59          * 
    60          * Throws: 
    61          *  fontLoadException when unable to load the font. */ 
    62         Font get(char[] path) { 
    63             if (font is null) font = new Font(path); 
    64             return font; 
     117        /** Get a FontStyle instance, for a section in the fonts.mtt file. 
     118          * 
     119          * Also loads the font if it's not already loaded, so the first call may take some time. 
     120          * 
     121          * Uses fallback font-style if the desired style isn't known about or fails to load, so 
     122          * this function should never fail or throw, in theory (unless out of memory). The 
     123          * fallback should already be loaded. */ 
     124        FontStyle get(char[] name) { 
     125            FontStyle* p = name in fonts; 
     126            if (p is null) { 
     127                logger.warn ("Font style "~name~" requested but not found; reverting to the fallback style."); 
     128                fonts[name] = fallback; // set to prevent another warning getting logged 
     129                return fallback; 
     130            } 
     131            // Got it, but we need to make sure it's loaded: 
     132            try { 
     133                p.load; 
     134            } catch (Exception e) { 
     135                logger.warn ("Font style "~name~" failed to load; reverting to the fallback style."); 
     136                return fallback; 
     137            } 
     138            return *p; 
    65139        } 
    66140         
    67141    private: 
    68142        FT_Library  library; 
    69         Font font; 
     143        FontTexture fontTex; 
     144        FontStyle[ID]   fonts;      // all font styles known about; not necessarily loaded 
     145        FontStyle   fallback;   // used when requested font isn't in fonts 
    70146    } 
    71147    //END Static 
    72148     
    73      
    74     /** Load & cache a new font. */ 
    75     this (char[] path) 
     149    this() {} 
     150     
     151    //BEGIN Mergetag code 
     152    //NOTE: would it be better not to use a new mergetag file for this? 
     153    //FIXME: revise when gui can set options 
     154    void addTag (char[] tp, ID id, char[] dt) { 
     155        if (tp == "char[]") { 
     156            if (id == "path") 
     157                path = parseTo!(char[]) (dt); 
     158        } 
     159        else if (tp == "int") { 
     160            if (id == "size") 
     161                size = parseTo!(int) (dt); 
     162        } 
     163    } 
     164    void writeAll (ItemDelg) {}     // no writing the config for now 
     165    //END Mergetag code 
     166     
     167    /** Load the font file. 
     168     * 
     169     * Even if the same font is used at multiple sizes, multiple copies of FT_Face are used. 
     170     * Sharing an FT_Face would require calling FT_Set_Pixel_Sizes each time a glyph is rendered or 
     171     * swapping the size information (face.size)? */ 
     172    void load () 
    76173    in { 
    77174        assert (library !is null, "font: library is null"); 
     
    80177            throw new fontLoadException ("Unable to read font: "~path); 
    81178         
    82         if (FT_Set_Pixel_Sizes (face, 0,16)) 
     179        if (!FT_IS_SCALABLE (face)) 
     180            throw new fontLoadException ("Currently no support for non-scalable fonts (which " ~ 
     181                    path ~ " is). Please report if you want to see support."); 
     182        /* The following will need to be addressed when adding support for non-scalables: 
     183         *  Use of face.size.metrics.height property. 
     184         */ 
     185         
     186        if (FT_Set_Pixel_Sizes (face, 0,size)) 
    83187            throw new fontLoadException ("Unable to set pixel size"); 
    84     } 
    85      
    86     void drawStr (int x, int y, char[] str) { 
    87         FT_Vector pen = { x*64, y*64 }; 
    88         auto g = face.glyph; 
    89          
    90         FT_Matrix m; 
    91         m.xx = 0x10000; 
    92         m.xy = m.yx = 0; 
    93         m.yy = -0x10000; 
    94          
    95         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 
    96          
    97         FT_Pos y_adj = 0;   // y adjustment (for height) 
    98          
    99         FT_Bool useKerning = FT_HAS_KERNING (face); 
    100         FT_UInt previous = 0; 
    101          
    102         foreach (chr; str) { 
    103             auto gi = FT_Get_Char_Index (face, chr); 
    104              
    105             if (useKerning && previous && gi) 
    106             { 
    107                 FT_Vector  delta; 
    108  
    109  
    110                 FT_Get_Kerning (face, previous, gi, FT_Kerning_Mode.FT_KERNING_DEFAULT, &delta); 
    111  
    112                 pen.x += delta.x; 
    113             } 
    114              
    115             FT_Set_Transform(face, &m, &pen); 
    116             if (FT_Load_Glyph(face, gi, FT_LOAD_RENDER)) 
    117                 return; // give up 
    118              
    119             if (y_adj < g.metrics.height) y_adj = g.metrics.height; 
    120              
    121             auto b = g.bitmap; 
    122             if (b.pixel_mode != FT_Pixel_Mode.FT_PIXEL_MODE_GRAY || b.num_grays != 256) { 
    123                 char[128] tmp; 
    124                 logger.warn (logger.format (tmp,"Unsupported freetype bitmap format: {}, {}", b.pixel_mode, b.num_grays)); 
    125                 return; 
    126             } 
    127             if (b.pitch != b.width) 
    128                 logger.info ("b.pitch != b.width"); 
    129              
    130             //NOTE: y direction! 
    131             glRasterPos2i (g.bitmap_left,g.bitmap_top /+ (y_adj >> 6)+/); 
    132             glDrawPixels (b.width, b.rows, GL_LUMINANCE, GL_UNSIGNED_BYTE, cast(void*) b.buffer); 
    133              
    134             pen.x += g.advance.x; 
    135             pen.y += g.advance.y; 
    136             previous = gi; 
    137         } 
     188         
     189        // Create if necessary: 
     190        if (fontTex is null) 
     191            fontTex = new FontTexture; 
     192    } 
     193     
     194    /** Update a TextBlock cache, as used by the textBlock function. 
     195     * 
     196     * The only use of this is to get the text block's size ahead of rendering, via TextBlock's w 
     197     * and h properties. 
     198     * 
     199     * This function will only actually update the cache if it is invalid, caused either by the 
     200     * font being changed or if cache.cacheVer < 0. */ 
     201    void updateBlock (char[] str, ref TextBlock cache) { 
     202        fontTex.updateCache (face, str, cache); 
     203    } 
     204     
     205    /** Draw a block of text (may inlcude new-lines). 
     206     * 
     207     * As a CPU-side code optimisation, store a TextBlock (unique to str) and pass a reference as 
     208     * the cache argument. This is the recommended method, although for one-time calls when you 
     209     * don't need to know the size, the other version of textBlock may be used. 
     210     * --------------------------------- 
     211     * char[] str; 
     212     * TextBlock strCache; 
     213     * textBlock (x, y, str, strCache); 
     214     * --------------------------------- 
     215     * The TextBlock cache will be updated as necessary. Besides the initial update, this will only 
     216     * be if the font changes, or it is manually invalidated. This can be done by setting the 
     217     * TextBlock's cacheVer property to -1, which should be done if str is changed. 
     218     * 
     219     * The TextBlock's w and h properties are set to the size (in pixels) of the text block; other 
     220     * than this cache only serves as a small optimisation. However, the only way to get the size 
     221     * of a text block is to use a TextBlock cache and update it, either with this function or with 
     222     * the updateBlock function. */ 
     223    void textBlock (int x, int y, char[] str, ref TextBlock cache) { 
     224        try { 
     225            fontTex.drawTextCache (face, str, cache, x, y); 
     226        } catch (Exception e) { 
     227            logger.warn ("Exception while drawing text: "~e.msg); 
     228        } 
     229    } 
     230    /** ditto */ 
     231    void textBlock (int x, int y, char[] str) { 
     232        try { 
     233            // Using the cache method for one-time use is slightly less than optimal, but doing so 
     234            // isn't really recommended anyway (and maintaining two versions of fontTex.drawText 
     235            // would be horrible). 
     236            TextBlock cache; 
     237            fontTex.drawTextCache (face, str, cache, x, y); 
     238        } catch (Exception e) { 
     239            logger.warn ("Exception while drawing text: "~e.msg); 
     240        } 
     241    } 
     242     
     243     
     244    /** The font-specified vertical distance between the baseline of consecutive lines. */ 
     245    int getLineSeparation () { 
     246        return face.size.metrics.height >> 6; 
    138247    } 
    139248     
     
    143252     
    144253private: 
     254    char[]  path;   // path to font file 
     255    int     size;   // font size 
     256     
    145257    FT_Face face; 
    146258} 
  • mde/scheduler/init2.d

    r44 r48  
    8787void initFreeType () {  // init func 
    8888    try { 
    89         font.Font.initialize; 
     89        font.FontStyle.initialize; 
    9090    } catch (Exception e) { 
    9191        logger.fatal ("initFreeType failed: " ~ e.msg); 
  • mde/sdl.d

    r45 r48  
    2828 
    2929import derelict.sdl.sdl; 
     30import derelict.opengl.gl;  // for loading a later gl version 
     31import derelict.util.exception; 
    3032 
    3133private Logger logger; 
     
    7779     
    7880    // OpenGL attributes 
    79     SDL_GL_SetAttribute(SDL_GL_RED_SIZE,    8); 
    80     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,  8); 
    81     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,   8); 
    82     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  24); 
     81    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,    5); 
     82    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,  6); 
     83    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,   5); 
     84    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  16); 
    8385    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1); 
    8486     
     
    9092        logger.fatal (msg ? fromStringz(msg) : "no reason available"); 
    9193         
     94        // Print a load of info: 
     95        logger.info ("Available video modes:"); 
     96        char[128] tmp; 
     97        SDL_Rect** modes = SDL_ListModes (null, SDL_FULLSCREEN); 
     98        if (modes is null) logger.info ("None!"); 
     99        else if (modes is cast(SDL_Rect**) -1) logger.info ("All modes are available"); 
     100        else { 
     101            for (uint i = 0; modes[i] !is null; ++i) { 
     102                logger.info (logger.format (tmp, "\t{}x{}", modes[i].w, modes[i].h)); 
     103            } 
     104        } 
     105     
     106        SDL_VideoInfo* vi = SDL_GetVideoInfo (); 
     107        if (vi !is null) { 
     108            logger.info ("Video info:"); 
     109            logger.info ("Hardware surface support: "~ (vi.flags & SDL_HWSURFACE ? "yes" : "no")); 
     110            logger.info (logger.format (tmp, "Video memory: {}", vi.video_mem)); 
     111     
     112            if (vi.vfmt !is null) { 
     113                logger.info ("Best video mode:"); 
     114                logger.info (logger.format (tmp, "Bits per pixel: {}", vi.vfmt.BitsPerPixel)); 
     115            } 
     116        } 
     117         
    92118        setInitFailure (); 
    93119        return; 
    94120    } 
    95121     
    96     // Now (must be done after GL context is created) we can try to load later version. 
    97     // The initial loading provides opengl 1.1 features. 
    98     /+ No later GL features are currently used. 
     122    /* Now (must be done after GL context is created) we can try to load later version. 
     123     * The initial loading provides opengl 1.1 features. 
     124     * 
     125     * Currently the latest version used is 1.3; adjust this as necessary. However, before using 
     126     * features from any OpenGL version > 1.1 a check must be made on what was loaded by calling 
     127     * DerelictGL.availableVersion(). Note that availableVersion() could be used instead to load 
     128     * the highest supported version but this way we know what we're getting. 
     129     */ 
    99130    try { 
    100         DerelictGL.loadVersions(GLVersion.Version21); 
    101     } catch (DerelictException de) { 
    102         logger.fatal ("Loading OpenGL version > 1.1 failed:"); 
    103         logger.fatal (de.msg); 
    104          
     131        DerelictGL.loadVersions(GLVersion.Version13); 
     132    } catch (SharedLibProcLoadException e) { 
     133        logger.warn ("Loading OpenGL version 1.3 failed:"); 
     134        logger.warn (e.msg); 
     135         
     136        //NOTE: might be worth guaranteeing a minimal version to save later checks? 
     137        /+ Do this if you want the program to abort: 
    105138        setInitFailure (); 
    106139        return; 
    107     } 
    108     +/ 
     140        +/ 
     141    } 
    109142     
    110143    // OpenGL stuff: 
     
    143176} 
    144177 
    145     /+ Load of info-printing stuff (currently doesn't have a use) 
    146     // Print a load of info: 
    147     logger.info ("Available video modes:"); 
    148     char[128] tmp; 
    149     SDL_Rect** modes = SDL_ListModes (null, SDL_FULLSCREEN); 
    150     if (modes is null) logger.info ("None!"); 
    151     else if (modes is cast(SDL_Rect**) -1) logger.info ("All modes are available"); 
    152     else { 
    153     for (uint i = 0; modes[i] !is null; ++i) { 
    154     logger.info (logger.format (tmp, "\t{}x{}", modes[i].w, modes[i].h)); 
    155     } 
    156     } 
    157      
    158     SDL_VideoInfo* vi = SDL_GetVideoInfo (); 
    159     if (vi !is null) { 
    160     logger.info ("Video info:"); 
    161     logger.info ("Hardware surface support: "~ (vi.flags & SDL_HWSURFACE ? "yes" : "no")); 
    162     logger.info (logger.format (tmp, "Video memory: {}", vi.video_mem)); 
    163      
    164     if (vi.vfmt !is null) { 
    165     logger.info ("Best video mode:"); 
    166     logger.info (logger.format (tmp, "Bits per pixel: {}", vi.vfmt.BitsPerPixel)); 
    167     } 
    168     } 
    169     +/ 
    170  
    171178 
    172179/** All video options. */