Mercurial > projects > mde
diff mde/font/font.d @ 63:66d555da083e
Moved many modules/packages to better reflect usage.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Fri, 27 Jun 2008 18:35:33 +0100 |
parents | mde/resource/font.d@7cab2af4ba21 |
children | cc3763817b8a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/font/font.d Fri Jun 27 18:35:33 2008 +0100 @@ -0,0 +1,302 @@ +/* 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.mergetag.Reader; +import mde.mergetag.DataSet; +import mde.mergetag.exception; +import mde.setup.paths; + +import derelict.freetype.ft; +import derelict.opengl.gl; + +import tango.scrapple.text.convert.parseTo : parseTo; +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. */ + void initialize () { + if (!confDir.exists (fileName)) + throw new fontException ("No font settings file (fonts.[mtt|mtb])"); + + 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) { + char[128] tmp; + logger.warn (logger.format (tmp, "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). + Options.setInt ("font", "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. */ + Options.setInt ("font", "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.Arg!(char[]).Arg; + if (p is null) + throw new fontException ("No fallback font style specified"); + fallbackName = *p; + } + catch (MTException e) { + throw new fontException ("Mergetag exception: "~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; + } + private const fileName = "fonts"; + + //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; +} + +/+class OptionsFont : Options { + alias store!(+/ \ No newline at end of file