view mde/font/font.d @ 137:9f035cd139c6

BIG commit. Major change: old Options class is gone, all content values are loaded and saved automatically. All options updated to reflect this, some changed. Content restrutured a lot: New IContent module, Content module includes more functionality. New ContentLoader module to manage content loading/saving/translation. Translation module moved to content dir and cut down to reflect current usage. File format unchanged except renames: FontOptions -> Font, VideoOptions -> Screen. Font render mode and LCD filter options are now enums. GUI loading needs to create content (and set type for enums), but doesn't save/load value. Some setup of mainSchedule moved to mde.mainLoop. Content callbacks are called on content change now. ContentLists are set up implicitly from content symbols. Not as fast but much easier! Bug-fix in the new MTTagReader. Renamed MT *Reader maker functions to avoid confusion in paths.d. New mde.setup.logger module to allow logger setup before any other module's static this().
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 07 Feb 2009 12:46:03 +0000
parents 7ababdf97748
children
line wrap: on
line source

/* LICENSE BLOCK
Part of mde: a Modular D game-oriented Engine
Copyright © 2007-2008 Diggory Hardy

This program is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

/// Sets up freetype (in a basic way).
module mde.font.font;

public import mde.types.Colour;
import mde.font.FontTexture;
import mde.font.exception;

import mde.setup.exception;     // InitStage stuff
import mde.file.paths;

import derelict.freetype.ft;

import tango.stdc.stringz;
import Util = tango.text.Util;
import tango.util.log.Log : Log, Logger;

// "Publically import" this symbol:
alias mde.font.FontTexture.TextBlock TextBlock;

private Logger logger;
static this () {
    logger = Log.getLogger ("mde.font.font");
}

/** FontStyle class.
 *
 * Particular to a font and size, and any other effects like bold/italic if ever implemented.
 * 
 * Note: it is not currently intended to be thread-safe. */
class FontStyle
{
    //BEGIN Static: manager
    static {
        debug (drawGlyphCache) void drawTexture() {
            if (fontTex !is null)
                fontTex.drawTexture;
        }
        
        /** Load the freetype library with settings from options. */
        StageState initialize () {
            if (FT_Init_FreeType (&library))
                throw new fontException ("error initialising the FreeType library");
            
            // Check version
            FT_Int maj, min, patch;
            FT_Library_Version (library, &maj, &min, &patch);
            if (maj != 2 || min != 3) {
                logger.warn ("Using an untested FreeType version: {}.{}.{}", maj, min, patch);
                logger.info ("The only tested version of freetype is 2.3");
            }
            
            // Set LCD filtering method if LCD rendering is enabled.
            if (fontOpts.mode() & 2 &&
                FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter())) {
                /* An error occurred, presumably because LCD rendering support
                * is not compiled into the library. */
                logger.warn ("Bad/unsupported LCD filter option; disabling LCD font rendering.");
                logger.warn ("Your FreeType 2 library may be compiled without support for LCD/sub-pixel rendering.");
                
                // Reset the default filter (in case an invalid value was set in config files).
                fontOpts.lcdFilter = FT_LcdFilter.FT_LCD_FILTER_DEFAULT;
                
                /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3
                 * times wider glyphs. So disable and save the extra work. */
                fontOpts.mode = 0;
            }
            
            // Load the fallback font; if it throws let exception abort program
	    fallback = new FontStyle (fontOpts.defaultFont(), fontOpts.defaultSize());
	    
            return StageState.ACTIVE;
        }
        
        /** Cleanup: delete all fonts. */
        StageState cleanup () {
            // Clear loaded fonts (each has an FT_Face object needing to be freed):
            /* FIXME
	    foreach (fs; fonts)
                fs.freeFace;
	    */
            
            FT_Done_FreeType (library); // free the library
            
            return StageState.INACTIVE;
        }
        
        /** Get a FontStyle instance, for a section in the fonts.mtt file.
          *
          * Also loads the font if it's not already loaded, so the first call may take some time.
          *
          * Uses fallback font-style if the desired style isn't known about or fails to load, so
          * this function should never fail or throw, in theory (unless out of memory). The
          * fallback should already be loaded. */
        FontStyle getDefault () {
	    //FIXME: Ddoc, new purpose; rename
            return fallback;
        }
        
    private:
        FT_Library	library;
        FontTexture	fontTex;
        //FontStyle[char[]]	fonts;	// loaded fonts, by file name	 FIXME: use hash of struct { char[] path; int size; }?
        FontStyle	fallback;	// default & used when requested font can't be loaded
    }
    //END Static
    
    /** Load the font file.
     *
     * Even if the same font is used at multiple sizes, multiple copies of FT_Face are used.
     * Sharing an FT_Face would require calling FT_Set_Pixel_Sizes each time a glyph is rendered or
     * swapping the size information (face.size)? */
    this (char[] file, int size)
    in {
        assert (library !is null, "font: library is null");
    } body {
	if (FT_New_Face (library, getFontPath (file).ptr, 0, &face))
            throw new fontLoadException ("Unable to read font: "~file[0..$-1]);
        
        if (!FT_IS_SCALABLE (face))
            throw new fontLoadException ("Currently no support for non-scalable fonts (which " ~
                    file[0..$-1] ~ " is). Please report if you want to see support.");
        /* The following will need to be addressed when adding support for non-scalables:
         *	Use of face.size.metrics.height property.
         */
        
        if (FT_Set_Pixel_Sizes (face, 0, size))
            throw new fontLoadException ("Unable to set pixel size");
        
        // Create if necessary:
        if (fontTex is null)
            fontTex = new FontTexture;
	
	return this;
    }
    
    this (FontStyle font, int size) {
	//FIXME: copy font's face and set new size?
    }
    
    /** Update a TextBlock cache, as used by the textBlock function.
     *
     * The only use of this is to get the text block's size ahead of rendering, via TextBlock's w
     * and h properties.
     *
     * This function will only actually update the cache if it is invalid, caused either by the
     * font being changed or if cache.cacheVer < 0. */
    void updateBlock (char[] str, ref TextBlock cache)
    in {
        debug assert (face, "FontStyle: face is null");
    } body {
        try {
            fontTex.updateCache (face, str, cache);
        } catch (Exception e) {
            logger.warn ("Exception while drawing text: "~e.msg);
        }
    }
    
    /** Draw a block of text (may inlcude new-lines).
     *
     * Params:
     *	x =	Top left x-coordinate of text block
     *	y =	Top left y-coordinate of text block
     *	str =	Text to render
     *	cache =	An (optional) TextBlock used for rendering this text, to save some CPU work. See updateBlock
     *	col =	Colour to render the text; see mde.types.Colour
     *	index =	Either the index of the character to draw with an edit cursor or size_t.max. Not
     *		the index within str, but the number of characters, excluding new-lines.
     * The text block is drawn with top-left corner at x,y. To put the text's baseline at a given
     * y coordinate would require some changes. Line height is fixed (see getLineSeparation).
     * Due to hinter, glyphs are not guaranteed to lie within the "bounding box" defined by cache.
     * Can be changed to test size of each glyph if necessary.
     *
     * Specify the text's colour with col. Use textBlockA() instead  for transparent text.
     *
     * As a CPU-side code optimisation, store a TextBlock (unique to str) and pass a reference as
     * the cache argument. This is the recommended method, although for one-time calls when you
     * don't need to know the size, the other version of textBlock may be used.
     * ---------------------------------
     * char[] str;
     * TextBlock strCache;
     * textBlock (x, y, str, strCache, Colour.WHITE);
     * ---------------------------------
     * The TextBlock cache will be updated as necessary. Besides the initial update, this will only
     * be if the font changes, or it is manually invalidated. This can be done by setting the
     * TextBlock's cacheVer property to -1, which should be done if str is changed.
     *
     * The TextBlock's w and h properties are set to the size (in pixels) of the text block; other
     * than this cache only serves as a small optimisation. However, the only way to get the size
     * of a text block is to use a TextBlock cache and update it, either with this function or with
     * the updateBlock function. */
    void textBlock (int x, int y, char[] str, ref TextBlock cache, Colour col, size_t index = size_t.max)
    in {
        assert (face, "FontStyle: face is null");
    } body {
        try {
            fontTex.drawCache (face, str, cache, x, y, col, index);
        } catch (Exception e) {
            logger.warn ("Exception while drawing text: "~e.msg);
        }
    }
    /** ditto */
    void textBlock (int x, int y, char[] str, Colour col, size_t index = size_t.max)
    in {
        assert (face, "FontStyle: face is null");
    } body {
        try {
            // Using the cache method for one-time use is slightly less than optimal, but doing so
            // isn't really recommended anyway (and maintaining two versions of fontTex.drawText
            // would be horrible).
            TextBlock cache;
            fontTex.drawCache (face, str, cache, x, y, col, index);
        } catch (Exception e) {
            logger.warn ("Exception while drawing text: "~e.msg);
        }
    }
    
    /** A variation of textBlock for transparency.
     *
     * Set the alpha by calling glColor*() first. See FontTexture.drawCacheA()'s documentation for
     * details. */
    void textBlockA (int x, int y, char[] str, ref TextBlock cache, Colour col, size_t index = size_t.max) 
    in {
        assert (face, "FontStyle: face is null");
    } body {
        try {
	    fontTex.drawCacheA (face, str, cache, x, y, col, index);
        } catch (Exception e) {
            logger.warn ("Exception while drawing text: "~e.msg);
        }
    }
    
    /** The font-specified vertical distance between the baseline of consecutive lines. */
    int getLineSeparation ()
    in {
        assert (face, "FontStyle: face is null");
    } body {
        return face.size.metrics.height >> 6;
    }
    
    void freeFace () {
        FT_Done_Face (face);
        face = null;    // functions using face use assertions on face to check its validity.
    }
    
private:
    FT_Face	face;
}