Mercurial > projects > mde
view mde/font/font.d @ 81:d8fccaa45d5f
Moved file IO code from mde/mergetag to mde/file[/mergetag] and changed how some errors are caught.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Fri, 29 Aug 2008 11:59:43 +0100 |
parents | 61ea26abe4dd |
children | e0f1ec7fe73a |
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.lookup.Options; import mde.font.FontTexture; import mde.font.exception; import mde.file.mergetag.Reader; import mde.file.mergetag.DataSet; import mde.setup.paths; import derelict.freetype.ft; import derelict.opengl.gl; import mde.file.deserialize; 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 : IDataSection { //BEGIN Static: manager static { debug (drawGlyphCache) void drawTexture() { if (fontTex !is null) fontTex.drawTexture; } /** Load the freetype library from the file fileName. */ private const fileName = "fonts"; void 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); // Set LCD filtering method if LCD rendering is enabled. const RMF = FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V; if (fontOpts.renderMode & RMF && 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 compiled without support for LCD/sub-pixel rendering."); // Reset the default filter (in case an invalid value was set in config files). fontOpts.set!(int) ("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.set!(int) ("renderMode", FT_LOAD_TARGET_NORMAL); } /* Load font settings * * Each mergetag section corresponds to a font; each is loaded whether used or not * (however the actual font files are only loaded on use). A fallback id must be * provided in the header which must match a loaded font name; if a non-existant font * is requested a warning will be logged and this font returned. */ char[] fallbackName; try { IReader reader; reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH, null, true); reader.dataSecCreator = delegate IDataSection(ID id) { auto f = new FontStyle; fonts[id] = f; return f; }; reader.read; // get fallback name char[]* p = "fallback" in reader.dataset.header._charA; if (p is null) throw new fontException ("No fallback font style specified"); fallbackName = *p; } catch (NoFileException) { throw new fontException ("No font settings file (fonts.[mtt|mtb])"); } catch (Exception e) { throw new fontException ("Reading font settings failed: "~e.msg); } // Find the fallback FontStyle* p = fallbackName in fonts; if (p is null) throw new fontException ("Fallback font style specified is not found"); fallback = *p; // Load the fallback now, to ensure it's available. // Also note that get() doesn't make sure the fallback is loaded before returning it. fallback.load; } //FIXME: don't use GC for FontStyle resources /** Cleanup: delete all fonts. */ void cleanup () { FT_Done_FreeType (library); } /** 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 get(char[] name) { FontStyle* p = name in fonts; if (p is null) { logger.warn ("Font style "~name~" requested but not found; reverting to the fallback style."); fonts[name] = fallback; // set to prevent another warning getting logged return fallback; } // Got it, but we need to make sure it's loaded: try { p.load; } catch (Exception e) { logger.warn ("Font style "~name~" failed to load; reverting to the fallback style."); return fallback; } return *p; } private: FT_Library library; FontTexture fontTex; FontStyle[ID] fonts; // all font styles known about; not necessarily loaded FontStyle fallback; // used when requested font isn't in fonts } //END Static this() {} //BEGIN Mergetag code //NOTE: would it be better not to use a new mergetag file for this? //FIXME: revise when gui can set options void addTag (char[] tp, ID id, char[] dt) { if (tp == "char[]") { if (id == "path") path = parseTo!(char[]) (dt); } else if (tp == "int") { if (id == "size") size = parseTo!(int) (dt); } } void writeAll (ItemDelg) {} // no writing the config for now //END Mergetag code /** 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)? */ void load () in { assert (library !is null, "font: library is null"); } body { if (FT_New_Face (library, toStringz(path), 0, &face)) throw new fontLoadException ("Unable to read font: "~path); if (!FT_IS_SCALABLE (face)) throw new fontLoadException ("Currently no support for non-scalable fonts (which " ~ path ~ " 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; } /** 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) { 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). * * 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 currently variable, depending on the * highest glyph in the line (should probably be fixed: FIXME). * * Specify the text's colour with col; currently this is only Colour.WHITE or Colour.BLACK * (FIXME). FIXME: add alpha support. * * 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); * --------------------------------- * 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) { try { fontTex.drawCache (face, str, cache, x, y, col); } catch (Exception e) { logger.warn ("Exception while drawing text: "~e.msg); } } /** ditto */ void textBlock (int x, int y, char[] str, Colour col) { 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); } 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) { try { fontTex.drawCacheA (face, str, cache, x, y, col); } catch (Exception e) { logger.warn ("Exception while drawing text: "~e.msg); } } /** The font-specified vertical distance between the baseline of consecutive lines. */ int getLineSeparation () { return face.size.metrics.height >> 6; } ~this () { FT_Done_Face (face); } private: char[] path; // path to font file int size; // font size FT_Face face; debug(mdeUnitTest) unittest { // Don't do a unittest since font relies on loading the freetype library dynamically, // normally done by Init. Also font is mostly visual and many problems will be obvious. } } /+class OptionsFont : Options { alias store!(+/