Mercurial > projects > mde
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!(+/ |