in plugin/:
[moon.git] / src / layout.cpp
bloba37ae58f401d81a96740b86d69c95f7439eedbe9
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
614 * TextLayout::GetActualExtents:
615 * @width:
616 * @height:
618 * Gets the actual width and height extents required for rendering the
619 * full text.
621 void
622 TextLayout::GetActualExtents (double *width, double *height)
624 *height = actual_height;
625 *width = actual_width;
629 static int
630 unichar_combining_class (gunichar c)
632 #if GLIB_CHECK_VERSION (2,14,0)
633 if (glib_check_version (2,14,0))
634 return g_unichar_combining_class (c);
635 else
636 #endif
638 return 0;
641 enum WordType {
642 WORD_TYPE_UNKNOWN,
643 WORD_TYPE_ALPHABETIC,
644 WORD_TYPE_IDEOGRAPHIC,
645 WORD_TYPE_INSEPARABLE,
646 WORD_TYPE_NUMERIC,
647 WORD_TYPE_HANGUL,
650 struct WordBreakOpportunity {
651 GUnicodeBreakType btype;
652 const char *inptr;
653 double advance;
654 guint32 index;
655 gunichar c;
656 int count;
659 struct LayoutWord {
660 // <internal use>
661 GArray *break_ops; // TextWrappingWrap only
663 WordType type;
665 // <input>
666 double line_advance;
667 TextFont *font;
669 // <input/output>
670 GlyphInfo *prev; // previous glyph; used for kerning
672 // <output>
673 double advance; // the advance-width of the 'word'
674 int length; // length of the word in bytes
675 int count; // length of the word in unichars
678 typedef bool (* LayoutWordCallback) (LayoutWord *word, const char *in, const char *inend, double max_width);
680 static inline bool
681 IsLineBreak (const char *text, size_t left, size_t *n_bytes, size_t *n_chars)
683 const char *inptr = text;
684 gunichar c;
686 if ((c = utf8_getc (&inptr, left)) == (gunichar) -1)
687 return false;
689 if (!UnicharIsLineBreak (c))
690 return false;
692 if (c == '\r' && *inptr == '\n') {
693 *n_bytes = 2;
694 *n_chars = 2;
695 } else {
696 *n_bytes = (size_t) (inptr - text);
697 *n_chars = 1;
700 return true;
703 static inline void
704 layout_word_init (LayoutWord *word, double line_advance, GlyphInfo *prev)
706 word->line_advance = line_advance;
707 word->prev = prev;
711 * layout_lwsp:
712 * @word: #LayoutWord context
713 * @in: input text
714 * @inend: end of input text
716 * Measures a word containing nothing but LWSP.
718 static void
719 layout_lwsp (LayoutWord *word, const char *in, const char *inend)
721 GlyphInfo *prev = word->prev;
722 GUnicodeBreakType btype;
723 const char *inptr = in;
724 const char *start;
725 GlyphInfo *glyph;
726 double advance;
727 gunichar c;
729 d(printf ("layout_lwsp():\n"));
731 word->advance = 0.0;
732 word->count = 0;
734 while (inptr < inend) {
735 start = inptr;
736 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
737 // ignore invalid chars
738 continue;
741 if (UnicharIsLineBreak (c)) {
742 inptr = start;
743 break;
746 btype = g_unichar_break_type (c);
747 if (!BreakSpace (c, btype)) {
748 inptr = start;
749 break;
752 #if DEBUG
753 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
754 if (c < 128 && isprint ((int) c))
755 printf ("\tunichar = %c; btype = %s, ctype = %s\n", (char) c,
756 unicode_break_types[btype], unicode_char_types[g_unichar_type (c)]);
757 else
758 printf ("\tunichar = 0x%.4X; btype = %s, ctype = %s\n", c,
759 unicode_break_types[btype], unicode_char_types[g_unichar_type (c)]);
761 #endif
763 word->count++;
765 // treat tab as a single space
766 if (c == '\t')
767 c = ' ';
769 // ignore glyphs the font doesn't contain...
770 if (!(glyph = word->font->GetGlyphInfo (c)))
771 continue;
773 // calculate total glyph advance
774 advance = glyph->metrics.horiAdvance;
775 if ((prev != NULL) && APPLY_KERNING (c))
776 advance += word->font->Kerning (prev, glyph);
777 else if (glyph->metrics.horiBearingX < 0)
778 advance -= glyph->metrics.horiBearingX;
780 word->line_advance += advance;
781 word->advance += advance;
782 prev = glyph;
785 word->length = (inptr - in);
786 word->prev = prev;
790 * layout_word_nowrap:
791 * @word: #LayoutWord context
792 * @in: input text
793 * @inend = end of input text
794 * @max_width: max allowable width for a line
796 * Calculates the advance of the current word.
798 * Returns: %true if the caller should create a new line for the
799 * remainder of the word or %false otherwise.
801 static bool
802 layout_word_nowrap (LayoutWord *word, const char *in, const char *inend, double max_width)
804 GUnicodeBreakType btype = G_UNICODE_BREAK_UNKNOWN;
805 GlyphInfo *prev = word->prev;
806 const char *inptr = in;
807 const char *start;
808 GlyphInfo *glyph;
809 double advance;
810 gunichar c;
812 // Note: since we don't ever need to wrap, no need to keep track of word-type
813 word->type = WORD_TYPE_UNKNOWN;
814 word->advance = 0.0;
815 word->count = 0;
817 while (inptr < inend) {
818 start = inptr;
819 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
820 // ignore invalid chars
821 continue;
824 if (UnicharIsLineBreak (c)) {
825 inptr = start;
826 break;
829 if (btype == G_UNICODE_BREAK_COMBINING_MARK) {
830 // ignore zero-width spaces
831 if ((btype = g_unichar_break_type (c)) == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
832 btype = G_UNICODE_BREAK_COMBINING_MARK;
833 } else {
834 btype = g_unichar_break_type (c);
837 if (BreakSpace (c, btype)) {
838 inptr = start;
839 break;
842 word->count++;
844 // ignore glyphs the font doesn't contain...
845 if (!(glyph = word->font->GetGlyphInfo (c)))
846 continue;
848 // calculate total glyph advance
849 advance = glyph->metrics.horiAdvance;
850 if ((prev != NULL) && APPLY_KERNING (c))
851 advance += word->font->Kerning (prev, glyph);
852 else if (glyph->metrics.horiBearingX < 0)
853 advance -= glyph->metrics.horiBearingX;
855 word->line_advance += advance;
856 word->advance += advance;
857 prev = glyph;
860 word->length = (inptr - in);
861 word->prev = prev;
863 return false;
866 static WordType
867 word_type (GUnicodeType ctype, GUnicodeBreakType btype)
869 switch (btype) {
870 case G_UNICODE_BREAK_ALPHABETIC:
871 return WORD_TYPE_ALPHABETIC;
872 case G_UNICODE_BREAK_IDEOGRAPHIC:
873 return WORD_TYPE_IDEOGRAPHIC;
874 case G_UNICODE_BREAK_NUMERIC:
875 if (ctype == G_UNICODE_OTHER_PUNCTUATION)
876 return WORD_TYPE_UNKNOWN;
877 return WORD_TYPE_NUMERIC;
878 case G_UNICODE_BREAK_INSEPARABLE:
879 return WORD_TYPE_INSEPARABLE;
880 #if GLIB_CHECK_VERSION (2,10,0)
881 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE:
882 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE:
883 case G_UNICODE_BREAK_HANGUL_L_JAMO:
884 case G_UNICODE_BREAK_HANGUL_V_JAMO:
885 case G_UNICODE_BREAK_HANGUL_T_JAMO:
886 return WORD_TYPE_HANGUL;
887 #endif
888 default:
889 return WORD_TYPE_UNKNOWN;
893 static bool
894 word_type_changed (WordType wtype, gunichar c, GUnicodeType ctype, GUnicodeBreakType btype)
896 WordType type;
898 // compare this character's word-type against the current word-type
899 if ((type = word_type (ctype, btype)) == wtype)
900 return false;
902 if (type == WORD_TYPE_UNKNOWN)
903 return false;
905 // word-types not identical... check if they are compatible
906 switch (wtype) {
907 case WORD_TYPE_ALPHABETIC:
908 return type != WORD_TYPE_NUMERIC;
909 #if 0
910 case WORD_TYPE_IDEOGRAPHIC:
911 // this fixes drt #411 but breaks drt #208. I can't win.
912 return type != WORD_TYPE_ALPHABETIC;
913 #endif
914 default:
915 return true;
921 * layout_word_wrap:
922 * @word: word state
923 * @in: start of word
924 * @inend = end of word
925 * @max_width: max allowable width for a line
927 * Calculates the advance of the current word, breaking if needed.
929 * Returns: %true if the caller should create a new line for the
930 * remainder of the word or %false otherwise.
932 static bool
933 layout_word_wrap (LayoutWord *word, const char *in, const char *inend, double max_width)
935 GUnicodeBreakType btype = G_UNICODE_BREAK_UNKNOWN;
936 bool line_start = word->line_advance == 0.0;
937 GlyphInfo *prev = word->prev;
938 WordBreakOpportunity op;
939 const char *inptr = in;
940 const char *start;
941 GUnicodeType ctype;
942 bool force = false;
943 bool fixed = false;
944 bool wrap = false;
945 GlyphInfo *glyph;
946 #if DEBUG
947 GString *debug;
948 #endif
949 double advance;
950 int glyphs = 0;
951 bool new_glyph;
952 gunichar c;
954 g_array_set_size (word->break_ops, 0);
955 word->type = WORD_TYPE_UNKNOWN;
956 word->advance = 0.0;
957 word->count = 0;
959 d(printf ("layout_word_wrap():\n"));
960 d(debug = g_string_new (""));
962 while (inptr < inend) {
963 start = inptr;
964 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
965 // ignore invalid chars
966 continue;
969 if (UnicharIsLineBreak (c)) {
970 inptr = start;
971 break;
974 // check the previous break-type
975 if (btype == G_UNICODE_BREAK_CLOSE_PUNCTUATION) {
976 // if anything other than an infix separator come after a close-punctuation, then the 'word' is done
977 btype = g_unichar_break_type (c);
978 if (btype != G_UNICODE_BREAK_INFIX_SEPARATOR) {
979 inptr = start;
980 break;
982 } else if (btype == G_UNICODE_BREAK_INFIX_SEPARATOR) {
983 btype = g_unichar_break_type (c);
984 if (word->type == WORD_TYPE_NUMERIC) {
985 // only accept numbers after the infix
986 if (btype != G_UNICODE_BREAK_NUMERIC) {
987 inptr = start;
988 break;
990 } else if (word->type == WORD_TYPE_UNKNOWN) {
991 // only accept alphanumerics after the infix
992 if (btype != G_UNICODE_BREAK_ALPHABETIC && btype != G_UNICODE_BREAK_NUMERIC) {
993 inptr = start;
994 break;
997 fixed = true;
999 } else if (btype == G_UNICODE_BREAK_WORD_JOINER) {
1000 btype = g_unichar_break_type (c);
1001 fixed = true;
1002 } else {
1003 btype = g_unichar_break_type (c);
1006 if (BreakSpace (c, btype)) {
1007 inptr = start;
1008 break;
1011 ctype = g_unichar_type (c);
1013 if (word->type == WORD_TYPE_UNKNOWN) {
1014 // record our word-type
1015 word->type = word_type (ctype, btype);
1016 } else if (btype == G_UNICODE_BREAK_OPEN_PUNCTUATION) {
1017 // this is a good place to break
1018 inptr = start;
1019 break;
1020 } else if (word_type_changed (word->type, c, ctype, btype)) {
1021 // changing word-types, don't continue
1022 inptr = start;
1023 break;
1026 d(g_string_append_unichar (debug, c));
1027 word->count++;
1029 // a Combining Class of 0 means start of a new glyph
1030 if (glyphs > 0 && unichar_combining_class (c) != 0) {
1031 // this char gets combined with the previous glyph
1032 new_glyph = false;
1033 } else {
1034 new_glyph = true;
1035 glyphs++;
1038 #if DEBUG
1039 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1040 if (c < 128 && isprint ((int) c))
1041 printf ("\tunichar = %c; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", (char) c,
1042 unicode_break_types[btype], new_glyph ? "true" : "false", unichar_combining_class (c),
1043 unicode_char_types[ctype]);
1044 else
1045 printf ("\tunichar = 0x%.4X; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", c,
1046 unicode_break_types[btype], new_glyph ? "true" : "false", unichar_combining_class (c),
1047 unicode_char_types[ctype]);
1049 #endif
1051 if ((glyph = word->font->GetGlyphInfo (c))) {
1052 // calculate total glyph advance
1053 advance = glyph->metrics.horiAdvance;
1054 if ((prev != NULL) && APPLY_KERNING (c))
1055 advance += word->font->Kerning (prev, glyph);
1056 else if (glyph->metrics.horiBearingX < 0)
1057 advance -= glyph->metrics.horiBearingX;
1059 word->line_advance += advance;
1060 word->advance += advance;
1061 prev = glyph;
1062 } else {
1063 advance = 0.0;
1066 if (new_glyph) {
1067 op.index = glyph ? glyph->index : 0;
1068 op.advance = word->advance;
1069 op.count = word->count;
1070 op.inptr = inptr;
1071 op.btype = btype;
1072 op.c = c;
1073 } else {
1074 g_array_remove_index (word->break_ops, word->break_ops->len - 1);
1075 op.advance += advance;
1076 op.inptr = inptr;
1077 op.count++;
1080 g_array_append_val (word->break_ops, op);
1082 if (!isinf (max_width) && word->line_advance > max_width) {
1083 d(printf ("\tjust exceeded max width (%fpx): %s\n", max_width, debug->str));
1084 wrap = true;
1085 break;
1089 if (!wrap) {
1090 d(g_string_free (debug, true));
1091 word->length = (inptr - in);
1092 word->prev = prev;
1093 return false;
1096 d(printf ("\tcollecting any/all decomposed chars and figuring out the break-type for the next glyph\n"));
1098 // pretend btype is SPACE here in case inptr is at the end of the run
1099 if (inptr == inend)
1100 btype = G_UNICODE_BREAK_SPACE;
1102 // keep going until we reach a new distinct glyph. we also
1103 // need to know the btype of the char after the char that
1104 // exceeded the width limit.
1105 while (inptr < inend) {
1106 start = inptr;
1107 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
1108 // ignore invalid chars
1109 continue;
1112 if (UnicharIsLineBreak (c)) {
1113 btype = G_UNICODE_BREAK_SPACE;
1114 inptr = start;
1115 break;
1118 btype = g_unichar_break_type (c);
1119 if (BreakSpace (c, btype) || unichar_combining_class (c) == 0) {
1120 inptr = start;
1121 break;
1124 d(g_string_append_unichar (debug, c));
1125 word->count++;
1127 if ((glyph = word->font->GetGlyphInfo (c))) {
1128 // calculate total glyph advance
1129 advance = glyph->metrics.horiAdvance;
1130 if ((prev != NULL) && APPLY_KERNING (c))
1131 advance += word->font->Kerning (prev, glyph);
1132 else if (glyph->metrics.horiBearingX < 0)
1133 advance -= glyph->metrics.horiBearingX;
1135 word->line_advance += advance;
1136 word->advance += advance;
1137 prev = glyph;
1138 } else {
1139 advance = 0.0;
1142 g_array_remove_index (word->break_ops, word->break_ops->len - 1);
1143 op.advance += advance;
1144 op.inptr = inptr;
1145 op.count++;
1146 g_array_append_val (word->break_ops, op);
1149 d(printf ("\tok, at this point we have: %s\n", debug->str));
1150 d(printf ("\tnext break-type is %s\n", unicode_break_types[btype]));
1151 d(g_string_free (debug, true));
1153 // at this point, we're going to break the word so we can reset kerning
1154 word->prev = 0;
1156 // we can't break any smaller than a single glyph
1157 if (line_start && glyphs == 1) {
1158 d(printf ("\tsince this is the first glyph on the line, can't break any smaller than that...\n"));
1159 word->length = (inptr - in);
1160 word->prev = prev;
1161 return true;
1164 retry:
1166 // search backwards for the best break point
1167 d(printf ("\tscanning over %d break opportunities...\n", word->break_ops->len));
1168 for (guint i = word->break_ops->len; i > 0; i--) {
1169 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 1);
1171 #if DEBUG
1172 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1173 if (op.c < 128 && isprint ((int) op.c))
1174 printf ("\tunichar = %c; btype = %s; i = %d\n", (char) op.c, unicode_break_types[op.btype], i);
1175 else
1176 printf ("\tunichar = 0x%.4X; btype = %s; i = %d\n", op.c, unicode_break_types[op.btype], i);
1178 #endif
1180 switch (op.btype) {
1181 case G_UNICODE_BREAK_BEFORE_AND_AFTER:
1182 if (i > 1 && i == word->break_ops->len) {
1183 // break after the previous glyph
1184 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1185 word->prev = word->font->GetGlyphInfo (op.c);
1186 word->length = (op.inptr - in);
1187 word->advance = op.advance;
1188 word->count = op.count;
1190 return true;
1191 } else if (i < word->break_ops->len) {
1192 // break after this glyph
1193 word->prev = word->font->GetGlyphInfo (op.c);
1194 word->length = (op.inptr - in);
1195 word->advance = op.advance;
1196 word->count = op.count;
1198 return true;
1200 case G_UNICODE_BREAK_NON_BREAKING_GLUE:
1201 case G_UNICODE_BREAK_WORD_JOINER:
1202 // cannot break before or after this character (unless forced)
1203 if (force && i < word->break_ops->len) {
1204 word->prev = word->font->GetGlyphInfo (op.c);
1205 word->length = (op.inptr - in);
1206 word->advance = op.advance;
1207 word->count = op.count;
1209 return true;
1212 if (i > 1) {
1213 // skip past previous glyph
1214 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1215 i--;
1217 break;
1218 case G_UNICODE_BREAK_INSEPARABLE:
1219 // only restriction is no breaking between inseparables unless we have to
1220 if (line_start && i < word->break_ops->len) {
1221 word->prev = word->font->GetGlyphInfo (op.c);
1222 word->length = (op.inptr - in);
1223 word->advance = op.advance;
1224 word->count = op.count;
1226 return true;
1228 break;
1229 case G_UNICODE_BREAK_BEFORE:
1230 if (i > 1) {
1231 // break after the previous glyph
1232 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1233 word->prev = word->font->GetGlyphInfo (op.c);
1234 word->length = (op.inptr - in);
1235 word->advance = op.advance;
1236 word->count = op.count;
1238 return true;
1240 break;
1241 case G_UNICODE_BREAK_CLOSE_PUNCTUATION:
1242 if (i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_INFIX_SEPARATOR)) {
1243 // we can safely break after this character
1244 word->prev = word->font->GetGlyphInfo (op.c);
1245 word->length = (op.inptr - in);
1246 word->advance = op.advance;
1247 word->count = op.count;
1249 return true;
1252 if (i > 1 && !force) {
1253 // we can never break before a closing punctuation, so skip past prev char
1254 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1255 i--;
1257 break;
1258 case G_UNICODE_BREAK_INFIX_SEPARATOR:
1259 if (i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_NUMERIC)) {
1260 // we can safely break after this character
1261 word->prev = word->font->GetGlyphInfo (op.c);
1262 word->length = (op.inptr - in);
1263 word->advance = op.advance;
1264 word->count = op.count;
1266 return true;
1269 if (i > 1 && !force) {
1270 // we can never break before an infix, skip past prev char
1271 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1272 if (op.btype == G_UNICODE_BREAK_INFIX_SEPARATOR ||
1273 op.btype == G_UNICODE_BREAK_CLOSE_PUNCTUATION) {
1274 // unless previous char is one of these special types...
1275 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 1);
1276 } else {
1277 i--;
1280 break;
1281 case G_UNICODE_BREAK_ALPHABETIC:
1282 // only break if we have no choice...
1283 if ((line_start || fixed || force) && i < word->break_ops->len) {
1284 word->prev = word->font->GetGlyphInfo (op.c);
1285 word->length = (op.inptr - in);
1286 word->advance = op.advance;
1287 word->count = op.count;
1289 return true;
1291 break;
1292 case G_UNICODE_BREAK_IDEOGRAPHIC:
1293 if (i < word->break_ops->len && btype != G_UNICODE_BREAK_NON_STARTER) {
1294 // we can safely break after this character
1295 word->prev = word->font->GetGlyphInfo (op.c);
1296 word->length = (op.inptr - in);
1297 word->advance = op.advance;
1298 word->count = op.count;
1300 return true;
1302 break;
1303 case G_UNICODE_BREAK_NUMERIC:
1304 // only break if we have no choice...
1305 if (line_start && i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_INFIX_SEPARATOR)) {
1306 word->prev = word->font->GetGlyphInfo (op.c);
1307 word->length = (op.inptr - in);
1308 word->advance = op.advance;
1309 word->count = op.count;
1311 return true;
1313 break;
1314 case G_UNICODE_BREAK_OPEN_PUNCTUATION:
1315 case G_UNICODE_BREAK_COMBINING_MARK:
1316 case G_UNICODE_BREAK_CONTINGENT:
1317 case G_UNICODE_BREAK_AMBIGUOUS:
1318 case G_UNICODE_BREAK_QUOTATION:
1319 case G_UNICODE_BREAK_PREFIX:
1320 // do not break after characters with these break-types (unless forced)
1321 if (force && i < word->break_ops->len) {
1322 word->prev = word->font->GetGlyphInfo (op.c);
1323 word->length = (op.inptr - in);
1324 word->advance = op.advance;
1325 word->count = op.count;
1327 return true;
1329 break;
1330 default:
1331 d(printf ("Unhandled Unicode break-type: %s\n", unicode_break_types[op.btype]));
1332 // fall thru to the "default" behavior
1334 #if GLIB_CHECK_VERSION (2,10,0)
1335 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE:
1336 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE:
1337 case G_UNICODE_BREAK_HANGUL_L_JAMO:
1338 case G_UNICODE_BREAK_HANGUL_V_JAMO:
1339 case G_UNICODE_BREAK_HANGUL_T_JAMO:
1340 #endif
1341 case G_UNICODE_BREAK_NON_STARTER:
1342 case G_UNICODE_BREAK_EXCLAMATION:
1343 case G_UNICODE_BREAK_MANDATORY:
1344 case G_UNICODE_BREAK_NEXT_LINE:
1345 case G_UNICODE_BREAK_UNKNOWN:
1346 case G_UNICODE_BREAK_POSTFIX:
1347 case G_UNICODE_BREAK_HYPHEN:
1348 case G_UNICODE_BREAK_AFTER:
1349 if (i < word->break_ops->len) {
1350 // we can safely break after this character
1351 word->prev = word->font->GetGlyphInfo (op.c);
1352 word->length = (op.inptr - in);
1353 word->advance = op.advance;
1354 word->count = op.count;
1356 return true;
1358 break;
1361 btype = op.btype;
1362 c = op.c;
1365 if (line_start && !force) {
1366 d(printf ("\tcouldn't find a good place to break but we must force a break, retrying...\n"));
1367 force = true;
1368 goto retry;
1371 d(printf ("\tcouldn't find a good place to break, defaulting to breaking before the word start\n"));
1373 word->advance = 0.0;
1374 word->length = 0;
1375 word->count = 0;
1377 return true;
1380 #if DEBUG
1381 static const char *wrap_modes[3] = {
1382 "WrapWithOverflow",
1383 "NoWrap",
1384 "Wrap"
1387 static void
1388 print_lines (GPtrArray *lines)
1390 TextLayoutLine *line;
1391 TextLayoutRun *run;
1392 const char *text;
1393 double y = 0.0;
1395 for (guint i = 0; i < lines->len; i++) {
1396 line = (TextLayoutLine *) lines->pdata[i];
1398 printf ("Line (top=%f, height=%f, advance=%f, offset=%d, count=%d):\n", y, line->height, line->advance, line->offset, line->count);
1399 for (guint j = 0; j < line->runs->len; j++) {
1400 run = (TextLayoutRun *) line->runs->pdata[j];
1402 text = line->layout->GetText () + run->start;
1404 printf ("\tRun (advance=%f, start=%d, length=%d): \"", run->advance, run->start, run->length);
1405 for (const char *s = text; s < text + run->length; s++) {
1406 switch (*s) {
1407 case '\r':
1408 fputs ("\\r", stdout);
1409 break;
1410 case '\n':
1411 fputs ("\\n", stdout);
1412 break;
1413 case '\t':
1414 fputs ("\\t", stdout);
1415 break;
1416 case '"':
1417 fputs ("\\\"", stdout);
1418 break;
1419 default:
1420 fputc (*s, stdout);
1421 break;
1424 printf ("\"\n");
1427 y += line->height;
1430 #endif
1432 double
1433 TextLayout::LineHeightOverride ()
1435 if (isnan (line_height))
1436 return base_height;
1437 else
1438 return line_height;
1441 double
1442 TextLayout::DescendOverride ()
1444 if (isnan (line_height))
1445 return base_descent;
1447 if (base_height == 0.0)
1448 return 0.0;
1450 return line_height * (base_descent / base_height);
1453 static LayoutWordCallback layout_word_behavior[] = {
1454 layout_word_wrap,
1455 layout_word_nowrap,
1456 layout_word_wrap
1459 static bool
1460 validate_attrs (List *attributes)
1462 TextLayoutAttributes *attrs;
1464 // if no attributes or first attribute doesn't start at 0, we can't layout any text
1465 if (!(attrs = (TextLayoutAttributes *) attributes->First ()) || attrs->start != 0)
1466 return false;
1468 while (attrs != NULL) {
1469 if (!attrs->Font ()) {
1470 // we can't layout any text if any of the attributes
1471 // weren't able to load their font
1472 return false;
1475 attrs = (TextLayoutAttributes *) attrs->next;
1478 return true;
1481 void
1482 TextLayout::Layout ()
1484 TextLayoutAttributes *attrs, *nattrs;
1485 LayoutWordCallback layout_word;
1486 const char *inptr, *inend;
1487 size_t n_bytes, n_chars;
1488 TextLayoutLine *line;
1489 TextLayoutRun *run;
1490 GlyphInfo *prev;
1491 LayoutWord word;
1492 TextFont *font;
1493 bool linebreak;
1494 int offset = 0;
1495 bool wrapped;
1497 if (!isnan (actual_width))
1498 return;
1500 actual_height = 0.0;
1501 actual_width = 0.0;
1502 is_wrapped = false;
1503 ClearLines ();
1504 count = 0;
1506 if (!text || !validate_attrs (attributes))
1507 return;
1509 d(printf ("TextLayout::Layout(): wrap mode = %s, wrapping to %f pixels\n", wrap_modes[wrapping], max_width));
1511 if (wrapping == TextWrappingWrap)
1512 word.break_ops = g_array_new (false, false, sizeof (WordBreakOpportunity));
1513 else
1514 word.break_ops = NULL;
1516 layout_word = layout_word_behavior[wrapping];
1518 attrs = (TextLayoutAttributes *) attributes->First ();
1519 line = new TextLayoutLine (this, 0, 0);
1520 if (OverrideLineHeight ()) {
1521 line->descend = DescendOverride ();
1522 line->height = LineHeightOverride ();
1525 g_ptr_array_add (lines, line);
1526 inptr = text;
1528 do {
1529 nattrs = (TextLayoutAttributes *) attrs->next;
1530 inend = text + (nattrs ? nattrs->start : length);
1531 run = new TextLayoutRun (line, attrs, inptr - text);
1532 g_ptr_array_add (line->runs, run);
1534 word.font = font = attrs->Font ();
1536 //if (!OverrideLineHeight ()) {
1537 // line->descend = MIN (line->descend, font->Descender ());
1538 // line->height = MAX (line->height, font->Height ());
1541 if (*inptr == '\0') {
1542 if (!OverrideLineHeight ()) {
1543 line->descend = MIN (line->descend, font->Descender ());
1544 line->height = MAX (line->height, font->Height ());
1547 actual_height += line->height;
1548 break;
1551 // layout until attrs change
1552 while (inptr < inend) {
1553 linebreak = false;
1554 wrapped = false;
1555 prev = NULL;
1557 // layout until eoln or until we reach max_width
1558 while (inptr < inend) {
1559 // check for line-breaks
1560 if (IsLineBreak (inptr, inend - inptr, &n_bytes, &n_chars)) {
1561 if (line->length == 0 && !OverrideLineHeight ()) {
1562 line->descend = font->Descender ();
1563 line->height = font->Height ();
1566 line->length += n_bytes;
1567 run->length += n_bytes;
1568 line->count += n_chars;
1569 run->count += n_chars;
1570 offset += n_chars;
1571 inptr += n_bytes;
1572 linebreak = true;
1573 break;
1576 layout_word_init (&word, line->advance, prev);
1578 // lay out the next word
1579 if (layout_word (&word, inptr, inend, max_width)) {
1580 // force a line wrap...
1581 is_wrapped = true;
1582 wrapped = true;
1585 if (word.length > 0) {
1586 // append the word to the run/line
1587 if (!OverrideLineHeight ()) {
1588 line->descend = MIN (line->descend, font->Descender ());
1589 line->height = MAX (line->height, font->Height ());
1592 line->advance += word.advance;
1593 run->advance += word.advance;
1594 line->width = line->advance;
1595 line->length += word.length;
1596 run->length += word.length;
1597 line->count += word.count;
1598 run->count += word.count;
1600 offset += word.count;
1601 inptr += word.length;
1602 prev = word.prev;
1605 if (wrapped)
1606 break;
1608 // now append any trailing lwsp
1609 layout_word_init (&word, line->advance, prev);
1611 layout_lwsp (&word, inptr, inend);
1613 if (word.length > 0) {
1614 if (!OverrideLineHeight ()) {
1615 line->descend = MIN (line->descend, font->Descender ());
1616 line->height = MAX (line->height, font->Height ());
1619 line->advance += word.advance;
1620 run->advance += word.advance;
1621 line->length += word.length;
1622 run->length += word.length;
1623 line->count += word.count;
1624 run->count += word.count;
1626 offset += word.count;
1627 inptr += word.length;
1628 prev = word.prev;
1632 if (linebreak || wrapped || *inptr == '\0') {
1633 // update actual width extents
1634 if (*inptr == '\0') {
1635 // ActualWidth extents only include trailing lwsp on the last line
1636 actual_width = MAX (actual_width, line->advance);
1637 } else {
1638 // not the last line, so don't include trailing lwsp
1639 actual_width = MAX (actual_width, line->width);
1642 // update actual height extents
1643 actual_height += line->height;
1645 if (linebreak || wrapped) {
1646 // more text to layout... which means we'll need a new line
1647 line = new TextLayoutLine (this, inptr - text, offset);
1649 if (!OverrideLineHeight ()) {
1650 if (*inptr == '\0') {
1651 line->descend = font->Descender ();
1652 line->height = font->Height ();
1654 } else {
1655 line->descend = DescendOverride ();
1656 line->height = LineHeightOverride ();
1659 if (linebreak && *inptr == '\0')
1660 actual_height += line->height;
1662 g_ptr_array_add (lines, line);
1663 prev = NULL;
1666 if (inptr < inend) {
1667 // more text to layout with the current attrs...
1668 run = new TextLayoutRun (line, attrs, inptr - text);
1669 g_ptr_array_add (line->runs, run);
1674 attrs = nattrs;
1675 } while (*inptr != '\0');
1677 if (word.break_ops != NULL)
1678 g_array_free (word.break_ops, true);
1680 count = offset;
1682 #if DEBUG
1683 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1684 print_lines (lines);
1685 printf ("actualWidth = %f, actualHeight = %f\n\n", actual_width, actual_height);
1687 #endif
1690 static inline TextLayoutGlyphCluster *
1691 GenerateGlyphCluster (TextFont *font, GlyphInfo **pglyph, const char *text, int start, int length)
1693 TextLayoutGlyphCluster *cluster = new TextLayoutGlyphCluster (start, length);
1694 const char *inend = text + start + length;
1695 const char *inptr = text + start;
1696 GlyphInfo *prev = *pglyph;
1697 double x0, x1, y0;
1698 GlyphInfo *glyph;
1699 int size = 0;
1700 gunichar c;
1702 // set y0 to the baseline
1703 y0 = font->Ascender ();
1704 x0 = 0.0;
1705 x1 = 0.0;
1707 // count how many path data items we'll need to allocate
1708 while (inptr < inend) {
1709 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
1710 continue;
1712 if (UnicharIsLineBreak (c))
1713 break;
1715 // treat tab as a single space
1716 if (c == '\t')
1717 c = ' ';
1719 if (!(glyph = font->GetGlyphInfo (c)))
1720 continue;
1722 if (glyph->path)
1723 size += glyph->path->cairo.num_data + 1;
1726 if (size > 0) {
1727 // generate the cached path for the cluster
1728 cluster->path = moon_path_new (size);
1729 inptr = text + start;
1731 while (inptr < inend) {
1732 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
1733 continue;
1735 if (UnicharIsLineBreak (c))
1736 break;
1738 // treat tab as a single space
1739 if (c == '\t')
1740 c = ' ';
1742 if (!(glyph = font->GetGlyphInfo (c)))
1743 continue;
1745 if ((prev != NULL) && APPLY_KERNING (c))
1746 x0 += font->Kerning (prev, glyph);
1747 else if (glyph->metrics.horiBearingX < 0)
1748 x0 += glyph->metrics.horiBearingX;
1750 font->AppendPath (cluster->path, glyph, x0, y0);
1751 x0 += glyph->metrics.horiAdvance;
1752 prev = glyph;
1754 if (!g_unichar_isspace (c))
1755 x1 = x0;
1758 moon_close_path (cluster->path);
1761 cluster->uadvance = x1;
1762 cluster->advance = x0;
1764 *pglyph = prev;
1766 return cluster;
1769 void
1770 TextLayoutRun::GenerateCache ()
1772 int selection_length = line->layout->GetSelectionLength ();
1773 int selection_start = line->layout->GetSelectionStart ();
1774 const char *text = line->layout->GetText ();
1775 const char *inend = text + start + length;
1776 const char *inptr = text + start;
1777 TextFont *font = attrs->Font ();
1778 TextLayoutGlyphCluster *cluster;
1779 const char *selection_end;
1780 GlyphInfo *prev = NULL;
1781 int len;
1783 // cache the glyph cluster leading up to the selection
1784 if (selection_length == 0 || start < selection_start) {
1785 if (selection_length > 0)
1786 len = MIN (selection_start - start, length);
1787 else
1788 len = length;
1790 cluster = GenerateGlyphCluster (font, &prev, text, start, len);
1791 g_ptr_array_add (clusters, cluster);
1792 inptr += len;
1795 // cache the selected glyph cluster
1796 selection_end = text + selection_start + selection_length;
1797 if (inptr < inend && inptr < selection_end) {
1798 len = MIN (inend, selection_end) - inptr;
1799 cluster = GenerateGlyphCluster (font, &prev, text, inptr - text, len);
1800 g_ptr_array_add (clusters, cluster);
1801 cluster->selected = true;
1802 inptr += len;
1805 // cache the glyph cluster following the selection
1806 if (inptr < inend) {
1807 cluster = GenerateGlyphCluster (font, &prev, text, inptr - text, inend - inptr);
1808 g_ptr_array_add (clusters, cluster);
1809 inptr = inend;
1813 void
1814 TextLayoutGlyphCluster::Render (cairo_t *cr, const Point &origin, TextLayoutAttributes *attrs, const char *text, double x, double y, bool uline_full)
1816 TextFont *font = attrs->Font ();
1817 const char *inend, *prev;
1818 GlyphInfo *glyph;
1819 Brush *brush;
1820 gunichar c;
1821 double y0;
1822 Rect area;
1824 if (length == 0 || advance == 0.0)
1825 return;
1827 // y is the baseline, set the origin to the top-left
1828 cairo_translate (cr, x, y - font->Ascender ());
1830 // set y0 to the baseline relative to the translation matrix
1831 y0 = font->Ascender ();
1833 if (selected && (brush = attrs->Background (true))) {
1834 area = Rect (origin.x, origin.y, advance, font->Height ());
1836 // extend the selection background by the width of a SPACE if it includes CRLF
1837 inend = text + start + length;
1838 if ((prev = g_utf8_find_prev_char (text + start, inend)))
1839 c = utf8_getc (&prev, inend - prev);
1840 else
1841 c = (gunichar) -1;
1843 if (UnicharIsLineBreak (c)) {
1844 if ((glyph = font->GetGlyphInfo (' ')))
1845 area.width += glyph->metrics.horiAdvance;
1848 // render the selection background
1849 brush->SetupBrush (cr, area);
1850 cairo_new_path (cr);
1851 cairo_rectangle (cr, area.x, area.y, area.width, area.height);
1852 brush->Fill (cr);
1855 // setup the foreground brush
1856 if (!(brush = attrs->Foreground (selected)))
1857 return;
1859 area = Rect (origin.x, origin.y, advance, font->Height ());
1860 brush->SetupBrush (cr, area);
1861 cairo_new_path (cr);
1863 if (path && path->cairo.data)
1864 cairo_append_path (cr, &path->cairo);
1866 brush->Fill (cr);
1868 if (attrs->IsUnderlined ()) {
1869 double thickness = font->UnderlineThickness ();
1870 double pos = y0 + font->UnderlinePosition ();
1872 cairo_set_line_width (cr, thickness);
1874 cairo_new_path (cr);
1875 Rect underline = Rect (0.0, pos - thickness * 0.5, uline_full ? advance : uadvance, thickness);
1876 underline.Draw (cr);
1878 brush->Fill (cr);
1882 void
1883 TextLayoutRun::Render (cairo_t *cr, const Point &origin, double x, double y, bool is_last_run)
1885 const char *text = line->layout->GetText ();
1886 TextLayoutGlyphCluster *cluster;
1887 double x0 = x;
1889 if (clusters->len == 0)
1890 GenerateCache ();
1892 for (guint i = 0; i < clusters->len; i++) {
1893 cluster = (TextLayoutGlyphCluster *) clusters->pdata[i];
1895 cairo_save (cr);
1896 cluster->Render (cr, origin, attrs, text, x0, y, is_last_run && ((i + 1) < clusters->len));
1897 cairo_restore (cr);
1899 x0 += cluster->advance;
1903 void
1904 TextLayoutLine::Render (cairo_t *cr, const Point &origin, double left, double top)
1906 TextLayoutRun *run;
1907 double x0, y0;
1909 // set y0 to the line's baseline (descend is a negative value)
1910 y0 = top + height + descend;
1911 x0 = left;
1913 for (guint i = 0; i < runs->len; i++) {
1914 run = (TextLayoutRun *) runs->pdata[i];
1915 run->Render (cr, origin, x0, y0, (i + 1) < runs->len);
1916 x0 += run->advance;
1920 static double
1921 GetWidthConstraint (double avail_width, double max_width, double actual_width)
1923 if (isinf (avail_width)) {
1924 // find an upper width constraint
1925 if (isinf (max_width))
1926 return actual_width;
1927 else
1928 return max_width;
1931 return avail_width;
1934 double
1935 TextLayout::HorizontalAlignment (double line_width)
1937 double deltax;
1938 double width;
1940 switch (alignment) {
1941 case TextAlignmentCenter:
1942 width = GetWidthConstraint (avail_width, max_width, actual_width);
1943 if (line_width < width)
1944 deltax = (width - line_width) / 2.0;
1945 else
1946 deltax = 0.0;
1947 break;
1948 case TextAlignmentRight:
1949 width = GetWidthConstraint (avail_width, max_width, actual_width);
1950 if (line_width < width)
1951 deltax = width - line_width;
1952 else
1953 deltax = 0.0;
1954 break;
1955 default:
1956 deltax = 0.0;
1957 break;
1960 return deltax;
1963 void
1964 TextLayout::Render (cairo_t *cr, const Point &origin, const Point &offset)
1966 TextLayoutLine *line;
1967 double x, y;
1969 y = offset.y;
1971 Layout ();
1973 for (guint i = 0; i < lines->len; i++) {
1974 line = (TextLayoutLine *) lines->pdata[i];
1976 x = offset.x + HorizontalAlignment (line->advance);
1977 line->Render (cr, origin, x, y);
1978 y += line->height;
1982 #ifdef USE_BINARY_SEARCH
1984 * MID:
1985 * @lo: the low bound
1986 * @hi: the high bound
1988 * Finds the midpoint between positive integer values, @lo and @hi.
1990 * Notes: Typically expressed as '(@lo + @hi) / 2', this is incorrect
1991 * when @lo and @hi are sufficiently large enough that combining them
1992 * would overflow their integer type. To work around this, we use the
1993 * formula, '@lo + ((@hi - @lo) / 2)', thus preventing this problem
1994 * from occuring.
1996 * Returns the midpoint between @lo and @hi (rounded down).
1998 #define MID(lo, hi) (lo + ((hi - lo) >> 1))
2000 TextLayoutLine *
2001 TextLayout::GetLineFromY (const Point &offset, double y, int *index)
2003 register guint lo, hi;
2004 TextLayoutLine *line;
2005 double y0;
2006 guint m;
2008 if (lines->len == 0)
2009 return NULL;
2011 lo = 0, hi = lines->len;
2012 y0 = y - offset.y;
2014 do {
2015 m = MID (lo, hi);
2017 line = (TextLayoutLine *) lines->pdata[m];
2019 if (m > 0 && y0 < line->top) {
2020 // y is on some line above us
2021 hi = m;
2022 } else if (y0 > line->top + line->height) {
2023 // y is on some line below us
2024 lo = m + 1;
2025 m = lo;
2026 } else {
2027 // y is on this line
2028 break;
2031 line = NULL;
2032 } while (lo < hi);
2034 if (line && index)
2035 *index = m;
2037 return line;
2039 #else /* linear search */
2040 TextLayoutLine *
2041 TextLayout::GetLineFromY (const Point &offset, double y, int *index)
2043 TextLayoutLine *line = NULL;
2044 double y0, y1;
2046 //printf ("TextLayout::GetLineFromY (%.2g)\n", y);
2048 y0 = offset.y;
2050 for (guint i = 0; i < lines->len; i++) {
2051 line = (TextLayoutLine *) lines->pdata[i];
2053 // set y1 the top of the next line
2054 y1 = y0 + line->height;
2056 if (y < y1) {
2057 // we found the line that the point is located on
2058 if (index)
2059 *index = (int) i;
2061 return line;
2064 y0 = y1;
2067 return NULL;
2069 #endif
2071 TextLayoutLine *
2072 TextLayout::GetLineFromIndex (int index)
2074 if (index >= (int) lines->len || index < 0)
2075 return NULL;
2077 return (TextLayoutLine *) lines->pdata[index];
2081 TextLayoutLine::GetCursorFromX (const Point &offset, double x)
2083 const char *text, *inend, *ch, *inptr;
2084 TextLayoutRun *run = NULL;
2085 GlyphInfo *prev, *glyph;
2086 TextFont *font;
2087 int cursor;
2088 gunichar c;
2089 double x0;
2090 double m;
2091 guint i;
2093 // adjust x0 for horizontal alignment
2094 x0 = offset.x + layout->HorizontalAlignment (advance);
2096 text = layout->GetText ();
2097 inptr = text + start;
2098 cursor = this->offset;
2099 prev = NULL;
2101 for (i = 0; i < runs->len; i++) {
2102 run = (TextLayoutRun *) runs->pdata[i];
2104 if (x < x0 + run->advance) {
2105 // x is in somewhere inside this run
2106 break;
2109 // x is beyond this run
2110 cursor += run->count;
2111 inptr += run->length;
2112 x0 += run->advance;
2113 run = NULL;
2116 if (run != NULL) {
2117 inptr = text + run->start;
2118 inend = inptr + run->length;
2119 font = run->attrs->Font ();
2121 while (inptr < inend) {
2122 ch = inptr;
2123 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
2124 continue;
2126 if (UnicharIsLineBreak (c)) {
2127 inptr = ch;
2128 break;
2131 cursor++;
2133 // we treat tabs as a single space
2134 if (c == '\t')
2135 c = ' ';
2137 if (!(glyph = font->GetGlyphInfo (c)))
2138 continue;
2140 if ((prev != NULL) && APPLY_KERNING (c))
2141 x0 += font->Kerning (prev, glyph);
2142 else if (glyph->metrics.horiBearingX < 0)
2143 x0 += glyph->metrics.horiBearingX;
2145 // calculate midpoint of the character
2146 m = glyph->metrics.horiAdvance / 2.0;
2148 // if x is <= the midpoint, then the cursor is
2149 // considered to be at the start of this character.
2150 if (x <= x0 + m) {
2151 inptr = ch;
2152 cursor--;
2153 break;
2156 x0 += glyph->metrics.horiAdvance;
2157 prev = glyph;
2159 } else if (i > 0) {
2160 // x is beyond the end of the last run
2161 run = (TextLayoutRun *) runs->pdata[i - 1];
2162 inend = text + run->start + run->length;
2163 inptr = text + run->start;
2165 if ((ch = g_utf8_find_prev_char (inptr, inend)))
2166 c = utf8_getc (&ch, inend - ch);
2167 else
2168 c = (gunichar) -1;
2170 if (c == '\n') {
2171 cursor--;
2172 inend--;
2174 if (inend > inptr && inend[-1] == '\r') {
2175 cursor--;
2176 inend--;
2178 } else if (UnicharIsLineBreak (c)) {
2179 cursor--;
2180 inend--;
2184 return cursor;
2188 TextLayout::GetCursorFromXY (const Point &offset, double x, double y)
2190 TextLayoutLine *line;
2192 //printf ("TextLayout::GetCursorFromXY (%.2g, %.2g)\n", x, y);
2194 if (y < offset.y)
2195 return 0;
2197 if (!(line = GetLineFromY (offset, y)))
2198 return count;
2200 return line->GetCursorFromX (offset, x);
2203 Rect
2204 TextLayout::GetCursor (const Point &offset, int index)
2206 const char *inptr, *inend, *pchar;
2207 double height, x0, y0, y1;
2208 GlyphInfo *prev, *glyph;
2209 TextLayoutLine *line;
2210 TextLayoutRun *run;
2211 TextFont *font;
2212 int cursor = 0;
2213 gunichar c;
2215 //printf ("TextLayout::GetCursor (%d)\n", index);
2217 x0 = offset.x;
2218 y0 = offset.y;
2219 height = 0.0;
2220 y1 = 0.0;
2222 for (guint i = 0; i < lines->len; i++) {
2223 line = (TextLayoutLine *) lines->pdata[i];
2224 inend = text + line->start + line->length;
2226 // adjust x0 for horizontal alignment
2227 x0 = offset.x + HorizontalAlignment (line->advance);
2229 // set y1 to the baseline (descend is a negative value)
2230 y1 = y0 + line->height + line->descend;
2231 height = line->height;
2233 //printf ("\tline: left=%.2f, top=%.2f, baseline=%.2f, start index=%d\n", x0, y0, y1, line->offset);
2235 if (index >= cursor + line->count) {
2236 // maybe the cursor is on the next line...
2237 if ((i + 1) == lines->len) {
2238 // we are on the last line... get the previous unichar
2239 inptr = text + line->start;
2240 inend = inptr + line->length;
2242 if ((pchar = g_utf8_find_prev_char (text + line->start, inend)))
2243 c = utf8_getc (&pchar, inend - pchar);
2244 else
2245 c = (gunichar) -1;
2247 if (UnicharIsLineBreak (c)) {
2248 // cursor is on the next line by itself
2249 x0 = offset.x + HorizontalAlignment (0.0);
2250 y0 += line->height;
2251 } else {
2252 // cursor at the end of the last line
2253 x0 += line->advance;
2256 break;
2259 cursor += line->count;
2260 y0 += line->height;
2261 continue;
2264 // cursor is on this line...
2265 for (guint j = 0; j < line->runs->len; j++) {
2266 run = (TextLayoutRun *) line->runs->pdata[j];
2267 inend = text + run->start + run->length;
2269 if (index >= cursor + run->count) {
2270 // maybe the cursor is in the next run...
2271 cursor += run->count;
2272 x0 += run->advance;
2273 continue;
2276 // cursor is in this run...
2277 font = run->attrs->Font ();
2278 inptr = text + run->start;
2279 prev = NULL;
2281 while (cursor < index) {
2282 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
2283 continue;
2285 cursor++;
2287 // we treat tabs as a single space
2288 if (c == '\t')
2289 c = ' ';
2291 if (!(glyph = font->GetGlyphInfo (c)))
2292 continue;
2294 if ((prev != NULL) && APPLY_KERNING (c))
2295 x0 += font->Kerning (prev, glyph);
2296 else if (glyph->metrics.horiBearingX < 0)
2297 x0 += glyph->metrics.horiBearingX;
2299 x0 += glyph->metrics.horiAdvance;
2300 prev = glyph;
2303 break;
2306 break;
2309 return Rect (x0, y0, 1.0, height);