comparison 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
comparison
equal deleted inserted replaced
62:960206198cbd 63:66d555da083e
1 /* LICENSE BLOCK
2 Part of mde: a Modular D game-oriented Engine
3 Copyright © 2007-2008 Diggory Hardy
4
5 This program is free software: you can redistribute it and/or modify it under the terms
6 of the GNU General Public License as published by the Free Software Foundation, either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16 /// Sets up freetype (in a basic way).
17 module mde.font.font;
18
19 public import mde.types.Colour;
20 import mde.lookup.Options;
21 import mde.font.FontTexture;
22 import mde.font.exception;
23
24 import mde.mergetag.Reader;
25 import mde.mergetag.DataSet;
26 import mde.mergetag.exception;
27 import mde.setup.paths;
28
29 import derelict.freetype.ft;
30 import derelict.opengl.gl;
31
32 import tango.scrapple.text.convert.parseTo : parseTo;
33 import tango.stdc.stringz;
34 import Util = tango.text.Util;
35 import tango.util.log.Log : Log, Logger;
36
37 // "Publically import" this symbol:
38 alias mde.font.FontTexture.TextBlock TextBlock;
39
40 private Logger logger;
41 static this () {
42 logger = Log.getLogger ("mde.font.font");
43 }
44
45 /** FontStyle class.
46 *
47 * Particular to a font and size, and any other effects like bold/italic if ever implemented.
48 *
49 * Note: it is not currently intended to be thread-safe. */
50 class FontStyle : IDataSection
51 {
52 //BEGIN Static: manager
53 static {
54 debug (drawGlyphCache) void drawTexture() {
55 if (fontTex !is null)
56 fontTex.drawTexture;
57 }
58
59 /** Load the freetype library. */
60 void initialize () {
61 if (!confDir.exists (fileName))
62 throw new fontException ("No font settings file (fonts.[mtt|mtb])");
63
64 if (FT_Init_FreeType (&library))
65 throw new fontException ("error initialising the FreeType library");
66
67 // Check version
68 FT_Int maj, min, patch;
69 FT_Library_Version (library, &maj, &min, &patch);
70 if (maj != 2 || min != 3) {
71 char[128] tmp;
72 logger.warn (logger.format (tmp, "Using an untested FreeType version: {}.{}.{}", maj, min, patch));
73 }
74
75 // Set LCD filtering method if LCD rendering is enabled.
76 const RMF = FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V;
77 if (fontOpts.renderMode & RMF &&
78 FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) {
79 /* An error occurred, presumably because LCD rendering support
80 * is not compiled into the library. */
81 logger.warn ("Bad/unsupported LCD filter option; disabling LCD font rendering.");
82 logger.warn ("Your FreeType 2 library may compiled without support for LCD/sub-pixel rendering.");
83
84 // Reset the default filter (in case an invalid value was set in config files).
85 Options.setInt ("font", "lcdFilter", FT_LcdFilter.FT_LCD_FILTER_DEFAULT);
86
87 /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3
88 * times wider glyphs. So disable and save the extra work. */
89 Options.setInt ("font", "renderMode", FT_LOAD_TARGET_NORMAL);
90 }
91
92 /* Load font settings
93 *
94 * Each mergetag section corresponds to a font; each is loaded whether used or not
95 * (however the actual font files are only loaded on use). A fallback id must be
96 * provided in the header which must match a loaded font name; if a non-existant font
97 * is requested a warning will be logged and this font returned. */
98 char[] fallbackName;
99 try {
100 IReader reader;
101 reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH, null, true);
102 reader.dataSecCreator = delegate IDataSection(ID id) {
103 auto f = new FontStyle;
104 fonts[id] = f;
105 return f;
106 };
107 reader.read;
108
109 // get fallback name
110 char[]* p = "fallback" in reader.dataset.header.Arg!(char[]).Arg;
111 if (p is null)
112 throw new fontException ("No fallback font style specified");
113 fallbackName = *p;
114 }
115 catch (MTException e) {
116 throw new fontException ("Mergetag exception: "~e.msg);
117 }
118
119 // Find the fallback
120 FontStyle* p = fallbackName in fonts;
121 if (p is null)
122 throw new fontException ("Fallback font style specified is not found");
123 fallback = *p;
124 // Load the fallback now, to ensure it's available.
125 // Also note that get() doesn't make sure the fallback is loaded before returning it.
126 fallback.load;
127 }
128 private const fileName = "fonts";
129
130 //FIXME: don't use GC for FontStyle resources
131 /** Cleanup: delete all fonts. */
132 void cleanup () {
133 FT_Done_FreeType (library);
134 }
135
136 /** Get a FontStyle instance, for a section in the fonts.mtt file.
137 *
138 * Also loads the font if it's not already loaded, so the first call may take some time.
139 *
140 * Uses fallback font-style if the desired style isn't known about or fails to load, so
141 * this function should never fail or throw, in theory (unless out of memory). The
142 * fallback should already be loaded. */
143 FontStyle get(char[] name) {
144 FontStyle* p = name in fonts;
145 if (p is null) {
146 logger.warn ("Font style "~name~" requested but not found; reverting to the fallback style.");
147 fonts[name] = fallback; // set to prevent another warning getting logged
148 return fallback;
149 }
150 // Got it, but we need to make sure it's loaded:
151 try {
152 p.load;
153 } catch (Exception e) {
154 logger.warn ("Font style "~name~" failed to load; reverting to the fallback style.");
155 return fallback;
156 }
157 return *p;
158 }
159
160 private:
161 FT_Library library;
162 FontTexture fontTex;
163 FontStyle[ID] fonts; // all font styles known about; not necessarily loaded
164 FontStyle fallback; // used when requested font isn't in fonts
165 }
166 //END Static
167
168 this() {}
169
170 //BEGIN Mergetag code
171 //NOTE: would it be better not to use a new mergetag file for this?
172 //FIXME: revise when gui can set options
173 void addTag (char[] tp, ID id, char[] dt) {
174 if (tp == "char[]") {
175 if (id == "path")
176 path = parseTo!(char[]) (dt);
177 }
178 else if (tp == "int") {
179 if (id == "size")
180 size = parseTo!(int) (dt);
181 }
182 }
183 void writeAll (ItemDelg) {} // no writing the config for now
184 //END Mergetag code
185
186 /** Load the font file.
187 *
188 * Even if the same font is used at multiple sizes, multiple copies of FT_Face are used.
189 * Sharing an FT_Face would require calling FT_Set_Pixel_Sizes each time a glyph is rendered or
190 * swapping the size information (face.size)? */
191 void load ()
192 in {
193 assert (library !is null, "font: library is null");
194 } body {
195 if (FT_New_Face (library, toStringz(path), 0, &face))
196 throw new fontLoadException ("Unable to read font: "~path);
197
198 if (!FT_IS_SCALABLE (face))
199 throw new fontLoadException ("Currently no support for non-scalable fonts (which " ~
200 path ~ " is). Please report if you want to see support.");
201 /* The following will need to be addressed when adding support for non-scalables:
202 * Use of face.size.metrics.height property.
203 */
204
205 if (FT_Set_Pixel_Sizes (face, 0,size))
206 throw new fontLoadException ("Unable to set pixel size");
207
208 // Create if necessary:
209 if (fontTex is null)
210 fontTex = new FontTexture;
211 }
212
213 /** Update a TextBlock cache, as used by the textBlock function.
214 *
215 * The only use of this is to get the text block's size ahead of rendering, via TextBlock's w
216 * and h properties.
217 *
218 * This function will only actually update the cache if it is invalid, caused either by the
219 * font being changed or if cache.cacheVer < 0. */
220 void updateBlock (char[] str, ref TextBlock cache) {
221 try {
222 fontTex.updateCache (face, str, cache);
223 } catch (Exception e) {
224 logger.warn ("Exception while drawing text: "~e.msg);
225 }
226 }
227
228 /** Draw a block of text (may inlcude new-lines).
229 *
230 * The text block is drawn with top-left corner at x,y. To put the text's baseline at a given
231 * y coordinate would require some changes. Line height is currently variable, depending on the
232 * highest glyph in the line (should probably be fixed: FIXME).
233 *
234 * Specify the text's colour with col; currently this is only Colour.WHITE or Colour.BLACK
235 * (FIXME). FIXME: add alpha support.
236 *
237 * As a CPU-side code optimisation, store a TextBlock (unique to str) and pass a reference as
238 * the cache argument. This is the recommended method, although for one-time calls when you
239 * don't need to know the size, the other version of textBlock may be used.
240 * ---------------------------------
241 * char[] str;
242 * TextBlock strCache;
243 * textBlock (x, y, str, strCache);
244 * ---------------------------------
245 * The TextBlock cache will be updated as necessary. Besides the initial update, this will only
246 * be if the font changes, or it is manually invalidated. This can be done by setting the
247 * TextBlock's cacheVer property to -1, which should be done if str is changed.
248 *
249 * The TextBlock's w and h properties are set to the size (in pixels) of the text block; other
250 * than this cache only serves as a small optimisation. However, the only way to get the size
251 * of a text block is to use a TextBlock cache and update it, either with this function or with
252 * the updateBlock function. */
253 void textBlock (int x, int y, char[] str, ref TextBlock cache, Colour col) {
254 try {
255 fontTex.drawCache (face, str, cache, x, y, col);
256 } catch (Exception e) {
257 logger.warn ("Exception while drawing text: "~e.msg);
258 }
259 }
260 /** ditto */
261 void textBlock (int x, int y, char[] str, Colour col) {
262 try {
263 // Using the cache method for one-time use is slightly less than optimal, but doing so
264 // isn't really recommended anyway (and maintaining two versions of fontTex.drawText
265 // would be horrible).
266 TextBlock cache;
267 fontTex.drawCache (face, str, cache, x, y, col);
268 } catch (Exception e) {
269 logger.warn ("Exception while drawing text: "~e.msg);
270 }
271 }
272
273 /** A variation of textBlock for transparency.
274 *
275 * Set the alpha by calling glColor*() first. See FontTexture.drawCacheA()'s documentation for
276 * details. */
277 void textBlockA (int x, int y, char[] str, ref TextBlock cache, Colour col) {
278 try {
279 fontTex.drawCacheA (face, str, cache, x, y, col);
280 } catch (Exception e) {
281 logger.warn ("Exception while drawing text: "~e.msg);
282 }
283 }
284
285 /** The font-specified vertical distance between the baseline of consecutive lines. */
286 int getLineSeparation () {
287 return face.size.metrics.height >> 6;
288 }
289
290 ~this () {
291 FT_Done_Face (face);
292 }
293
294 private:
295 char[] path; // path to font file
296 int size; // font size
297
298 FT_Face face;
299 }
300
301 /+class OptionsFont : Options {
302 alias store!(+/