2009-12-03 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / layout.cpp
blob79dd770cdb8bba33d1c8d27a2c6e94d56809a708
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * layout.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2008 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <ctype.h>
16 #include <math.h>
18 #include "moon-path.h"
19 #include "layout.h"
20 #include "debug.h"
23 #if DEBUG
24 #define d(x) if (debug_flags & RUNTIME_DEBUG_LAYOUT) x
25 #else
26 #define d(x)
27 #endif
29 #if DEBUG
30 static const char *unicode_break_types[] = {
31 "G_UNICODE_BREAK_MANDATORY",
32 "G_UNICODE_BREAK_CARRIAGE_RETURN",
33 "G_UNICODE_BREAK_LINE_FEED",
34 "G_UNICODE_BREAK_COMBINING_MARK",
35 "G_UNICODE_BREAK_SURROGATE",
36 "G_UNICODE_BREAK_ZERO_WIDTH_SPACE",
37 "G_UNICODE_BREAK_INSEPARABLE",
38 "G_UNICODE_BREAK_NON_BREAKING_GLUE",
39 "G_UNICODE_BREAK_CONTINGENT",
40 "G_UNICODE_BREAK_SPACE",
41 "G_UNICODE_BREAK_AFTER",
42 "G_UNICODE_BREAK_BEFORE",
43 "G_UNICODE_BREAK_BEFORE_AND_AFTER",
44 "G_UNICODE_BREAK_HYPHEN",
45 "G_UNICODE_BREAK_NON_STARTER",
46 "G_UNICODE_BREAK_OPEN_PUNCTUATION",
47 "G_UNICODE_BREAK_CLOSE_PUNCTUATION",
48 "G_UNICODE_BREAK_QUOTATION",
49 "G_UNICODE_BREAK_EXCLAMATION",
50 "G_UNICODE_BREAK_IDEOGRAPHIC",
51 "G_UNICODE_BREAK_NUMERIC",
52 "G_UNICODE_BREAK_INFIX_SEPARATOR",
53 "G_UNICODE_BREAK_SYMBOL",
54 "G_UNICODE_BREAK_ALPHABETIC",
55 "G_UNICODE_BREAK_PREFIX",
56 "G_UNICODE_BREAK_POSTFIX",
57 "G_UNICODE_BREAK_COMPLEX_CONTEXT",
58 "G_UNICODE_BREAK_AMBIGUOUS",
59 "G_UNICODE_BREAK_UNKNOWN",
60 "G_UNICODE_BREAK_NEXT_LINE",
61 "G_UNICODE_BREAK_WORD_JOINER",
62 "G_UNICODE_BREAK_HANGUL_L_JAMO",
63 "G_UNICODE_BREAK_HANGUL_V_JAMO",
64 "G_UNICODE_BREAK_HANGUL_T_JAMO",
65 "G_UNICODE_BREAK_HANGUL_LV_SYLLABLE",
66 "G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE"
69 static const char *unicode_char_types[] = {
70 "G_UNICODE_CONTROL",
71 "G_UNICODE_FORMAT",
72 "G_UNICODE_UNASSIGNED",
73 "G_UNICODE_PRIVATE_USE",
74 "G_UNICODE_SURROGATE",
75 "G_UNICODE_LOWERCASE_LETTER",
76 "G_UNICODE_MODIFIER_LETTER",
77 "G_UNICODE_OTHER_LETTER",
78 "G_UNICODE_TITLECASE_LETTER",
79 "G_UNICODE_UPPERCASE_LETTER",
80 "G_UNICODE_COMBINING_MARK",
81 "G_UNICODE_ENCLOSING_MARK",
82 "G_UNICODE_NON_SPACING_MARK",
83 "G_UNICODE_DECIMAL_NUMBER",
84 "G_UNICODE_LETTER_NUMBER",
85 "G_UNICODE_OTHER_NUMBER",
86 "G_UNICODE_CONNECT_PUNCTUATION",
87 "G_UNICODE_DASH_PUNCTUATION",
88 "G_UNICODE_CLOSE_PUNCTUATION",
89 "G_UNICODE_FINAL_PUNCTUATION",
90 "G_UNICODE_INITIAL_PUNCTUATION",
91 "G_UNICODE_OTHER_PUNCTUATION",
92 "G_UNICODE_OPEN_PUNCTUATION",
93 "G_UNICODE_CURRENCY_SYMBOL",
94 "G_UNICODE_MODIFIER_SYMBOL",
95 "G_UNICODE_MATH_SYMBOL",
96 "G_UNICODE_OTHER_SYMBOL",
97 "G_UNICODE_LINE_SEPARATOR",
98 "G_UNICODE_PARAGRAPH_SEPARATOR",
99 "G_UNICODE_SPACE_SEPARATOR"
101 #endif
103 #define UnicharIsLineBreak(c) ((c) == '\r' || (c) == '\n' || (c) == 0x2028)
105 #define BreakSpace(c, btype) (c == '\t' || btype == G_UNICODE_BREAK_SPACE || btype == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
109 * Silverlight does not apply any kerning on a DOT, so we exclude them
110 * U+002E FULL STOP
111 * U+06D4 ARABIC FULL STOP
112 * U+3002 IDEOGRAPHIC FULL STOP
113 * Note: this is different than using the "sliding dot" algorithm from
114 * http://www.freetype.org/freetype2/docs/glyphs/glyphs-4.html
116 #define APPLY_KERNING(uc) ((uc != 0x002E) && (uc != 0x06D4) && (uc != 3002))
119 static inline gunichar
120 utf8_getc (const char **in, size_t inlen)
122 register const unsigned char *inptr = (const unsigned char *) *in;
123 const unsigned char *inend = inptr + inlen;
124 register unsigned char c, r;
125 register gunichar m, u = 0;
127 if (inlen == 0)
128 return 0;
130 r = *inptr++;
131 if (r < 0x80) {
132 *in = (const char *) inptr;
133 u = r;
134 } else if (r < 0xfe) { /* valid start char? */
135 u = r;
136 m = 0x7f80; /* used to mask out the length bits */
137 do {
138 if (inptr >= inend) {
139 *in = (const char *) inend;
140 return 0;
143 c = *inptr++;
144 if ((c & 0xc0) != 0x80)
145 goto error;
147 u = (u << 6) | (c & 0x3f);
148 r <<= 1;
149 m <<= 5;
150 } while (r & 0x40);
152 *in = (const char *) inptr;
154 u &= ~m;
155 } else {
156 error:
157 u = (gunichar) -1;
158 *in = (*in) + 1;
161 return u;
166 // TextLayoutGlyphCluster
169 TextLayoutGlyphCluster::TextLayoutGlyphCluster (int _start, int _length)
171 length = _length;
172 start = _start;
173 selected = false;
174 advance = 0.0;
175 path = NULL;
178 TextLayoutGlyphCluster::~TextLayoutGlyphCluster ()
180 if (path)
181 moon_path_destroy (path);
186 // TextLayoutRun
189 TextLayoutRun::TextLayoutRun (TextLayoutLine *_line, TextLayoutAttributes *_attrs, int _start)
191 clusters = g_ptr_array_new ();
192 attrs = _attrs;
193 start = _start;
194 line = _line;
195 advance = 0.0;
196 length = 0;
197 count = 0;
200 TextLayoutRun::~TextLayoutRun ()
202 for (guint i = 0; i < clusters->len; i++)
203 delete (TextLayoutGlyphCluster *) clusters->pdata[i];
205 g_ptr_array_free (clusters, true);
208 void
209 TextLayoutRun::ClearCache ()
211 for (guint i = 0; i < clusters->len; i++)
212 delete (TextLayoutGlyphCluster *) clusters->pdata[i];
214 g_ptr_array_set_size (clusters, 0);
219 // TextLayoutLine
222 TextLayoutLine::TextLayoutLine (TextLayout *_layout, int _start, int _offset)
224 runs = g_ptr_array_new ();
225 layout = _layout;
226 offset = _offset;
227 start = _start;
228 advance = 0.0;
229 descend = 0.0;
230 height = 0.0;
231 width = 0.0;
232 length = 0;
233 count = 0;
236 TextLayoutLine::~TextLayoutLine ()
238 for (guint i = 0; i < runs->len; i++)
239 delete (TextLayoutRun *) runs->pdata[i];
241 g_ptr_array_free (runs, true);
247 // TextLayout
250 TextLayout::TextLayout ()
252 // Note: TextBlock and TextBox assume their default values match these
253 strategy = LineStackingStrategyMaxHeight;
254 alignment = TextAlignmentLeft;
255 wrapping = TextWrappingNoWrap;
256 selection_length = 0;
257 selection_start = 0;
258 avail_width = INFINITY;
259 max_height = INFINITY;
260 max_width = INFINITY;
261 base_descent = 0.0;
262 base_height = 0.0;
263 actual_height = NAN;
264 actual_width = NAN;
265 line_height = NAN;
266 attributes = NULL;
267 lines = g_ptr_array_new ();
268 is_wrapped = true;
269 text = NULL;
270 length = 0;
271 count = 0;
274 TextLayout::~TextLayout ()
276 if (attributes) {
277 attributes->Clear (true);
278 delete attributes;
281 ClearLines ();
282 g_ptr_array_free (lines, true);
284 g_free (text);
287 void
288 TextLayout::ClearLines ()
290 for (guint i = 0; i < lines->len; i++)
291 delete (TextLayoutLine *) lines->pdata[i];
293 g_ptr_array_set_size (lines, 0);
296 void
297 TextLayout::ResetState ()
299 actual_height = NAN;
300 actual_width = NAN;
303 void
304 TextLayout::SetBaseFont (const TextFont *font)
306 if (font) {
307 base_descent = font->Descender ();
308 base_height = font->Height ();
309 } else {
310 base_descent = 0.0;
311 base_height = 0.0;
315 bool
316 TextLayout::SetLineStackingStrategy (LineStackingStrategy mode)
318 if (strategy == mode)
319 return false;
321 strategy = mode;
323 ResetState ();
325 return true;
328 bool
329 TextLayout::SetTextAlignment (TextAlignment align)
331 if (alignment == align)
332 return false;
334 alignment = align;
336 return false;
339 bool
340 TextLayout::SetTextWrapping (TextWrapping mode)
342 switch (mode) {
343 case TextWrappingNoWrap:
344 case TextWrappingWrap:
345 break;
346 default:
347 // Silverlight defaults to Wrap for unknown values
348 mode = TextWrappingWrap;
349 break;
352 if (wrapping == mode)
353 return false;
355 wrapping = mode;
357 ResetState ();
359 return true;
362 bool
363 TextLayout::SetLineHeight (double height)
365 if (line_height == height)
366 return false;
368 line_height = height;
370 ResetState ();
372 return true;
375 bool
376 TextLayout::SetMaxHeight (double height)
378 if (max_height == height)
379 return false;
381 max_height = height;
383 ResetState ();
385 return true;
388 bool
389 TextLayout::SetMaxWidth (double width)
391 if (width == 0.0)
392 width = INFINITY;
394 if (max_width == width)
395 return false;
397 if (!is_wrapped && (isinf (width) || width > actual_width)) {
398 // the new max_width won't change layout
399 max_width = width;
400 return false;
403 max_width = width;
405 ResetState ();
407 return true;
410 bool
411 TextLayout::SetTextAttributes (List *attrs)
413 if (attributes) {
414 attributes->Clear (true);
415 delete attributes;
418 attributes = attrs;
420 ResetState ();
422 return true;
425 bool
426 TextLayout::SetText (const char *str, int len)
428 g_free (text);
430 if (str) {
431 length = len == -1 ? strlen (str) : len;
432 text = (char *) g_malloc (length + 1);
433 memcpy (text, str, length);
434 text[length] = '\0';
435 } else {
436 text = NULL;
437 length = 0;
440 count = -1;
442 ResetState ();
444 return true;
447 void
448 TextLayout::ClearCache ()
450 TextLayoutLine *line;
451 TextLayoutRun *run;
453 for (guint i = 0; i < lines->len; i++) {
454 line = (TextLayoutLine *) lines->pdata[i];
456 for (guint j = 0; j < line->runs->len; j++) {
457 run = (TextLayoutRun *) line->runs->pdata[j];
458 run->ClearCache ();
463 struct TextRegion {
464 int start, length;
465 bool select;
468 static void
469 UpdateSelection (GPtrArray *lines, TextRegion *pre, TextRegion *post)
471 TextLayoutGlyphCluster *cluster;
472 TextLayoutLine *line;
473 TextLayoutRun *run;
474 guint i, j;
476 // first update pre-region
477 for (i = 0; i < lines->len; i++) {
478 line = (TextLayoutLine *) lines->pdata[i];
480 if (pre->start >= line->start + line->length) {
481 // pre-region not on this line...
482 continue;
485 for (j = 0; j < line->runs->len; j++) {
486 run = (TextLayoutRun *) line->runs->pdata[j];
488 if (pre->start >= run->start + run->length) {
489 // pre-region not in this run...
490 continue;
493 if (pre->start <= run->start) {
494 if (pre->start + pre->length >= run->start + run->length) {
495 // run is fully contained within the pre-region
496 if (run->clusters->len == 1) {
497 cluster = (TextLayoutGlyphCluster *) run->clusters->pdata[0];
498 cluster->selected = pre->select;
499 } else {
500 run->ClearCache ();
502 } else {
503 run->ClearCache ();
505 } else {
506 run->ClearCache ();
509 if (pre->start + pre->length <= run->start + run->length)
510 break;
514 // now update the post region...
515 for ( ; i < lines->len; i++, j = 0) {
516 line = (TextLayoutLine *) lines->pdata[i];
518 if (post->start >= line->start + line->length) {
519 // pre-region not on this line...
520 continue;
523 for ( ; j < line->runs->len; j++) {
524 run = (TextLayoutRun *) line->runs->pdata[j];
526 if (post->start >= run->start + run->length) {
527 // post-region not in this run...
528 continue;
531 if (post->start <= run->start) {
532 if (post->start + post->length >= run->start + run->length) {
533 // run is fully contained within the pre-region
534 if (run->clusters->len == 1) {
535 cluster = (TextLayoutGlyphCluster *) run->clusters->pdata[0];
536 cluster->selected = post->select;
537 } else {
538 run->ClearCache ();
540 } else {
541 run->ClearCache ();
543 } else {
544 run->ClearCache ();
547 if (post->start + post->length <= run->start + run->length)
548 break;
553 void
554 TextLayout::Select (int start, int length, bool byte_offsets)
556 int new_selection_length;
557 int new_selection_start;
558 int new_selection_end;
559 int selection_end;
560 TextRegion pre, post;
561 const char *inptr;
562 const char *inend;
564 if (!text) {
565 selection_length = 0;
566 selection_start = 0;
567 return;
570 if (!byte_offsets) {
571 inptr = g_utf8_offset_to_pointer (text, start);
572 new_selection_start = inptr - text;
574 inend = g_utf8_offset_to_pointer (inptr, length);
575 new_selection_length = inend - inptr;
576 } else {
577 new_selection_length = length;
578 new_selection_start = start;
581 if (selection_start == new_selection_start &&
582 selection_length == new_selection_length) {
583 // no change in selection...
584 return;
587 #if 1
588 // compute the region between the 2 starts
589 pre.length = abs (new_selection_start - selection_start);
590 pre.start = MIN (selection_start, new_selection_start);
591 pre.select = (new_selection_start < selection_start) && (new_selection_length > 0);
593 // compute the region between the 2 ends
594 new_selection_end = new_selection_start + new_selection_length;
595 selection_end = selection_start + selection_length;
596 post.length = abs (new_selection_end - selection_end);
597 post.start = MIN (selection_end, new_selection_end);
598 post.select = (new_selection_end > selection_end) && (new_selection_length > 0);
600 UpdateSelection (lines, &pre, &post);
602 selection_length = new_selection_length;
603 selection_start = new_selection_start;
604 #else
605 if (selection_length || new_selection_length)
606 ClearCache ();
608 selection_length = new_selection_length;
609 selection_start = new_selection_start;
610 #endif
613 void
614 TextLayout::GetActualExtents (double *width, double *height)
616 *height = actual_height;
617 *width = actual_width;
620 static int
621 unichar_combining_class (gunichar c)
623 #if GLIB_CHECK_VERSION (2,14,0)
624 if (glib_check_version (2,14,0))
625 return g_unichar_combining_class (c);
626 else
627 #endif
629 return 0;
632 enum WordType {
633 WORD_TYPE_UNKNOWN,
634 WORD_TYPE_ALPHABETIC,
635 WORD_TYPE_IDEOGRAPHIC,
636 WORD_TYPE_INSEPARABLE,
637 WORD_TYPE_NUMERIC,
638 WORD_TYPE_HANGUL,
641 struct WordBreakOpportunity {
642 GUnicodeBreakType btype;
643 const char *inptr;
644 double advance;
645 guint32 index;
646 gunichar c;
647 int count;
650 struct LayoutWord {
651 // <internal use>
652 GArray *break_ops; // TextWrappingWrap only
654 WordType type;
656 // <input>
657 double line_advance;
658 TextFont *font;
660 // <input/output>
661 GlyphInfo *prev; // previous glyph; used for kerning
663 // <output>
664 double advance; // the advance-width of the 'word'
665 int length; // length of the word in bytes
666 int count; // length of the word in unichars
669 typedef bool (* LayoutWordCallback) (LayoutWord *word, const char *in, const char *inend, double max_width);
671 static inline bool
672 IsLineBreak (const char *text, size_t left, size_t *n_bytes, size_t *n_chars)
674 const char *inptr = text;
675 gunichar c;
677 if ((c = utf8_getc (&inptr, left)) == (gunichar) -1)
678 return false;
680 if (!UnicharIsLineBreak (c))
681 return false;
683 if (c == '\r' && *inptr == '\n') {
684 *n_bytes = 2;
685 *n_chars = 2;
686 } else {
687 *n_bytes = (size_t) (inptr - text);
688 *n_chars = 1;
691 return true;
694 static inline void
695 layout_word_init (LayoutWord *word, double line_advance, GlyphInfo *prev)
697 word->line_advance = line_advance;
698 word->prev = prev;
702 * layout_lwsp:
703 * @word: #LayoutWord context
704 * @in: input text
705 * @inend: end of input text
707 * Measures a word containing nothing but LWSP.
709 static void
710 layout_lwsp (LayoutWord *word, const char *in, const char *inend)
712 GlyphInfo *prev = word->prev;
713 GUnicodeBreakType btype;
714 const char *inptr = in;
715 const char *start;
716 GlyphInfo *glyph;
717 double advance;
718 gunichar c;
720 d(printf ("layout_lwsp():\n"));
722 word->advance = 0.0;
723 word->count = 0;
725 while (inptr < inend) {
726 start = inptr;
727 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
728 // ignore invalid chars
729 continue;
732 if (UnicharIsLineBreak (c)) {
733 inptr = start;
734 break;
737 btype = g_unichar_break_type (c);
738 if (!BreakSpace (c, btype)) {
739 inptr = start;
740 break;
743 #if DEBUG
744 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
745 if (c < 128 && isprint ((int) c))
746 printf ("\tunichar = %c; btype = %s, ctype = %s\n", (char) c,
747 unicode_break_types[btype], unicode_char_types[g_unichar_type (c)]);
748 else
749 printf ("\tunichar = 0x%.4X; btype = %s, ctype = %s\n", c,
750 unicode_break_types[btype], unicode_char_types[g_unichar_type (c)]);
752 #endif
754 word->count++;
756 // treat tab as a single space
757 if (c == '\t')
758 c = ' ';
760 // ignore glyphs the font doesn't contain...
761 if (!(glyph = word->font->GetGlyphInfo (c)))
762 continue;
764 // calculate total glyph advance
765 advance = glyph->metrics.horiAdvance;
766 if ((prev != NULL) && APPLY_KERNING (c))
767 advance += word->font->Kerning (prev, glyph);
768 else if (glyph->metrics.horiBearingX < 0)
769 advance -= glyph->metrics.horiBearingX;
771 word->line_advance += advance;
772 word->advance += advance;
773 prev = glyph;
776 word->length = (inptr - in);
777 word->prev = prev;
781 * layout_word_nowrap:
782 * @word: #LayoutWord context
783 * @in: input text
784 * @inend = end of input text
785 * @max_width: max allowable width for a line
787 * Calculates the advance of the current word.
789 * Returns: %true if the caller should create a new line for the
790 * remainder of the word or %false otherwise.
792 static bool
793 layout_word_nowrap (LayoutWord *word, const char *in, const char *inend, double max_width)
795 GUnicodeBreakType btype = G_UNICODE_BREAK_UNKNOWN;
796 GlyphInfo *prev = word->prev;
797 const char *inptr = in;
798 const char *start;
799 GlyphInfo *glyph;
800 double advance;
801 gunichar c;
803 // Note: since we don't ever need to wrap, no need to keep track of word-type
804 word->type = WORD_TYPE_UNKNOWN;
805 word->advance = 0.0;
806 word->count = 0;
808 while (inptr < inend) {
809 start = inptr;
810 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
811 // ignore invalid chars
812 continue;
815 if (UnicharIsLineBreak (c)) {
816 inptr = start;
817 break;
820 if (btype == G_UNICODE_BREAK_COMBINING_MARK) {
821 // ignore zero-width spaces
822 if ((btype = g_unichar_break_type (c)) == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
823 btype = G_UNICODE_BREAK_COMBINING_MARK;
824 } else {
825 btype = g_unichar_break_type (c);
828 if (BreakSpace (c, btype)) {
829 inptr = start;
830 break;
833 word->count++;
835 // ignore glyphs the font doesn't contain...
836 if (!(glyph = word->font->GetGlyphInfo (c)))
837 continue;
839 // calculate total glyph advance
840 advance = glyph->metrics.horiAdvance;
841 if ((prev != NULL) && APPLY_KERNING (c))
842 advance += word->font->Kerning (prev, glyph);
843 else if (glyph->metrics.horiBearingX < 0)
844 advance -= glyph->metrics.horiBearingX;
846 word->line_advance += advance;
847 word->advance += advance;
848 prev = glyph;
851 word->length = (inptr - in);
852 word->prev = prev;
854 return false;
857 static WordType
858 word_type (GUnicodeType ctype, GUnicodeBreakType btype)
860 switch (btype) {
861 case G_UNICODE_BREAK_ALPHABETIC:
862 return WORD_TYPE_ALPHABETIC;
863 case G_UNICODE_BREAK_IDEOGRAPHIC:
864 return WORD_TYPE_IDEOGRAPHIC;
865 case G_UNICODE_BREAK_NUMERIC:
866 if (ctype == G_UNICODE_OTHER_PUNCTUATION)
867 return WORD_TYPE_UNKNOWN;
868 return WORD_TYPE_NUMERIC;
869 case G_UNICODE_BREAK_INSEPARABLE:
870 return WORD_TYPE_INSEPARABLE;
871 #if GLIB_CHECK_VERSION (2,10,0)
872 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE:
873 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE:
874 case G_UNICODE_BREAK_HANGUL_L_JAMO:
875 case G_UNICODE_BREAK_HANGUL_V_JAMO:
876 case G_UNICODE_BREAK_HANGUL_T_JAMO:
877 return WORD_TYPE_HANGUL;
878 #endif
879 default:
880 return WORD_TYPE_UNKNOWN;
884 static bool
885 word_type_changed (WordType wtype, gunichar c, GUnicodeType ctype, GUnicodeBreakType btype)
887 WordType type;
889 // compare this character's word-type against the current word-type
890 if ((type = word_type (ctype, btype)) == wtype)
891 return false;
893 if (type == WORD_TYPE_UNKNOWN)
894 return false;
896 // word-types not identical... check if they are compatible
897 switch (wtype) {
898 case WORD_TYPE_ALPHABETIC:
899 return type != WORD_TYPE_NUMERIC;
900 #if 0
901 case WORD_TYPE_IDEOGRAPHIC:
902 // this fixes drt #411 but breaks drt #208. I can't win.
903 return type != WORD_TYPE_ALPHABETIC;
904 #endif
905 default:
906 return true;
912 * layout_word_wrap:
913 * @word: word state
914 * @in: start of word
915 * @inend = end of word
916 * @max_width: max allowable width for a line
918 * Calculates the advance of the current word, breaking if needed.
920 * Returns: %true if the caller should create a new line for the
921 * remainder of the word or %false otherwise.
923 static bool
924 layout_word_wrap (LayoutWord *word, const char *in, const char *inend, double max_width)
926 GUnicodeBreakType btype = G_UNICODE_BREAK_UNKNOWN;
927 bool line_start = word->line_advance == 0.0;
928 GlyphInfo *prev = word->prev;
929 WordBreakOpportunity op;
930 const char *inptr = in;
931 const char *start;
932 GUnicodeType ctype;
933 bool force = false;
934 bool fixed = false;
935 bool wrap = false;
936 GlyphInfo *glyph;
937 #if DEBUG
938 GString *debug;
939 #endif
940 double advance;
941 int glyphs = 0;
942 bool new_glyph;
943 gunichar c;
945 g_array_set_size (word->break_ops, 0);
946 word->type = WORD_TYPE_UNKNOWN;
947 word->advance = 0.0;
948 word->count = 0;
950 d(printf ("layout_word_wrap():\n"));
951 d(debug = g_string_new (""));
953 while (inptr < inend) {
954 start = inptr;
955 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
956 // ignore invalid chars
957 continue;
960 if (UnicharIsLineBreak (c)) {
961 inptr = start;
962 break;
965 // check the previous break-type
966 if (btype == G_UNICODE_BREAK_CLOSE_PUNCTUATION) {
967 // if anything other than an infix separator come after a close-punctuation, then the 'word' is done
968 btype = g_unichar_break_type (c);
969 if (btype != G_UNICODE_BREAK_INFIX_SEPARATOR) {
970 inptr = start;
971 break;
973 } else if (btype == G_UNICODE_BREAK_INFIX_SEPARATOR) {
974 btype = g_unichar_break_type (c);
975 if (word->type == WORD_TYPE_NUMERIC) {
976 // only accept numbers after the infix
977 if (btype != G_UNICODE_BREAK_NUMERIC) {
978 inptr = start;
979 break;
981 } else if (word->type == WORD_TYPE_UNKNOWN) {
982 // only accept alphanumerics after the infix
983 if (btype != G_UNICODE_BREAK_ALPHABETIC && btype != G_UNICODE_BREAK_NUMERIC) {
984 inptr = start;
985 break;
988 fixed = true;
990 } else if (btype == G_UNICODE_BREAK_WORD_JOINER) {
991 btype = g_unichar_break_type (c);
992 fixed = true;
993 } else {
994 btype = g_unichar_break_type (c);
997 if (BreakSpace (c, btype)) {
998 inptr = start;
999 break;
1002 ctype = g_unichar_type (c);
1004 if (word->type == WORD_TYPE_UNKNOWN) {
1005 // record our word-type
1006 word->type = word_type (ctype, btype);
1007 } else if (btype == G_UNICODE_BREAK_OPEN_PUNCTUATION) {
1008 // this is a good place to break
1009 inptr = start;
1010 break;
1011 } else if (word_type_changed (word->type, c, ctype, btype)) {
1012 // changing word-types, don't continue
1013 inptr = start;
1014 break;
1017 d(g_string_append_unichar (debug, c));
1018 word->count++;
1020 // a Combining Class of 0 means start of a new glyph
1021 if (glyphs > 0 && unichar_combining_class (c) != 0) {
1022 // this char gets combined with the previous glyph
1023 new_glyph = false;
1024 } else {
1025 new_glyph = true;
1026 glyphs++;
1029 #if DEBUG
1030 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1031 if (c < 128 && isprint ((int) c))
1032 printf ("\tunichar = %c; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", (char) c,
1033 unicode_break_types[btype], new_glyph ? "true" : "false", unichar_combining_class (c),
1034 unicode_char_types[ctype]);
1035 else
1036 printf ("\tunichar = 0x%.4X; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", c,
1037 unicode_break_types[btype], new_glyph ? "true" : "false", unichar_combining_class (c),
1038 unicode_char_types[ctype]);
1040 #endif
1042 if ((glyph = word->font->GetGlyphInfo (c))) {
1043 // calculate total glyph advance
1044 advance = glyph->metrics.horiAdvance;
1045 if ((prev != NULL) && APPLY_KERNING (c))
1046 advance += word->font->Kerning (prev, glyph);
1047 else if (glyph->metrics.horiBearingX < 0)
1048 advance -= glyph->metrics.horiBearingX;
1050 word->line_advance += advance;
1051 word->advance += advance;
1052 prev = glyph;
1053 } else {
1054 advance = 0.0;
1057 if (new_glyph) {
1058 op.index = glyph ? glyph->index : 0;
1059 op.advance = word->advance;
1060 op.count = word->count;
1061 op.inptr = inptr;
1062 op.btype = btype;
1063 op.c = c;
1064 } else {
1065 g_array_remove_index (word->break_ops, word->break_ops->len - 1);
1066 op.advance += advance;
1067 op.inptr = inptr;
1068 op.count++;
1071 g_array_append_val (word->break_ops, op);
1073 if (!isinf (max_width) && word->line_advance > max_width) {
1074 d(printf ("\tjust exceeded max width (%fpx): %s\n", max_width, debug->str));
1075 wrap = true;
1076 break;
1080 if (!wrap) {
1081 d(g_string_free (debug, true));
1082 word->length = (inptr - in);
1083 word->prev = prev;
1084 return false;
1087 d(printf ("\tcollecting any/all decomposed chars and figuring out the break-type for the next glyph\n"));
1089 // pretend btype is SPACE here in case inptr is at the end of the run
1090 if (inptr == inend)
1091 btype = G_UNICODE_BREAK_SPACE;
1093 // keep going until we reach a new distinct glyph. we also
1094 // need to know the btype of the char after the char that
1095 // exceeded the width limit.
1096 while (inptr < inend) {
1097 start = inptr;
1098 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
1099 // ignore invalid chars
1100 continue;
1103 if (UnicharIsLineBreak (c)) {
1104 btype = G_UNICODE_BREAK_SPACE;
1105 inptr = start;
1106 break;
1109 btype = g_unichar_break_type (c);
1110 if (BreakSpace (c, btype) || unichar_combining_class (c) == 0) {
1111 inptr = start;
1112 break;
1115 d(g_string_append_unichar (debug, c));
1116 word->count++;
1118 if ((glyph = word->font->GetGlyphInfo (c))) {
1119 // calculate total glyph advance
1120 advance = glyph->metrics.horiAdvance;
1121 if ((prev != NULL) && APPLY_KERNING (c))
1122 advance += word->font->Kerning (prev, glyph);
1123 else if (glyph->metrics.horiBearingX < 0)
1124 advance -= glyph->metrics.horiBearingX;
1126 word->line_advance += advance;
1127 word->advance += advance;
1128 prev = glyph;
1129 } else {
1130 advance = 0.0;
1133 g_array_remove_index (word->break_ops, word->break_ops->len - 1);
1134 op.advance += advance;
1135 op.inptr = inptr;
1136 op.count++;
1137 g_array_append_val (word->break_ops, op);
1140 d(printf ("\tok, at this point we have: %s\n", debug->str));
1141 d(printf ("\tnext break-type is %s\n", unicode_break_types[btype]));
1142 d(g_string_free (debug, true));
1144 // at this point, we're going to break the word so we can reset kerning
1145 word->prev = 0;
1147 // we can't break any smaller than a single glyph
1148 if (line_start && glyphs == 1) {
1149 d(printf ("\tsince this is the first glyph on the line, can't break any smaller than that...\n"));
1150 word->length = (inptr - in);
1151 word->prev = prev;
1152 return true;
1155 retry:
1157 // search backwards for the best break point
1158 d(printf ("\tscanning over %d break opportunities...\n", word->break_ops->len));
1159 for (guint i = word->break_ops->len; i > 0; i--) {
1160 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 1);
1162 #if DEBUG
1163 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1164 if (op.c < 128 && isprint ((int) op.c))
1165 printf ("\tunichar = %c; btype = %s; i = %d\n", (char) op.c, unicode_break_types[op.btype], i);
1166 else
1167 printf ("\tunichar = 0x%.4X; btype = %s; i = %d\n", op.c, unicode_break_types[op.btype], i);
1169 #endif
1171 switch (op.btype) {
1172 case G_UNICODE_BREAK_BEFORE_AND_AFTER:
1173 if (i > 1 && i == word->break_ops->len) {
1174 // break after the previous glyph
1175 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1176 word->prev = word->font->GetGlyphInfo (op.c);
1177 word->length = (op.inptr - in);
1178 word->advance = op.advance;
1179 word->count = op.count;
1181 return true;
1182 } else if (i < word->break_ops->len) {
1183 // break after this glyph
1184 word->prev = word->font->GetGlyphInfo (op.c);
1185 word->length = (op.inptr - in);
1186 word->advance = op.advance;
1187 word->count = op.count;
1189 return true;
1191 case G_UNICODE_BREAK_NON_BREAKING_GLUE:
1192 case G_UNICODE_BREAK_WORD_JOINER:
1193 // cannot break before or after this character (unless forced)
1194 if (force && i < word->break_ops->len) {
1195 word->prev = word->font->GetGlyphInfo (op.c);
1196 word->length = (op.inptr - in);
1197 word->advance = op.advance;
1198 word->count = op.count;
1200 return true;
1203 if (i > 1) {
1204 // skip past previous glyph
1205 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1206 i--;
1208 break;
1209 case G_UNICODE_BREAK_INSEPARABLE:
1210 // only restriction is no breaking between inseparables unless we have to
1211 if (line_start && i < word->break_ops->len) {
1212 word->prev = word->font->GetGlyphInfo (op.c);
1213 word->length = (op.inptr - in);
1214 word->advance = op.advance;
1215 word->count = op.count;
1217 return true;
1219 break;
1220 case G_UNICODE_BREAK_BEFORE:
1221 if (i > 1) {
1222 // break after the previous glyph
1223 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1224 word->prev = word->font->GetGlyphInfo (op.c);
1225 word->length = (op.inptr - in);
1226 word->advance = op.advance;
1227 word->count = op.count;
1229 return true;
1231 break;
1232 case G_UNICODE_BREAK_CLOSE_PUNCTUATION:
1233 if (i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_INFIX_SEPARATOR)) {
1234 // we can safely break after this character
1235 word->prev = word->font->GetGlyphInfo (op.c);
1236 word->length = (op.inptr - in);
1237 word->advance = op.advance;
1238 word->count = op.count;
1240 return true;
1243 if (i > 1 && !force) {
1244 // we can never break before a closing punctuation, so skip past prev char
1245 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1246 i--;
1248 break;
1249 case G_UNICODE_BREAK_INFIX_SEPARATOR:
1250 if (i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_NUMERIC)) {
1251 // we can safely break after this character
1252 word->prev = word->font->GetGlyphInfo (op.c);
1253 word->length = (op.inptr - in);
1254 word->advance = op.advance;
1255 word->count = op.count;
1257 return true;
1260 if (i > 1 && !force) {
1261 // we can never break before an infix, skip past prev char
1262 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1263 if (op.btype == G_UNICODE_BREAK_INFIX_SEPARATOR ||
1264 op.btype == G_UNICODE_BREAK_CLOSE_PUNCTUATION) {
1265 // unless previous char is one of these special types...
1266 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 1);
1267 } else {
1268 i--;
1271 break;
1272 case G_UNICODE_BREAK_ALPHABETIC:
1273 // only break if we have no choice...
1274 if ((line_start || fixed || force) && i < word->break_ops->len) {
1275 word->prev = word->font->GetGlyphInfo (op.c);
1276 word->length = (op.inptr - in);
1277 word->advance = op.advance;
1278 word->count = op.count;
1280 return true;
1282 break;
1283 case G_UNICODE_BREAK_IDEOGRAPHIC:
1284 if (i < word->break_ops->len && btype != G_UNICODE_BREAK_NON_STARTER) {
1285 // we can safely break after this character
1286 word->prev = word->font->GetGlyphInfo (op.c);
1287 word->length = (op.inptr - in);
1288 word->advance = op.advance;
1289 word->count = op.count;
1291 return true;
1293 break;
1294 case G_UNICODE_BREAK_NUMERIC:
1295 // only break if we have no choice...
1296 if (line_start && i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_INFIX_SEPARATOR)) {
1297 word->prev = word->font->GetGlyphInfo (op.c);
1298 word->length = (op.inptr - in);
1299 word->advance = op.advance;
1300 word->count = op.count;
1302 return true;
1304 break;
1305 case G_UNICODE_BREAK_OPEN_PUNCTUATION:
1306 case G_UNICODE_BREAK_COMBINING_MARK:
1307 case G_UNICODE_BREAK_CONTINGENT:
1308 case G_UNICODE_BREAK_AMBIGUOUS:
1309 case G_UNICODE_BREAK_QUOTATION:
1310 case G_UNICODE_BREAK_PREFIX:
1311 // do not break after characters with these break-types (unless forced)
1312 if (force && i < word->break_ops->len) {
1313 word->prev = word->font->GetGlyphInfo (op.c);
1314 word->length = (op.inptr - in);
1315 word->advance = op.advance;
1316 word->count = op.count;
1318 return true;
1320 break;
1321 default:
1322 d(printf ("Unhandled Unicode break-type: %s\n", unicode_break_types[op.btype]));
1323 // fall thru to the "default" behavior
1325 #if GLIB_CHECK_VERSION (2,10,0)
1326 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE:
1327 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE:
1328 case G_UNICODE_BREAK_HANGUL_L_JAMO:
1329 case G_UNICODE_BREAK_HANGUL_V_JAMO:
1330 case G_UNICODE_BREAK_HANGUL_T_JAMO:
1331 #endif
1332 case G_UNICODE_BREAK_NON_STARTER:
1333 case G_UNICODE_BREAK_EXCLAMATION:
1334 case G_UNICODE_BREAK_MANDATORY:
1335 case G_UNICODE_BREAK_NEXT_LINE:
1336 case G_UNICODE_BREAK_UNKNOWN:
1337 case G_UNICODE_BREAK_POSTFIX:
1338 case G_UNICODE_BREAK_HYPHEN:
1339 case G_UNICODE_BREAK_AFTER:
1340 if (i < word->break_ops->len) {
1341 // we can safely break after this character
1342 word->prev = word->font->GetGlyphInfo (op.c);
1343 word->length = (op.inptr - in);
1344 word->advance = op.advance;
1345 word->count = op.count;
1347 return true;
1349 break;
1352 btype = op.btype;
1353 c = op.c;
1356 if (line_start && !force) {
1357 d(printf ("\tcouldn't find a good place to break but we must force a break, retrying...\n"));
1358 force = true;
1359 goto retry;
1362 d(printf ("\tcouldn't find a good place to break, defaulting to breaking before the word start\n"));
1364 word->advance = 0.0;
1365 word->length = 0;
1366 word->count = 0;
1368 return true;
1371 #if DEBUG
1372 static const char *wrap_modes[3] = {
1373 "WrapWithOverflow",
1374 "NoWrap",
1375 "Wrap"
1378 static void
1379 print_lines (GPtrArray *lines)
1381 TextLayoutLine *line;
1382 TextLayoutRun *run;
1383 const char *text;
1384 double y = 0.0;
1386 for (guint i = 0; i < lines->len; i++) {
1387 line = (TextLayoutLine *) lines->pdata[i];
1389 printf ("Line (top=%f, height=%f, advance=%f, offset=%d, count=%d):\n", y, line->height, line->advance, line->offset, line->count);
1390 for (guint j = 0; j < line->runs->len; j++) {
1391 run = (TextLayoutRun *) line->runs->pdata[j];
1393 text = line->layout->GetText () + run->start;
1395 printf ("\tRun (advance=%f, start=%d, length=%d): \"", run->advance, run->start, run->length);
1396 for (const char *s = text; s < text + run->length; s++) {
1397 switch (*s) {
1398 case '\r':
1399 fputs ("\\r", stdout);
1400 break;
1401 case '\n':
1402 fputs ("\\n", stdout);
1403 break;
1404 case '\t':
1405 fputs ("\\t", stdout);
1406 break;
1407 case '"':
1408 fputs ("\\\"", stdout);
1409 break;
1410 default:
1411 fputc (*s, stdout);
1412 break;
1415 printf ("\"\n");
1418 y += line->height;
1421 #endif
1423 double
1424 TextLayout::LineHeightOverride ()
1426 if (isnan (line_height))
1427 return base_height;
1428 else
1429 return line_height;
1432 double
1433 TextLayout::DescendOverride ()
1435 if (isnan (line_height))
1436 return base_descent;
1438 if (base_height == 0.0)
1439 return 0.0;
1441 return line_height * (base_descent / base_height);
1444 static LayoutWordCallback layout_word_behavior[] = {
1445 layout_word_wrap,
1446 layout_word_nowrap,
1447 layout_word_wrap
1450 static bool
1451 validate_attrs (List *attributes)
1453 TextLayoutAttributes *attrs;
1455 // if no attributes or first attribute doesn't start at 0, we can't layout any text
1456 if (!(attrs = (TextLayoutAttributes *) attributes->First ()) || attrs->start != 0)
1457 return false;
1459 while (attrs != NULL) {
1460 if (!attrs->Font ()) {
1461 // we can't layout any text if any of the attributes
1462 // weren't able to load their font
1463 return false;
1466 attrs = (TextLayoutAttributes *) attrs->next;
1469 return true;
1472 void
1473 TextLayout::Layout ()
1475 TextLayoutAttributes *attrs, *nattrs;
1476 LayoutWordCallback layout_word;
1477 const char *inptr, *inend;
1478 size_t n_bytes, n_chars;
1479 TextLayoutLine *line;
1480 TextLayoutRun *run;
1481 GlyphInfo *prev;
1482 LayoutWord word;
1483 TextFont *font;
1484 bool linebreak;
1485 int offset = 0;
1486 bool wrapped;
1488 if (!isnan (actual_width))
1489 return;
1491 actual_height = 0.0;
1492 actual_width = 0.0;
1493 is_wrapped = false;
1494 ClearLines ();
1495 count = 0;
1497 if (!text || !validate_attrs (attributes))
1498 return;
1500 d(printf ("TextLayout::Layout(): wrap mode = %s, wrapping to %f pixels\n", wrap_modes[wrapping], max_width));
1502 if (wrapping == TextWrappingWrap)
1503 word.break_ops = g_array_new (false, false, sizeof (WordBreakOpportunity));
1504 else
1505 word.break_ops = NULL;
1507 layout_word = layout_word_behavior[wrapping];
1509 attrs = (TextLayoutAttributes *) attributes->First ();
1510 line = new TextLayoutLine (this, 0, 0);
1511 if (OverrideLineHeight ()) {
1512 line->descend = DescendOverride ();
1513 line->height = LineHeightOverride ();
1516 g_ptr_array_add (lines, line);
1517 inptr = text;
1519 do {
1520 nattrs = (TextLayoutAttributes *) attrs->next;
1521 inend = text + (nattrs ? nattrs->start : length);
1522 run = new TextLayoutRun (line, attrs, inptr - text);
1523 g_ptr_array_add (line->runs, run);
1525 word.font = font = attrs->Font ();
1527 //if (!OverrideLineHeight ()) {
1528 // line->descend = MIN (line->descend, font->Descender ());
1529 // line->height = MAX (line->height, font->Height ());
1532 if (*inptr == '\0') {
1533 if (!OverrideLineHeight ()) {
1534 line->descend = MIN (line->descend, font->Descender ());
1535 line->height = MAX (line->height, font->Height ());
1538 actual_height += line->height;
1539 break;
1542 // layout until attrs change
1543 while (inptr < inend) {
1544 linebreak = false;
1545 wrapped = false;
1546 prev = NULL;
1548 // layout until eoln or until we reach max_width
1549 while (inptr < inend) {
1550 // check for line-breaks
1551 if (IsLineBreak (inptr, inend - inptr, &n_bytes, &n_chars)) {
1552 if (line->length == 0 && !OverrideLineHeight ()) {
1553 line->descend = font->Descender ();
1554 line->height = font->Height ();
1557 line->length += n_bytes;
1558 run->length += n_bytes;
1559 line->count += n_chars;
1560 run->count += n_chars;
1561 offset += n_chars;
1562 inptr += n_bytes;
1563 linebreak = true;
1564 break;
1567 layout_word_init (&word, line->advance, prev);
1569 // lay out the next word
1570 if (layout_word (&word, inptr, inend, max_width)) {
1571 // force a line wrap...
1572 is_wrapped = true;
1573 wrapped = true;
1576 if (word.length > 0) {
1577 // append the word to the run/line
1578 if (!OverrideLineHeight ()) {
1579 line->descend = MIN (line->descend, font->Descender ());
1580 line->height = MAX (line->height, font->Height ());
1583 line->advance += word.advance;
1584 run->advance += word.advance;
1585 line->width = line->advance;
1586 line->length += word.length;
1587 run->length += word.length;
1588 line->count += word.count;
1589 run->count += word.count;
1591 offset += word.count;
1592 inptr += word.length;
1593 prev = word.prev;
1596 if (wrapped)
1597 break;
1599 // now append any trailing lwsp
1600 layout_word_init (&word, line->advance, prev);
1602 layout_lwsp (&word, inptr, inend);
1604 if (word.length > 0) {
1605 if (!OverrideLineHeight ()) {
1606 line->descend = MIN (line->descend, font->Descender ());
1607 line->height = MAX (line->height, font->Height ());
1610 line->advance += word.advance;
1611 run->advance += word.advance;
1612 line->length += word.length;
1613 run->length += word.length;
1614 line->count += word.count;
1615 run->count += word.count;
1617 offset += word.count;
1618 inptr += word.length;
1619 prev = word.prev;
1623 if (linebreak || wrapped || *inptr == '\0') {
1624 // update actual width extents
1625 if (*inptr == '\0') {
1626 // ActualWidth extents only include trailing lwsp on the last line
1627 actual_width = MAX (actual_width, line->advance);
1628 } else {
1629 // not the last line, so don't include trailing lwsp
1630 actual_width = MAX (actual_width, line->width);
1633 // update actual height extents
1634 actual_height += line->height;
1636 if (linebreak || wrapped) {
1637 // more text to layout... which means we'll need a new line
1638 line = new TextLayoutLine (this, inptr - text, offset);
1640 if (!OverrideLineHeight ()) {
1641 if (*inptr == '\0') {
1642 line->descend = font->Descender ();
1643 line->height = font->Height ();
1645 } else {
1646 line->descend = DescendOverride ();
1647 line->height = LineHeightOverride ();
1650 if (linebreak && *inptr == '\0')
1651 actual_height += line->height;
1653 g_ptr_array_add (lines, line);
1654 prev = NULL;
1657 if (inptr < inend) {
1658 // more text to layout with the current attrs...
1659 run = new TextLayoutRun (line, attrs, inptr - text);
1660 g_ptr_array_add (line->runs, run);
1665 attrs = nattrs;
1666 } while (*inptr != '\0');
1668 if (word.break_ops != NULL)
1669 g_array_free (word.break_ops, true);
1671 count = offset;
1673 #if DEBUG
1674 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1675 print_lines (lines);
1676 printf ("actualWidth = %f, actualHeight = %f\n\n", actual_width, actual_height);
1678 #endif
1681 static inline TextLayoutGlyphCluster *
1682 GenerateGlyphCluster (TextFont *font, GlyphInfo **pglyph, const char *text, int start, int length)
1684 TextLayoutGlyphCluster *cluster = new TextLayoutGlyphCluster (start, length);
1685 const char *inend = text + start + length;
1686 const char *inptr = text + start;
1687 GlyphInfo *prev = *pglyph;
1688 double x0, x1, y0;
1689 GlyphInfo *glyph;
1690 int size = 0;
1691 gunichar c;
1693 // set y0 to the baseline
1694 y0 = font->Ascender ();
1695 x0 = 0.0;
1696 x1 = 0.0;
1698 // count how many path data items we'll need to allocate
1699 while (inptr < inend) {
1700 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
1701 continue;
1703 if (UnicharIsLineBreak (c))
1704 break;
1706 // treat tab as a single space
1707 if (c == '\t')
1708 c = ' ';
1710 if (!(glyph = font->GetGlyphInfo (c)))
1711 continue;
1713 if (glyph->path)
1714 size += glyph->path->cairo.num_data + 1;
1717 if (size > 0) {
1718 // generate the cached path for the cluster
1719 cluster->path = moon_path_new (size);
1720 inptr = text + start;
1722 while (inptr < inend) {
1723 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
1724 continue;
1726 if (UnicharIsLineBreak (c))
1727 break;
1729 // treat tab as a single space
1730 if (c == '\t')
1731 c = ' ';
1733 if (!(glyph = font->GetGlyphInfo (c)))
1734 continue;
1736 if (prev != NULL) {
1737 if (APPLY_KERNING (c))
1738 x0 += font->Kerning (prev, glyph);
1739 else if (glyph->metrics.horiBearingX < 0)
1740 x0 += glyph->metrics.horiBearingX;
1743 font->AppendPath (cluster->path, glyph, x0, y0);
1744 x0 += glyph->metrics.horiAdvance;
1745 prev = glyph;
1747 if (!g_unichar_isspace (c))
1748 x1 = x0;
1751 moon_close_path (cluster->path);
1754 cluster->uadvance = x1;
1755 cluster->advance = x0;
1757 *pglyph = prev;
1759 return cluster;
1762 void
1763 TextLayoutRun::GenerateCache ()
1765 int selection_length = line->layout->GetSelectionLength ();
1766 int selection_start = line->layout->GetSelectionStart ();
1767 const char *text = line->layout->GetText ();
1768 const char *inend = text + start + length;
1769 const char *inptr = text + start;
1770 TextFont *font = attrs->Font ();
1771 TextLayoutGlyphCluster *cluster;
1772 const char *selection_end;
1773 GlyphInfo *prev = NULL;
1774 int len;
1776 // cache the glyph cluster leading up to the selection
1777 if (selection_length == 0 || start < selection_start) {
1778 if (selection_length > 0)
1779 len = MIN (selection_start - start, length);
1780 else
1781 len = length;
1783 cluster = GenerateGlyphCluster (font, &prev, text, start, len);
1784 g_ptr_array_add (clusters, cluster);
1785 inptr += len;
1788 // cache the selected glyph cluster
1789 selection_end = text + selection_start + selection_length;
1790 if (inptr < inend && inptr < selection_end) {
1791 len = MIN (inend, selection_end) - inptr;
1792 cluster = GenerateGlyphCluster (font, &prev, text, inptr - text, len);
1793 g_ptr_array_add (clusters, cluster);
1794 cluster->selected = true;
1795 inptr += len;
1798 // cache the glyph cluster following the selection
1799 if (inptr < inend) {
1800 cluster = GenerateGlyphCluster (font, &prev, text, inptr - text, inend - inptr);
1801 g_ptr_array_add (clusters, cluster);
1802 inptr = inend;
1806 void
1807 TextLayoutGlyphCluster::Render (cairo_t *cr, const Point &origin, TextLayoutAttributes *attrs, const char *text, double x, double y, bool uline_full)
1809 TextFont *font = attrs->Font ();
1810 const char *inend, *prev;
1811 GlyphInfo *glyph;
1812 Brush *brush;
1813 gunichar c;
1814 double y0;
1815 Rect area;
1817 if (length == 0 || advance == 0.0)
1818 return;
1820 // y is the baseline, set the origin to the top-left
1821 cairo_translate (cr, x, y - font->Ascender ());
1823 // set y0 to the baseline relative to the translation matrix
1824 y0 = font->Ascender ();
1826 if (selected && (brush = attrs->Background (true))) {
1827 area = Rect (origin.x, origin.y, advance, font->Height ());
1829 // extend the selection background by the width of a SPACE if it includes CRLF
1830 inend = text + start + length;
1831 if ((prev = g_utf8_find_prev_char (text + start, inend)))
1832 c = utf8_getc (&prev, inend - prev);
1833 else
1834 c = (gunichar) -1;
1836 if (UnicharIsLineBreak (c)) {
1837 if ((glyph = font->GetGlyphInfo (' ')))
1838 area.width += glyph->metrics.horiAdvance;
1841 // render the selection background
1842 brush->SetupBrush (cr, area);
1843 cairo_new_path (cr);
1844 cairo_rectangle (cr, area.x, area.y, area.width, area.height);
1845 brush->Fill (cr);
1848 // setup the foreground brush
1849 if (!(brush = attrs->Foreground (selected)))
1850 return;
1852 area = Rect (origin.x, origin.y, advance, font->Height ());
1853 brush->SetupBrush (cr, area);
1854 cairo_new_path (cr);
1856 if (path && path->cairo.data)
1857 cairo_append_path (cr, &path->cairo);
1859 brush->Fill (cr);
1861 if (attrs->IsUnderlined ()) {
1862 double thickness = font->UnderlineThickness ();
1863 double pos = y0 + font->UnderlinePosition ();
1865 cairo_set_line_width (cr, thickness);
1867 cairo_new_path (cr);
1868 Rect underline = Rect (0.0, pos - thickness * 0.5, uline_full ? advance : uadvance, thickness);
1869 underline.Draw (cr);
1871 brush->Fill (cr);
1875 void
1876 TextLayoutRun::Render (cairo_t *cr, const Point &origin, double x, double y, bool is_last_run)
1878 const char *text = line->layout->GetText ();
1879 TextLayoutGlyphCluster *cluster;
1880 double x0 = x;
1882 if (clusters->len == 0)
1883 GenerateCache ();
1885 for (guint i = 0; i < clusters->len; i++) {
1886 cluster = (TextLayoutGlyphCluster *) clusters->pdata[i];
1888 cairo_save (cr);
1889 cluster->Render (cr, origin, attrs, text, x0, y, is_last_run && ((i + 1) < clusters->len));
1890 cairo_restore (cr);
1892 x0 += cluster->advance;
1896 void
1897 TextLayoutLine::Render (cairo_t *cr, const Point &origin, double left, double top)
1899 TextLayoutRun *run;
1900 double x0, y0;
1902 // set y0 to the line's baseline (descend is a negative value)
1903 y0 = top + height + descend;
1904 x0 = left;
1906 for (guint i = 0; i < runs->len; i++) {
1907 run = (TextLayoutRun *) runs->pdata[i];
1908 run->Render (cr, origin, x0, y0, (i + 1) < runs->len);
1909 x0 += run->advance;
1913 static double
1914 GetWidthConstraint (double avail_width, double max_width, double actual_width)
1916 if (isinf (avail_width)) {
1917 // find an upper width constraint
1918 if (isinf (max_width))
1919 return actual_width;
1920 else
1921 return MIN (actual_width, max_width);
1924 return avail_width;
1927 double
1928 TextLayout::HorizontalAlignment (double line_width)
1930 double deltax;
1931 double width;
1933 switch (alignment) {
1934 case TextAlignmentCenter:
1935 width = GetWidthConstraint (avail_width, max_width, actual_width);
1936 if (line_width < width)
1937 deltax = (width - line_width) / 2.0;
1938 else
1939 deltax = 0.0;
1940 break;
1941 case TextAlignmentRight:
1942 width = GetWidthConstraint (avail_width, max_width, actual_width);
1943 if (line_width < width)
1944 deltax = width - line_width;
1945 else
1946 deltax = 0.0;
1947 break;
1948 default:
1949 deltax = 0.0;
1950 break;
1953 return deltax;
1956 void
1957 TextLayout::Render (cairo_t *cr, const Point &origin, const Point &offset)
1959 TextLayoutLine *line;
1960 double x, y;
1962 y = offset.y;
1964 Layout ();
1966 for (guint i = 0; i < lines->len; i++) {
1967 line = (TextLayoutLine *) lines->pdata[i];
1969 x = offset.x + HorizontalAlignment (line->advance);
1970 line->Render (cr, origin, x, y);
1971 y += line->height;
1974 if (moonlight_flags & RUNTIME_INIT_SHOW_TEXTBOXES) {
1975 Rect rect = GetRenderExtents ();
1977 rect.x += offset.x;
1978 rect.y += offset.y;
1980 cairo_set_source_rgba (cr, 0.0, 1.0, 0.0, 1.0);
1981 cairo_set_line_width (cr, 1);
1982 rect.Draw (cr);
1983 cairo_stroke (cr);
1987 Rect
1988 TextLayout::GetRenderExtents ()
1990 return Rect (HorizontalAlignment (actual_width), 0.0, actual_width, actual_height);
1993 #ifdef USE_BINARY_SEARCH
1995 * MID:
1996 * @lo: the low bound
1997 * @hi: the high bound
1999 * Finds the midpoint between positive integer values, @lo and @hi.
2001 * Notes: Typically expressed as '(@lo + @hi) / 2', this is incorrect
2002 * when @lo and @hi are sufficiently large enough that combining them
2003 * would overflow their integer type. To work around this, we use the
2004 * formula, '@lo + ((@hi - @lo) / 2)', thus preventing this problem
2005 * from occuring.
2007 * Returns the midpoint between @lo and @hi (rounded down).
2009 #define MID(lo, hi) (lo + ((hi - lo) >> 1))
2011 TextLayoutLine *
2012 TextLayout::GetLineFromY (const Point &offset, double y, int *index)
2014 register guint lo, hi;
2015 TextLayoutLine *line;
2016 double y0;
2017 guint m;
2019 if (lines->len == 0)
2020 return NULL;
2022 lo = 0, hi = lines->len;
2023 y0 = y - offset.y;
2025 do {
2026 m = MID (lo, hi);
2028 line = (TextLayoutLine *) lines->pdata[m];
2030 if (m > 0 && y0 < line->top) {
2031 // y is on some line above us
2032 hi = m;
2033 } else if (y0 > line->top + line->height) {
2034 // y is on some line below us
2035 lo = m + 1;
2036 m = lo;
2037 } else {
2038 // y is on this line
2039 break;
2042 line = NULL;
2043 } while (lo < hi);
2045 if (line && index)
2046 *index = m;
2048 return line;
2050 #else /* linear search */
2051 TextLayoutLine *
2052 TextLayout::GetLineFromY (const Point &offset, double y, int *index)
2054 TextLayoutLine *line = NULL;
2055 double y0, y1;
2057 //printf ("TextLayout::GetLineFromY (%.2g)\n", y);
2059 y0 = offset.y;
2061 for (guint i = 0; i < lines->len; i++) {
2062 line = (TextLayoutLine *) lines->pdata[i];
2064 // set y1 the top of the next line
2065 y1 = y0 + line->height;
2067 if (y < y1) {
2068 // we found the line that the point is located on
2069 if (index)
2070 *index = (int) i;
2072 return line;
2075 y0 = y1;
2078 return NULL;
2080 #endif
2082 TextLayoutLine *
2083 TextLayout::GetLineFromIndex (int index)
2085 if (index >= (int) lines->len || index < 0)
2086 return NULL;
2088 return (TextLayoutLine *) lines->pdata[index];
2092 TextLayoutLine::GetCursorFromX (const Point &offset, double x)
2094 const char *text, *inend, *ch, *inptr;
2095 TextLayoutRun *run = NULL;
2096 GlyphInfo *prev, *glyph;
2097 TextFont *font;
2098 int cursor;
2099 gunichar c;
2100 double x0;
2101 double m;
2102 guint i;
2104 // adjust x0 for horizontal alignment
2105 x0 = offset.x + layout->HorizontalAlignment (advance);
2107 text = layout->GetText ();
2108 inptr = text + start;
2109 cursor = this->offset;
2110 prev = NULL;
2112 for (i = 0; i < runs->len; i++) {
2113 run = (TextLayoutRun *) runs->pdata[i];
2115 if (x < x0 + run->advance) {
2116 // x is in somewhere inside this run
2117 break;
2120 // x is beyond this run
2121 cursor += run->count;
2122 inptr += run->length;
2123 x0 += run->advance;
2124 run = NULL;
2127 if (run != NULL) {
2128 inptr = text + run->start;
2129 inend = inptr + run->length;
2130 font = run->attrs->Font ();
2132 while (inptr < inend) {
2133 ch = inptr;
2134 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
2135 continue;
2137 if (UnicharIsLineBreak (c)) {
2138 inptr = ch;
2139 break;
2142 cursor++;
2144 // we treat tabs as a single space
2145 if (c == '\t')
2146 c = ' ';
2148 if (!(glyph = font->GetGlyphInfo (c)))
2149 continue;
2151 if ((prev != NULL) && APPLY_KERNING (c))
2152 x0 += font->Kerning (prev, glyph);
2153 else if (glyph->metrics.horiBearingX < 0)
2154 x0 += glyph->metrics.horiBearingX;
2156 // calculate midpoint of the character
2157 m = glyph->metrics.horiAdvance / 2.0;
2159 // if x is <= the midpoint, then the cursor is
2160 // considered to be at the start of this character.
2161 if (x <= x0 + m) {
2162 inptr = ch;
2163 cursor--;
2164 break;
2167 x0 += glyph->metrics.horiAdvance;
2168 prev = glyph;
2170 } else if (i > 0) {
2171 // x is beyond the end of the last run
2172 run = (TextLayoutRun *) runs->pdata[i - 1];
2173 inend = text + run->start + run->length;
2174 inptr = text + run->start;
2176 if ((ch = g_utf8_find_prev_char (inptr, inend)))
2177 c = utf8_getc (&ch, inend - ch);
2178 else
2179 c = (gunichar) -1;
2181 if (c == '\n') {
2182 cursor--;
2183 inend--;
2185 if (inend > inptr && inend[-1] == '\r') {
2186 cursor--;
2187 inend--;
2189 } else if (UnicharIsLineBreak (c)) {
2190 cursor--;
2191 inend--;
2195 return cursor;
2199 TextLayout::GetCursorFromXY (const Point &offset, double x, double y)
2201 TextLayoutLine *line;
2203 //printf ("TextLayout::GetCursorFromXY (%.2g, %.2g)\n", x, y);
2205 if (y < offset.y)
2206 return 0;
2208 if (!(line = GetLineFromY (offset, y)))
2209 return count;
2211 return line->GetCursorFromX (offset, x);
2214 Rect
2215 TextLayout::GetCursor (const Point &offset, int index)
2217 const char *inptr, *inend, *pchar;
2218 double height, x0, y0, y1;
2219 GlyphInfo *prev, *glyph;
2220 TextLayoutLine *line;
2221 TextLayoutRun *run;
2222 TextFont *font;
2223 int cursor = 0;
2224 gunichar c;
2226 //printf ("TextLayout::GetCursor (%d)\n", index);
2228 x0 = offset.x;
2229 y0 = offset.y;
2230 height = 0.0;
2231 y1 = 0.0;
2233 for (guint i = 0; i < lines->len; i++) {
2234 line = (TextLayoutLine *) lines->pdata[i];
2235 inend = text + line->start + line->length;
2237 // adjust x0 for horizontal alignment
2238 x0 = offset.x + HorizontalAlignment (line->advance);
2240 // set y1 to the baseline (descend is a negative value)
2241 y1 = y0 + line->height + line->descend;
2242 height = line->height;
2244 //printf ("\tline: left=%.2f, top=%.2f, baseline=%.2f, start index=%d\n", x0, y0, y1, line->offset);
2246 if (index >= cursor + line->count) {
2247 // maybe the cursor is on the next line...
2248 if ((i + 1) == lines->len) {
2249 // we are on the last line... get the previous unichar
2250 inptr = text + line->start;
2251 inend = inptr + line->length;
2253 if ((pchar = g_utf8_find_prev_char (text + line->start, inend)))
2254 c = utf8_getc (&pchar, inend - pchar);
2255 else
2256 c = (gunichar) -1;
2258 if (UnicharIsLineBreak (c)) {
2259 // cursor is on the next line by itself
2260 x0 = offset.x + HorizontalAlignment (0.0);
2261 y0 += line->height;
2262 } else {
2263 // cursor at the end of the last line
2264 x0 += line->advance;
2267 break;
2270 cursor += line->count;
2271 y0 += line->height;
2272 continue;
2275 // cursor is on this line...
2276 for (guint j = 0; j < line->runs->len; j++) {
2277 run = (TextLayoutRun *) line->runs->pdata[j];
2278 inend = text + run->start + run->length;
2280 if (index >= cursor + run->count) {
2281 // maybe the cursor is in the next run...
2282 cursor += run->count;
2283 x0 += run->advance;
2284 continue;
2287 // cursor is in this run...
2288 font = run->attrs->Font ();
2289 inptr = text + run->start;
2290 prev = NULL;
2292 while (cursor < index) {
2293 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
2294 continue;
2296 cursor++;
2298 // we treat tabs as a single space
2299 if (c == '\t')
2300 c = ' ';
2302 if (!(glyph = font->GetGlyphInfo (c)))
2303 continue;
2305 if ((prev != NULL) && APPLY_KERNING (c))
2306 x0 += font->Kerning (prev, glyph);
2307 else if (glyph->metrics.horiBearingX < 0)
2308 x0 += glyph->metrics.horiBearingX;
2310 x0 += glyph->metrics.horiAdvance;
2311 prev = glyph;
2314 break;
2317 break;
2320 return Rect (x0, y0, 1.0, height);