comparison mde/font/font.d @ 100:0ea4a3e651ae

There is now a position marker for text editing. Changed the way fonts are configured. Actually, not much of the new way exists yet.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 15 Nov 2008 17:39:14 +0000
parents 49e7cfed4b34
children 71f0f1f83620
comparison
equal deleted inserted replaced
99:5de5810e3516 100:0ea4a3e651ae
19 public import mde.types.Colour; 19 public import mde.types.Colour;
20 import mde.lookup.Options; 20 import mde.lookup.Options;
21 import mde.font.FontTexture; 21 import mde.font.FontTexture;
22 import mde.font.exception; 22 import mde.font.exception;
23 23
24 import mde.file.mergetag.Reader;
25 import mde.file.mergetag.DataSet;
26 import mde.setup.paths;
27 import mde.setup.exception; // InitStage stuff 24 import mde.setup.exception; // InitStage stuff
28 25
29 import derelict.freetype.ft; 26 import derelict.freetype.ft;
30 27
31 import mde.file.deserialize;
32 import tango.stdc.stringz; 28 import tango.stdc.stringz;
33 import Util = tango.text.Util; 29 import Util = tango.text.Util;
34 import tango.util.log.Log : Log, Logger; 30 import tango.util.log.Log : Log, Logger;
35 31
36 // "Publically import" this symbol: 32 // "Publically import" this symbol:
44 /** FontStyle class. 40 /** FontStyle class.
45 * 41 *
46 * Particular to a font and size, and any other effects like bold/italic if ever implemented. 42 * Particular to a font and size, and any other effects like bold/italic if ever implemented.
47 * 43 *
48 * Note: it is not currently intended to be thread-safe. */ 44 * Note: it is not currently intended to be thread-safe. */
49 class FontStyle : IDataSection 45 class FontStyle
50 { 46 {
51 //BEGIN Static: manager 47 //BEGIN Static: manager
52 static { 48 static {
53 debug (drawGlyphCache) void drawTexture() { 49 debug (drawGlyphCache) void drawTexture() {
54 if (fontTex !is null) 50 if (fontTex !is null)
55 fontTex.drawTexture; 51 fontTex.drawTexture;
56 } 52 }
57 53
58 /** Load the freetype library with settings from the file fileName. */ 54 /** Load the freetype library with settings from options. */
59 private const fileName = "fonts";
60 StageState initialize () { 55 StageState initialize () {
61 if (FT_Init_FreeType (&library)) 56 if (FT_Init_FreeType (&library))
62 throw new fontException ("error initialising the FreeType library"); 57 throw new fontException ("error initialising the FreeType library");
63 58
64 // Check version 59 // Check version
65 FT_Int maj, min, patch; 60 FT_Int maj, min, patch;
66 FT_Library_Version (library, &maj, &min, &patch); 61 FT_Library_Version (library, &maj, &min, &patch);
67 if (maj != 2 || min != 3) { 62 if (maj != 2 || min != 3) {
68 logger.warn ("Using an untested FreeType version: {}.{}.{}", maj, min, patch); 63 logger.warn ("Using an untested FreeType version: {}.{}.{}", maj, min, patch);
69 logger.info ("The only tested version of freetype is 2.3.5"); 64 logger.info ("The only tested version of freetype is 2.3");
70 } 65 }
71 66
72 // Set LCD filtering method if LCD rendering is enabled. 67 // Set LCD filtering method if LCD rendering is enabled.
73 const RMF = FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V; 68 const RMF = FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V;
74 if (fontOpts.renderMode() & RMF && 69 if (fontOpts.renderMode() & RMF &&
84 /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3 79 /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3
85 * times wider glyphs. So disable and save the extra work. */ 80 * times wider glyphs. So disable and save the extra work. */
86 fontOpts.renderMode = FT_LOAD_TARGET_NORMAL; 81 fontOpts.renderMode = FT_LOAD_TARGET_NORMAL;
87 } 82 }
88 83
89 /* Load font settings 84 // Load the fallback font; if it throws let exception abort program
90 * 85 fallback = new FontStyle (fontOpts.defaultFont(), fontOpts.defaultSize());
91 * Each mergetag section corresponds to a font; each is loaded whether used or not 86
92 * (however the actual font files are only loaded on use). A fallback id must be
93 * provided in the header which must match a loaded font name; if a non-existant font
94 * is requested a warning will be logged and this font returned. */
95 char[] fallbackName;
96 try {
97 IReader reader;
98 reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH, null, true);
99 reader.dataSecCreator = delegate IDataSection(ID id) {
100 auto f = new FontStyle;
101 fonts[id] = f;
102 return f;
103 };
104 reader.read;
105
106 // get fallback name
107 char[]* p = "fallback" in reader.dataset.header._charA;
108 if (p is null)
109 throw new fontException ("No fallback font style specified");
110 fallbackName = *p;
111 } catch (NoFileException) {
112 throw new fontException ("No font settings file (fonts.[mtt|mtb])");
113 } catch (Exception e) {
114 throw new fontException ("Reading font settings failed: "~e.msg);
115 }
116
117 // Find the fallback
118 FontStyle* p = fallbackName in fonts;
119 if (p is null)
120 throw new fontException ("Fallback font style specified is not found");
121 fallback = *p;
122 // Load the fallback now, to ensure it's available.
123 // Also note that get() doesn't make sure the fallback is loaded before returning it.
124 fallback.load;
125
126 return StageState.ACTIVE; 87 return StageState.ACTIVE;
127 } 88 }
128 89
129 /** Cleanup: delete all fonts. */ 90 /** Cleanup: delete all fonts. */
130 StageState cleanup () { 91 StageState cleanup () {
131 // Clear loaded fonts (each has an FT_Face object needing to be freed): 92 // Clear loaded fonts (each has an FT_Face object needing to be freed):
132 foreach (fs; fonts) 93 /* FIXME
94 foreach (fs; fonts)
133 fs.freeFace; 95 fs.freeFace;
96 */
134 97
135 FT_Done_FreeType (library); // free the library 98 FT_Done_FreeType (library); // free the library
136 99
137 return StageState.INACTIVE; 100 return StageState.INACTIVE;
138 } 101 }
142 * Also loads the font if it's not already loaded, so the first call may take some time. 105 * Also loads the font if it's not already loaded, so the first call may take some time.
143 * 106 *
144 * Uses fallback font-style if the desired style isn't known about or fails to load, so 107 * Uses fallback font-style if the desired style isn't known about or fails to load, so
145 * this function should never fail or throw, in theory (unless out of memory). The 108 * this function should never fail or throw, in theory (unless out of memory). The
146 * fallback should already be loaded. */ 109 * fallback should already be loaded. */
147 FontStyle get(char[] name) { 110 FontStyle getDefault () {
148 FontStyle* p = name in fonts; 111 //FIXME: Ddoc, new purpose; rename
149 if (p is null) { 112 return fallback;
150 logger.warn ("Font style "~name~" requested but not found; reverting to the fallback style.");
151 fonts[name] = fallback; // set to prevent another warning getting logged
152 return fallback;
153 }
154 // Got it, but we need to make sure it's loaded:
155 try {
156 p.load;
157 } catch (Exception e) {
158 logger.warn ("Font style "~name~" failed to load; reverting to the fallback style.");
159 return fallback;
160 }
161 return *p;
162 } 113 }
163 114
164 private: 115 private:
165 FT_Library library; 116 FT_Library library;
166 FontTexture fontTex; 117 FontTexture fontTex;
167 FontStyle[ID] fonts; // all font styles known about; not necessarily loaded 118 //FontStyle[char[]] fonts; // loaded fonts, by file name FIXME: use hash of struct { char[] path; int size; }?
168 FontStyle fallback; // used when requested font isn't in fonts 119 FontStyle fallback; // default & used when requested font can't be loaded
169 } 120 }
170 //END Static 121 //END Static
171
172 this() {}
173
174 //BEGIN Mergetag code
175 //NOTE: would it be better not to use a new mergetag file for this?
176 //FIXME: revise when gui can set options
177 void addTag (char[] tp, ID id, char[] dt) {
178 if (tp == "char[]") {
179 if (id == "path")
180 path = parseTo!(char[]) (dt);
181 }
182 else if (tp == "int") {
183 if (id == "size")
184 size = parseTo!(int) (dt);
185 }
186 }
187 void writeAll (ItemDelg) {} // no writing the config for now
188 //END Mergetag code
189 122
190 /** Load the font file. 123 /** Load the font file.
191 * 124 *
192 * Even if the same font is used at multiple sizes, multiple copies of FT_Face are used. 125 * Even if the same font is used at multiple sizes, multiple copies of FT_Face are used.
193 * Sharing an FT_Face would require calling FT_Set_Pixel_Sizes each time a glyph is rendered or 126 * Sharing an FT_Face would require calling FT_Set_Pixel_Sizes each time a glyph is rendered or
194 * swapping the size information (face.size)? */ 127 * swapping the size information (face.size)? */
195 void load () 128 this (char[] path, int size)
196 in { 129 in {
197 assert (library !is null, "font: library is null"); 130 assert (library !is null, "font: library is null");
198 } body { 131 } body {
199 if (FT_New_Face (library, toStringz(path), 0, &face)) 132 if (FT_New_Face (library, toStringz(path), 0, &face))
200 throw new fontLoadException ("Unable to read font: "~path); 133 throw new fontLoadException ("Unable to read font: "~path);
204 path ~ " is). Please report if you want to see support."); 137 path ~ " is). Please report if you want to see support.");
205 /* The following will need to be addressed when adding support for non-scalables: 138 /* The following will need to be addressed when adding support for non-scalables:
206 * Use of face.size.metrics.height property. 139 * Use of face.size.metrics.height property.
207 */ 140 */
208 141
209 if (FT_Set_Pixel_Sizes (face, 0,size)) 142 if (FT_Set_Pixel_Sizes (face, 0, size))
210 throw new fontLoadException ("Unable to set pixel size"); 143 throw new fontLoadException ("Unable to set pixel size");
211 144
212 // Create if necessary: 145 // Create if necessary:
213 if (fontTex is null) 146 if (fontTex is null)
214 fontTex = new FontTexture; 147 fontTex = new FontTexture;
148
149 return this;
150 }
151
152 this (FontStyle font, int size) {
153 //FIXME: copy font's face and set new size?
215 } 154 }
216 155
217 /** Update a TextBlock cache, as used by the textBlock function. 156 /** Update a TextBlock cache, as used by the textBlock function.
218 * 157 *
219 * The only use of this is to get the text block's size ahead of rendering, via TextBlock's w 158 * The only use of this is to get the text block's size ahead of rendering, via TextBlock's w
232 } 171 }
233 } 172 }
234 173
235 /** Draw a block of text (may inlcude new-lines). 174 /** Draw a block of text (may inlcude new-lines).
236 * 175 *
176 * Params:
177 * x = Top left x-coordinate of text block
178 * y = Top left y-coordinate of text block
179 * str = Text to render
180 * cache = An (optional) TextBlock used for rendering this text, to save some CPU work. See updateBlock
181 * col = Colour to render the text; see mde.types.Colour
182 * index = Either the index of the character to draw with an edit cursor or size_t.max. Not
183 * the index within str, but the number of characters, excluding new-lines.
237 * The text block is drawn with top-left corner at x,y. To put the text's baseline at a given 184 * The text block is drawn with top-left corner at x,y. To put the text's baseline at a given
238 * y coordinate would require some changes. Line height is fixed based on largest glyph. 185 * y coordinate would require some changes. Line height is fixed (see getLineSeparation).
239 * Due to hinter, glyphs are not guaranteed to lie within the "bounding box" defined by cache. 186 * Due to hinter, glyphs are not guaranteed to lie within the "bounding box" defined by cache.
240 * Can be changed to test size of each glyph if necessary. 187 * Can be changed to test size of each glyph if necessary.
241 * 188 *
242 * Specify the text's colour with col. Use textBlockA() instead for transparent text. 189 * Specify the text's colour with col. Use textBlockA() instead for transparent text.
243 * 190 *
245 * the cache argument. This is the recommended method, although for one-time calls when you 192 * the cache argument. This is the recommended method, although for one-time calls when you
246 * don't need to know the size, the other version of textBlock may be used. 193 * don't need to know the size, the other version of textBlock may be used.
247 * --------------------------------- 194 * ---------------------------------
248 * char[] str; 195 * char[] str;
249 * TextBlock strCache; 196 * TextBlock strCache;
250 * textBlock (x, y, str, strCache); 197 * textBlock (x, y, str, strCache, Colour.WHITE);
251 * --------------------------------- 198 * ---------------------------------
252 * The TextBlock cache will be updated as necessary. Besides the initial update, this will only 199 * The TextBlock cache will be updated as necessary. Besides the initial update, this will only
253 * be if the font changes, or it is manually invalidated. This can be done by setting the 200 * be if the font changes, or it is manually invalidated. This can be done by setting the
254 * TextBlock's cacheVer property to -1, which should be done if str is changed. 201 * TextBlock's cacheVer property to -1, which should be done if str is changed.
255 * 202 *
256 * The TextBlock's w and h properties are set to the size (in pixels) of the text block; other 203 * The TextBlock's w and h properties are set to the size (in pixels) of the text block; other
257 * than this cache only serves as a small optimisation. However, the only way to get the size 204 * than this cache only serves as a small optimisation. However, the only way to get the size
258 * of a text block is to use a TextBlock cache and update it, either with this function or with 205 * of a text block is to use a TextBlock cache and update it, either with this function or with
259 * the updateBlock function. */ 206 * the updateBlock function. */
260 void textBlock (int x, int y, char[] str, ref TextBlock cache, Colour col) 207 void textBlock (int x, int y, char[] str, ref TextBlock cache, Colour col, size_t index = size_t.max)
261 in { 208 in {
262 assert (face, "FontStyle: face is null"); 209 assert (face, "FontStyle: face is null");
263 } body { 210 } body {
264 try { 211 try {
265 fontTex.drawCache (face, str, cache, x, y, col); 212 fontTex.drawCache (face, str, cache, x, y, col, index);
266 } catch (Exception e) { 213 } catch (Exception e) {
267 logger.warn ("Exception while drawing text: "~e.msg); 214 logger.warn ("Exception while drawing text: "~e.msg);
268 } 215 }
269 } 216 }
270 /** ditto */ 217 /** ditto */
271 void textBlock (int x, int y, char[] str, Colour col) 218 void textBlock (int x, int y, char[] str, Colour col, size_t index = size_t.max)
272 in { 219 in {
273 assert (face, "FontStyle: face is null"); 220 assert (face, "FontStyle: face is null");
274 } body { 221 } body {
275 try { 222 try {
276 // Using the cache method for one-time use is slightly less than optimal, but doing so 223 // Using the cache method for one-time use is slightly less than optimal, but doing so
277 // isn't really recommended anyway (and maintaining two versions of fontTex.drawText 224 // isn't really recommended anyway (and maintaining two versions of fontTex.drawText
278 // would be horrible). 225 // would be horrible).
279 TextBlock cache; 226 TextBlock cache;
280 fontTex.drawCache (face, str, cache, x, y, col); 227 fontTex.drawCache (face, str, cache, x, y, col, index);
281 } catch (Exception e) { 228 } catch (Exception e) {
282 logger.warn ("Exception while drawing text: "~e.msg); 229 logger.warn ("Exception while drawing text: "~e.msg);
283 } 230 }
284 } 231 }
285 232
286 /** A variation of textBlock for transparency. 233 /** A variation of textBlock for transparency.
287 * 234 *
288 * Set the alpha by calling glColor*() first. See FontTexture.drawCacheA()'s documentation for 235 * Set the alpha by calling glColor*() first. See FontTexture.drawCacheA()'s documentation for
289 * details. */ 236 * details. */
290 void textBlockA (int x, int y, char[] str, ref TextBlock cache, Colour col) 237 void textBlockA (int x, int y, char[] str, ref TextBlock cache, Colour col, size_t index = size_t.max)
291 in { 238 in {
292 assert (face, "FontStyle: face is null"); 239 assert (face, "FontStyle: face is null");
293 } body { 240 } body {
294 try { 241 try {
295 fontTex.drawCacheA (face, str, cache, x, y, col); 242 fontTex.drawCacheA (face, str, cache, x, y, col, index);
296 } catch (Exception e) { 243 } catch (Exception e) {
297 logger.warn ("Exception while drawing text: "~e.msg); 244 logger.warn ("Exception while drawing text: "~e.msg);
298 } 245 }
299 } 246 }
300 247
310 FT_Done_Face (face); 257 FT_Done_Face (face);
311 face = null; // functions using face use assertions on face to check its validity. 258 face = null; // functions using face use assertions on face to check its validity.
312 } 259 }
313 260
314 private: 261 private:
315 char[] path; // path to font file
316 int size; // font size
317
318 FT_Face face; 262 FT_Face face;
319
320 debug(mdeUnitTest) unittest {
321 // Don't do a unittest since font relies on loading the freetype library dynamically,
322 // normally done by Init. Also font is mostly visual and many problems will be obvious.
323 }
324 } 263 }
325
326 /+class OptionsFont : Options {
327 alias store!(+/