Mercurial > projects > dynamin
comparison dynamin/painting/text_layout.d @ 91:85cd0b04777c
Add TextLayout.formatRuns and fontFormatRuns, and tests for formatRuns
author | Jordan Miner <jminer7@gmail.com> |
---|---|
date | Thu, 05 Aug 2010 03:31:21 -0500 |
parents | 41b430aa319f |
children | 27445f24d5fd |
comparison
equal
deleted
inserted
replaced
90:41b430aa319f | 91:85cd0b04777c |
---|---|
179 data.multiple = spacing; | 179 data.multiple = spacing; |
180 else | 180 else |
181 throw new Exception("unknown type"); | 181 throw new Exception("unknown type"); |
182 return data; | 182 return data; |
183 } | 183 } |
184 void setDataForType(FormatType type, FormatData data) { | |
185 if(type == FormatType.FontFamily) | |
186 fontFamily = data.family; | |
187 else if(type == FormatType.FontSize) | |
188 fontSize = data.size; | |
189 else if(type == FormatType.Bold) | |
190 bold = data.on; | |
191 else if(type == FormatType.Italic) | |
192 italic = data.on; | |
193 else if(type == FormatType.Underline) | |
194 underline = data.style; | |
195 else if(type == FormatType.Strikethrough) | |
196 strikethrough = data.style; | |
197 else if(type == FormatType.Overline) | |
198 overline = data.style; | |
199 else if(type == FormatType.Small) | |
200 small = data.type; | |
201 else if(type == FormatType.ForeColor) | |
202 foreColor = data.color; | |
203 else if(type == FormatType.BackColor) | |
204 backColor = data.color; | |
205 else if(type == FormatType.Spacing) | |
206 spacing = data.multiple; | |
207 else | |
208 throw new Exception("unknown type"); | |
209 } | |
184 } | 210 } |
185 //}}} | 211 //}}} |
186 | 212 |
187 /// | 213 /// |
188 enum TextAlignment { | 214 enum TextAlignment { |
244 uint index = 0; | 270 uint index = 0; |
245 foreach(change; formatting) { | 271 foreach(change; formatting) { |
246 assert(change.index >= index); | 272 assert(change.index >= index); |
247 index = change.index; | 273 index = change.index; |
248 } | 274 } |
275 } | |
276 | |
277 /// | |
278 struct FormatRunsIter { | |
279 private: | |
280 TextLayout owner; | |
281 FormatType[] filter; | |
282 int delegate(uint index) splitter; | |
283 public: | |
284 /// | |
285 int opApply(int delegate(ref uint start, ref uint length, ref Format format) dg) { | |
286 bool inFilter(FormatType type) { | |
287 return filter.length == 0 || filter.containsT(type); | |
288 } | |
289 with(owner) { | |
290 int result; | |
291 uint fIndex = 0; // index of formatting array | |
292 uint sIndex = 0; // index passed to splitter | |
293 Format format = initialFormat; | |
294 | |
295 uint start = 0; | |
296 uint end; | |
297 while(start != text.length) { | |
298 end = text.length; | |
299 // stop looping when one is found that is greater than start and in the filter | |
300 while(fIndex < formatting.count && (formatting[fIndex].index <= start || | |
301 !inFilter(formatting[fIndex].type))) { | |
302 // the only ones that are greater are ones skipped due to filter | |
303 assert(formatting[fIndex].index >= start); | |
304 format.setDataForType(formatting[fIndex].type, formatting[fIndex].data); | |
305 fIndex++; | |
306 } | |
307 if(fIndex < formatting.count) | |
308 end = formatting[fIndex].index; | |
309 | |
310 if(splitter) { | |
311 while(splitter(sIndex) != -1 && splitter(sIndex) <= start) | |
312 sIndex++; | |
313 if(splitter(sIndex) != -1 && splitter(sIndex) < end) | |
314 end = splitter(sIndex); | |
315 } | |
316 if(end == start) | |
317 end = text.length; | |
318 | |
319 uint _start = start; | |
320 uint _length = end - start; | |
321 result = dg(_start, _length, format); | |
322 if(result) | |
323 break; | |
324 | |
325 start = end; | |
326 } | |
327 | |
328 return result; | |
329 } | |
330 } | |
331 } | |
332 | |
333 /** | |
334 * Returns an iterator struct that can be used with foreach. The struct will iterate over | |
335 * each range of the text that has the same formatting. | |
336 * | |
337 * The difference between formatRuns and fontFormatRuns is that fontFormatRuns ignores | |
338 * all formatting except FontFamily, FontSize, Bold, Italic, and Small, whereas formatRuns | |
339 * does not ignore any formatting. | |
340 * | |
341 * If the optional delegate is specified, it will be called with index = 0, then index = 1, | |
342 * and so on. The delegate should return an index to split the ranges at. When there are no | |
343 * more indexes to split the ranges at, the delegate should return -1. It is a way of | |
344 * giving an array of indexes to split at without allocating an array. | |
345 * | |
346 * Example: | |
347 * ----- | |
348 * foreach(start, length, format; textLayout.formatRuns) { | |
349 * // code goes here | |
350 * } | |
351 * ----- | |
352 */ | |
353 FormatRunsIter formatRuns(int delegate(uint index) splitter = null) { | |
354 FormatRunsIter iter; | |
355 iter.owner = this; | |
356 iter.splitter = splitter; | |
357 return iter; | |
358 } | |
359 | |
360 /// ditto | |
361 FormatRunsIter fontFormatRuns(int delegate(uint index) splitter = null) { | |
362 FormatRunsIter iter; | |
363 iter.owner = this; | |
364 iter.filter = [FormatType.FontFamily, | |
365 FormatType.FontSize, | |
366 FormatType.Bold, | |
367 FormatType.Italic, | |
368 FormatType.Small]; | |
369 iter.splitter = splitter; | |
370 return iter; | |
249 } | 371 } |
250 | 372 |
251 //{{{ character formatting | 373 //{{{ character formatting |
252 void setFontFamily(string family, uint start, uint length) { | 374 void setFontFamily(string family, uint start, uint length) { |
253 FormatData data; | 375 FormatData data; |
422 assert(t.formatting[0].data.style == LineStyle.Single); | 544 assert(t.formatting[0].data.style == LineStyle.Single); |
423 assert(t.formatting[0].index == 18); | 545 assert(t.formatting[0].index == 18); |
424 t.setUnderline(LineStyle.None, 4, 20); // "are you doing today?" | 546 t.setUnderline(LineStyle.None, 4, 20); // "are you doing today?" |
425 assert(t.formatting.count == 0); | 547 assert(t.formatting.count == 0); |
426 } | 548 } |
427 | 549 unittest { |
550 auto t = new TextLayout("Arial", 14); | |
551 t.text = "The computer is black."; | |
552 int[][] runs = [[0, 22]]; | |
553 int delegate(uint index) splitter; | |
554 void checkRuns() { | |
555 int i = 0; | |
556 foreach(s, l, f; t.formatRuns(splitter)) { | |
557 assert(runs[i][0] == s && runs[i][1] == l); | |
558 i++; | |
559 } | |
560 } | |
561 // no formatting or splitter | |
562 checkRuns(); | |
563 | |
564 // with a splitter but no formatting | |
565 splitter = delegate int(uint i) { return [0, -1][i]; }; | |
566 checkRuns(); | |
567 splitter = delegate int(uint i) { return [22, -1][i]; }; | |
568 checkRuns(); | |
569 splitter = delegate int(uint i) { return [10, 15, -1][i]; }; | |
570 runs = [[0, 10], [10, 5], [15, 7]]; | |
571 checkRuns(); | |
572 splitter = null; | |
573 | |
574 // with formatting but no splitter | |
575 t.setFontFamily("Tahoma", 4, 8); // "computer" | |
576 runs = [[0, 4], [4, 8], [12, 10]]; | |
577 checkRuns(); | |
578 | |
579 t.setUnderline(LineStyle.Single, 12, 3); // " is" | |
580 runs = [[0, 4], [4, 8], [12, 3], [15,7]]; | |
581 checkRuns(); // test two FormatChanges at the same index | |
582 | |
583 t.setUnderline(LineStyle.Double, 0, 22); | |
584 runs = [[0, 4], [4, 8], [12, 10]]; | |
585 checkRuns(); // test a FormatChange at beginning and at end | |
586 | |
587 splitter = delegate int(uint i) { return [2, 4, 21, -1][i]; }; | |
588 runs = [[0, 2], [2, 2], [4, 8], [12, 9], [21, 1]]; | |
589 checkRuns(); // test a split in middle of a format run and at the same index as a format run | |
590 | |
591 // TODO: test fontFormatRuns | |
592 } | |
593 |