2009-06-05 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / layout.cpp
blobf872df92ebf19c6817b09eaf95fa0425fcaccd64
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 #ifdef HAVE_CONFIG_H
14 #include <config.h>
15 #endif
17 #include <ctype.h>
18 #include <math.h>
20 #include "moon-path.h"
21 #include "layout.h"
22 #include "debug.h"
25 #if DEBUG
26 #define d(x) if (debug_flags & RUNTIME_DEBUG_LAYOUT) x
27 #else
28 #define d(x)
29 #endif
31 #if DEBUG
32 static const char *unicode_break_types[] = {
33 "G_UNICODE_BREAK_MANDATORY",
34 "G_UNICODE_BREAK_CARRIAGE_RETURN",
35 "G_UNICODE_BREAK_LINE_FEED",
36 "G_UNICODE_BREAK_COMBINING_MARK",
37 "G_UNICODE_BREAK_SURROGATE",
38 "G_UNICODE_BREAK_ZERO_WIDTH_SPACE",
39 "G_UNICODE_BREAK_INSEPARABLE",
40 "G_UNICODE_BREAK_NON_BREAKING_GLUE",
41 "G_UNICODE_BREAK_CONTINGENT",
42 "G_UNICODE_BREAK_SPACE",
43 "G_UNICODE_BREAK_AFTER",
44 "G_UNICODE_BREAK_BEFORE",
45 "G_UNICODE_BREAK_BEFORE_AND_AFTER",
46 "G_UNICODE_BREAK_HYPHEN",
47 "G_UNICODE_BREAK_NON_STARTER",
48 "G_UNICODE_BREAK_OPEN_PUNCTUATION",
49 "G_UNICODE_BREAK_CLOSE_PUNCTUATION",
50 "G_UNICODE_BREAK_QUOTATION",
51 "G_UNICODE_BREAK_EXCLAMATION",
52 "G_UNICODE_BREAK_IDEOGRAPHIC",
53 "G_UNICODE_BREAK_NUMERIC",
54 "G_UNICODE_BREAK_INFIX_SEPARATOR",
55 "G_UNICODE_BREAK_SYMBOL",
56 "G_UNICODE_BREAK_ALPHABETIC",
57 "G_UNICODE_BREAK_PREFIX",
58 "G_UNICODE_BREAK_POSTFIX",
59 "G_UNICODE_BREAK_COMPLEX_CONTEXT",
60 "G_UNICODE_BREAK_AMBIGUOUS",
61 "G_UNICODE_BREAK_UNKNOWN",
62 "G_UNICODE_BREAK_NEXT_LINE",
63 "G_UNICODE_BREAK_WORD_JOINER",
64 "G_UNICODE_BREAK_HANGUL_L_JAMO",
65 "G_UNICODE_BREAK_HANGUL_V_JAMO",
66 "G_UNICODE_BREAK_HANGUL_T_JAMO",
67 "G_UNICODE_BREAK_HANGUL_LV_SYLLABLE",
68 "G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE"
71 static const char *unicode_char_types[] = {
72 "G_UNICODE_CONTROL",
73 "G_UNICODE_FORMAT",
74 "G_UNICODE_UNASSIGNED",
75 "G_UNICODE_PRIVATE_USE",
76 "G_UNICODE_SURROGATE",
77 "G_UNICODE_LOWERCASE_LETTER",
78 "G_UNICODE_MODIFIER_LETTER",
79 "G_UNICODE_OTHER_LETTER",
80 "G_UNICODE_TITLECASE_LETTER",
81 "G_UNICODE_UPPERCASE_LETTER",
82 "G_UNICODE_COMBINING_MARK",
83 "G_UNICODE_ENCLOSING_MARK",
84 "G_UNICODE_NON_SPACING_MARK",
85 "G_UNICODE_DECIMAL_NUMBER",
86 "G_UNICODE_LETTER_NUMBER",
87 "G_UNICODE_OTHER_NUMBER",
88 "G_UNICODE_CONNECT_PUNCTUATION",
89 "G_UNICODE_DASH_PUNCTUATION",
90 "G_UNICODE_CLOSE_PUNCTUATION",
91 "G_UNICODE_FINAL_PUNCTUATION",
92 "G_UNICODE_INITIAL_PUNCTUATION",
93 "G_UNICODE_OTHER_PUNCTUATION",
94 "G_UNICODE_OPEN_PUNCTUATION",
95 "G_UNICODE_CURRENCY_SYMBOL",
96 "G_UNICODE_MODIFIER_SYMBOL",
97 "G_UNICODE_MATH_SYMBOL",
98 "G_UNICODE_OTHER_SYMBOL",
99 "G_UNICODE_LINE_SEPARATOR",
100 "G_UNICODE_PARAGRAPH_SEPARATOR",
101 "G_UNICODE_SPACE_SEPARATOR"
103 #endif
105 #define UnicharIsLineBreak(c) ((c) == '\r' || (c) == '\n' || (c) == 0x2028)
107 #define BreakSpace(c, btype) (c == '\t' || btype == G_UNICODE_BREAK_SPACE || btype == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
111 * Silverlight does not apply any kerning on a DOT, so we exclude them
112 * U+002E FULL STOP
113 * U+06D4 ARABIC FULL STOP
114 * U+3002 IDEOGRAPHIC FULL STOP
115 * Note: this is different than using the "sliding dot" algorithm from
116 * http://www.freetype.org/freetype2/docs/glyphs/glyphs-4.html
118 #define APPLY_KERNING(uc) ((uc != 0x002E) && (uc != 0x06D4) && (uc != 3002))
121 static inline gunichar
122 utf8_getc (const char **in, size_t inlen)
124 register const unsigned char *inptr = (const unsigned char *) *in;
125 const unsigned char *inend = inptr + inlen;
126 register unsigned char c, r;
127 register gunichar m, u = 0;
129 if (inlen == 0)
130 return 0;
132 r = *inptr++;
133 if (r < 0x80) {
134 *in = (const char *) inptr;
135 u = r;
136 } else if (r < 0xfe) { /* valid start char? */
137 u = r;
138 m = 0x7f80; /* used to mask out the length bits */
139 do {
140 if (inptr >= inend) {
141 *in = (const char *) inend;
142 return 0;
145 c = *inptr++;
146 if ((c & 0xc0) != 0x80)
147 goto error;
149 u = (u << 6) | (c & 0x3f);
150 r <<= 1;
151 m <<= 5;
152 } while (r & 0x40);
154 *in = (const char *) inptr;
156 u &= ~m;
157 } else {
158 error:
159 u = (gunichar) -1;
160 *in = (*in) + 1;
163 return u;
168 // TextLayoutGlyphCluster
171 TextLayoutGlyphCluster::TextLayoutGlyphCluster (int _start, int _length)
173 length = _length;
174 start = _start;
175 selected = false;
176 advance = 0.0;
177 path = NULL;
180 TextLayoutGlyphCluster::~TextLayoutGlyphCluster ()
182 if (path)
183 moon_path_destroy (path);
188 // TextLayoutRun
191 TextLayoutRun::TextLayoutRun (TextLayoutLine *_line, TextLayoutAttributes *_attrs, int _start)
193 clusters = g_ptr_array_new ();
194 attrs = _attrs;
195 start = _start;
196 line = _line;
197 advance = 0.0;
198 length = 0;
199 count = 0;
202 TextLayoutRun::~TextLayoutRun ()
204 for (guint i = 0; i < clusters->len; i++)
205 delete (TextLayoutGlyphCluster *) clusters->pdata[i];
207 g_ptr_array_free (clusters, true);
210 void
211 TextLayoutRun::ClearCache ()
213 for (guint i = 0; i < clusters->len; i++)
214 delete (TextLayoutGlyphCluster *) clusters->pdata[i];
216 g_ptr_array_set_size (clusters, 0);
221 // TextLayoutLine
224 TextLayoutLine::TextLayoutLine (TextLayout *_layout, int _start, int _offset)
226 runs = g_ptr_array_new ();
227 layout = _layout;
228 offset = _offset;
229 start = _start;
230 advance = 0.0;
231 descend = 0.0;
232 height = 0.0;
233 width = 0.0;
234 length = 0;
235 count = 0;
238 TextLayoutLine::~TextLayoutLine ()
240 for (guint i = 0; i < runs->len; i++)
241 delete (TextLayoutRun *) runs->pdata[i];
243 g_ptr_array_free (runs, true);
249 // TextLayout
252 TextLayout::TextLayout ()
254 // Note: TextBlock and TextBox assume their default values match these
255 strategy = LineStackingStrategyMaxHeight;
256 alignment = TextAlignmentLeft;
257 wrapping = TextWrappingNoWrap;
258 selection_length = 0;
259 selection_start = 0;
260 avail_width = INFINITY;
261 max_height = INFINITY;
262 max_width = INFINITY;
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 bool
304 TextLayout::SetLineStackingStrategy (LineStackingStrategy mode)
306 if (strategy == mode)
307 return false;
309 strategy = mode;
311 ResetState ();
313 return true;
316 bool
317 TextLayout::SetTextAlignment (TextAlignment align)
319 if (alignment == align)
320 return false;
322 alignment = align;
324 return false;
327 bool
328 TextLayout::SetTextWrapping (TextWrapping mode)
330 switch (mode) {
331 case TextWrappingNoWrap:
332 case TextWrappingWrap:
333 break;
334 default:
335 // Silverlight defaults to Wrap for unknown values
336 mode = TextWrappingWrap;
337 break;
340 if (wrapping == mode)
341 return false;
343 wrapping = mode;
345 ResetState ();
347 return true;
350 bool
351 TextLayout::SetLineHeight (double height)
353 if (line_height == height)
354 return false;
356 line_height = height;
358 ResetState ();
360 return true;
363 bool
364 TextLayout::SetMaxHeight (double height)
366 if (max_height == height)
367 return false;
369 max_height = height;
371 ResetState ();
373 return true;
376 bool
377 TextLayout::SetMaxWidth (double width)
379 if (max_width == width)
380 return false;
382 if (!is_wrapped && (isinf (width) || width > actual_width)) {
383 // the new max_width won't change layout
384 max_width = width;
385 return false;
388 max_width = width;
390 ResetState ();
392 return true;
395 bool
396 TextLayout::SetTextAttributes (List *attrs)
398 if (attributes) {
399 attributes->Clear (true);
400 delete attributes;
403 attributes = attrs;
405 ResetState ();
407 return true;
410 bool
411 TextLayout::SetText (const char *str, int len)
413 g_free (text);
415 if (str) {
416 length = len == -1 ? strlen (str) : len;
417 text = (char *) g_malloc (length + 1);
418 memcpy (text, str, length);
419 text[length] = '\0';
420 } else {
421 text = NULL;
422 length = 0;
425 count = -1;
427 ResetState ();
429 return true;
432 void
433 TextLayout::ClearCache ()
435 TextLayoutLine *line;
436 TextLayoutRun *run;
438 for (guint i = 0; i < lines->len; i++) {
439 line = (TextLayoutLine *) lines->pdata[i];
441 for (guint j = 0; j < line->runs->len; j++) {
442 run = (TextLayoutRun *) line->runs->pdata[j];
443 run->ClearCache ();
448 struct TextRegion {
449 int start, length;
450 bool select;
453 static void
454 UpdateSelection (GPtrArray *lines, TextRegion *pre, TextRegion *post)
456 TextLayoutGlyphCluster *cluster;
457 TextLayoutLine *line;
458 TextLayoutRun *run;
459 guint i, j;
461 // first update pre-region
462 for (i = 0; i < lines->len; i++) {
463 line = (TextLayoutLine *) lines->pdata[i];
465 if (pre->start >= line->start + line->length) {
466 // pre-region not on this line...
467 continue;
470 for (j = 0; j < line->runs->len; j++) {
471 run = (TextLayoutRun *) line->runs->pdata[j];
473 if (pre->start >= run->start + run->length) {
474 // pre-region not in this run...
475 continue;
478 if (pre->start <= run->start) {
479 if (pre->start + pre->length >= run->start + run->length) {
480 // run is fully contained within the pre-region
481 if (run->clusters->len == 1) {
482 cluster = (TextLayoutGlyphCluster *) run->clusters->pdata[0];
483 cluster->selected = pre->select;
484 } else {
485 run->ClearCache ();
487 } else {
488 run->ClearCache ();
490 } else {
491 run->ClearCache ();
494 if (pre->start + pre->length <= run->start + run->length)
495 break;
499 // now update the post region...
500 for ( ; i < lines->len; i++, j = 0) {
501 line = (TextLayoutLine *) lines->pdata[i];
503 if (post->start >= line->start + line->length) {
504 // pre-region not on this line...
505 continue;
508 for ( ; j < line->runs->len; j++) {
509 run = (TextLayoutRun *) line->runs->pdata[j];
511 if (post->start >= run->start + run->length) {
512 // post-region not in this run...
513 continue;
516 if (post->start <= run->start) {
517 if (post->start + post->length >= run->start + run->length) {
518 // run is fully contained within the pre-region
519 if (run->clusters->len == 1) {
520 cluster = (TextLayoutGlyphCluster *) run->clusters->pdata[0];
521 cluster->selected = post->select;
522 } else {
523 run->ClearCache ();
525 } else {
526 run->ClearCache ();
528 } else {
529 run->ClearCache ();
532 if (post->start + post->length <= run->start + run->length)
533 break;
538 void
539 TextLayout::Select (int start, int length, bool byte_offsets)
541 int new_selection_length;
542 int new_selection_start;
543 int new_selection_end;
544 int selection_end;
545 TextRegion pre, post;
546 const char *inptr;
547 const char *inend;
549 if (!text) {
550 selection_length = 0;
551 selection_start = 0;
552 return;
555 if (!byte_offsets) {
556 inptr = g_utf8_offset_to_pointer (text, start);
557 new_selection_start = inptr - text;
559 inend = g_utf8_offset_to_pointer (inptr, length);
560 new_selection_length = inend - inptr;
561 } else {
562 new_selection_length = length;
563 new_selection_start = start;
566 if (selection_start == new_selection_start &&
567 selection_length == new_selection_length) {
568 // no change in selection...
569 return;
572 #if 1
573 // compute the region between the 2 starts
574 pre.length = abs (new_selection_start - selection_start);
575 pre.start = MIN (selection_start, new_selection_start);
576 pre.select = (new_selection_start < selection_start) && (new_selection_length > 0);
578 // compute the region between the 2 ends
579 new_selection_end = new_selection_start + new_selection_length;
580 selection_end = selection_start + selection_length;
581 post.length = abs (new_selection_end - selection_end);
582 post.start = MIN (selection_end, new_selection_end);
583 post.select = (new_selection_end > selection_end) && (new_selection_length > 0);
585 UpdateSelection (lines, &pre, &post);
587 selection_length = new_selection_length;
588 selection_start = new_selection_start;
589 #else
590 if (selection_length || new_selection_length)
591 ClearCache ();
593 selection_length = new_selection_length;
594 selection_start = new_selection_start;
595 #endif
599 * TextLayout::GetActualExtents:
600 * @width:
601 * @height:
603 * Gets the actual width and height extents required for rendering the
604 * full text.
606 void
607 TextLayout::GetActualExtents (double *width, double *height)
609 *height = actual_height;
610 *width = actual_width;
614 static int
615 unichar_combining_class (gunichar c)
617 #if GLIB_CHECK_VERSION (2,14,0)
618 if (glib_check_version (2,14,0))
619 return g_unichar_combining_class (c);
620 else
621 #endif
623 return 0;
626 enum WordType {
627 WORD_TYPE_UNKNOWN,
628 WORD_TYPE_ALPHABETIC,
629 WORD_TYPE_IDEOGRAPHIC,
630 WORD_TYPE_INSEPARABLE,
631 WORD_TYPE_NUMERIC,
632 WORD_TYPE_HANGUL,
635 struct WordBreakOpportunity {
636 GUnicodeBreakType btype;
637 const char *inptr;
638 double advance;
639 guint32 index;
640 guint32 prev;
641 gunichar c;
642 int count;
645 struct LayoutWord {
646 // <internal use>
647 GArray *break_ops; // TextWrappingWrap only
649 WordType type;
651 // <input>
652 double line_advance;
653 TextFont *font;
655 // <input/output>
656 guint32 prev; // previous glyph index; used for kerning
658 // <output>
659 double advance; // the advance-width of the 'word'
660 int length; // length of the word in bytes
661 int count; // length of the word in unichars
664 typedef bool (* LayoutWordCallback) (LayoutWord *word, const char *in, const char *inend, double max_width);
666 static inline bool
667 IsLineBreak (const char *text, size_t left, size_t *n_bytes, size_t *n_chars)
669 const char *inptr = text;
670 gunichar c;
672 if ((c = utf8_getc (&inptr, left)) == (gunichar) -1)
673 return false;
675 if (!UnicharIsLineBreak (c))
676 return false;
678 if (c == '\r' && *inptr == '\n') {
679 *n_bytes = 2;
680 *n_chars = 2;
681 } else {
682 *n_bytes = (size_t) (inptr - text);
683 *n_chars = 1;
686 return true;
689 static inline void
690 layout_word_init (LayoutWord *word, double line_advance, guint32 prev)
692 word->line_advance = line_advance;
693 word->prev = prev;
697 * layout_lwsp:
698 * @word: #LayoutWord context
699 * @in: input text
700 * @inend: end of input text
702 * Measures a word containing nothing but LWSP.
704 static void
705 layout_lwsp (LayoutWord *word, const char *in, const char *inend)
707 guint32 prev = word->prev;
708 GUnicodeBreakType btype;
709 const char *inptr = in;
710 const char *start;
711 GlyphInfo *glyph;
712 double advance;
713 gunichar c;
715 d(printf ("layout_lwsp():\n"));
717 word->advance = 0.0;
718 word->count = 0;
720 while (inptr < inend) {
721 start = inptr;
722 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
723 // ignore invalid chars
724 continue;
727 if (UnicharIsLineBreak (c)) {
728 inptr = start;
729 break;
732 btype = g_unichar_break_type (c);
733 if (!BreakSpace (c, btype)) {
734 inptr = start;
735 break;
738 #if DEBUG
739 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
740 if (c < 128 && isprint ((int) c))
741 printf ("\tunichar = %c; btype = %s, ctype = %s\n", (char) c,
742 unicode_break_types[btype], unicode_char_types[g_unichar_type (c)]);
743 else
744 printf ("\tunichar = 0x%.4X; btype = %s, ctype = %s\n", c,
745 unicode_break_types[btype], unicode_char_types[g_unichar_type (c)]);
747 #endif
749 word->count++;
751 // treat tab as a single space
752 if (c == '\t')
753 c = ' ';
755 // ignore glyphs the font doesn't contain...
756 if (!(glyph = word->font->GetGlyphInfo (c)))
757 continue;
759 // calculate total glyph advance
760 advance = glyph->metrics.horiAdvance;
761 if ((prev != 0) && APPLY_KERNING (c))
762 advance += word->font->Kerning (prev, glyph->index);
763 else if (glyph->metrics.horiBearingX < 0)
764 advance -= glyph->metrics.horiBearingX;
766 word->line_advance += advance;
767 word->advance += advance;
768 prev = glyph->index;
771 word->length = (inptr - in);
772 word->prev = prev;
776 * layout_word_nowrap:
777 * @word: #LayoutWord context
778 * @in: input text
779 * @inend = end of input text
780 * @max_width: max allowable width for a line
782 * Calculates the advance of the current word.
784 * Returns: %true if the caller should create a new line for the
785 * remainder of the word or %false otherwise.
787 static bool
788 layout_word_nowrap (LayoutWord *word, const char *in, const char *inend, double max_width)
790 GUnicodeBreakType btype = G_UNICODE_BREAK_UNKNOWN;
791 guint32 prev = word->prev;
792 const char *inptr = in;
793 const char *start;
794 GlyphInfo *glyph;
795 double advance;
796 gunichar c;
798 // Note: since we don't ever need to wrap, no need to keep track of word-type
799 word->type = WORD_TYPE_UNKNOWN;
800 word->advance = 0.0;
801 word->count = 0;
803 while (inptr < inend) {
804 start = inptr;
805 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
806 // ignore invalid chars
807 continue;
810 if (UnicharIsLineBreak (c)) {
811 inptr = start;
812 break;
815 if (btype == G_UNICODE_BREAK_COMBINING_MARK) {
816 // ignore zero-width spaces
817 if ((btype = g_unichar_break_type (c)) == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
818 btype = G_UNICODE_BREAK_COMBINING_MARK;
819 } else {
820 btype = g_unichar_break_type (c);
823 if (BreakSpace (c, btype)) {
824 inptr = start;
825 break;
828 word->count++;
830 // ignore glyphs the font doesn't contain...
831 if (!(glyph = word->font->GetGlyphInfo (c)))
832 continue;
834 // calculate total glyph advance
835 advance = glyph->metrics.horiAdvance;
836 if ((prev != 0) && APPLY_KERNING (c))
837 advance += word->font->Kerning (prev, glyph->index);
838 else if (glyph->metrics.horiBearingX < 0)
839 advance -= glyph->metrics.horiBearingX;
841 word->line_advance += advance;
842 word->advance += advance;
843 prev = glyph->index;
846 word->length = (inptr - in);
847 word->prev = prev;
849 return false;
852 static WordType
853 word_type (GUnicodeType ctype, GUnicodeBreakType btype)
855 switch (btype) {
856 case G_UNICODE_BREAK_ALPHABETIC:
857 return WORD_TYPE_ALPHABETIC;
858 case G_UNICODE_BREAK_IDEOGRAPHIC:
859 return WORD_TYPE_IDEOGRAPHIC;
860 case G_UNICODE_BREAK_NUMERIC:
861 if (ctype == G_UNICODE_OTHER_PUNCTUATION)
862 return WORD_TYPE_UNKNOWN;
863 return WORD_TYPE_NUMERIC;
864 case G_UNICODE_BREAK_INSEPARABLE:
865 return WORD_TYPE_INSEPARABLE;
866 #if GLIB_CHECK_VERSION (2,10,0)
867 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE:
868 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE:
869 case G_UNICODE_BREAK_HANGUL_L_JAMO:
870 case G_UNICODE_BREAK_HANGUL_V_JAMO:
871 case G_UNICODE_BREAK_HANGUL_T_JAMO:
872 return WORD_TYPE_HANGUL;
873 #endif
874 default:
875 return WORD_TYPE_UNKNOWN;
879 static bool
880 word_type_changed (WordType wtype, gunichar c, GUnicodeType ctype, GUnicodeBreakType btype)
882 WordType type;
884 if (wtype == WORD_TYPE_ALPHABETIC && c >= '0' && c <= '9')
885 return false;
887 if ((type = word_type (ctype, btype)) != WORD_TYPE_UNKNOWN)
888 return type != wtype;
890 switch (type) {
891 case WORD_TYPE_INSEPARABLE:
892 // only allow inseparables in an "inseparable" word.
893 return true;
894 case WORD_TYPE_NUMERIC:
895 // if btype is anything other than an infix, then the word
896 // type has changed.
897 if (btype != G_UNICODE_BREAK_INFIX_SEPARATOR)
898 return true;
899 break;
900 default:
901 break;
904 return false;
908 * layout_word_wrap:
909 * @word: word state
910 * @in: start of word
911 * @inend = end of word
912 * @max_width: max allowable width for a line
914 * Calculates the advance of the current word, breaking if needed.
916 * Returns: %true if the caller should create a new line for the
917 * remainder of the word or %false otherwise.
919 static bool
920 layout_word_wrap (LayoutWord *word, const char *in, const char *inend, double max_width)
922 GUnicodeBreakType btype = G_UNICODE_BREAK_UNKNOWN;
923 bool line_start = word->line_advance == 0.0;
924 guint32 prev = word->prev;
925 WordBreakOpportunity op;
926 const char *inptr = in;
927 const char *start;
928 GUnicodeType ctype;
929 bool force = false;
930 bool fixed = false;
931 bool wrap = false;
932 GlyphInfo *glyph;
933 #if DEBUG
934 GString *debug;
935 #endif
936 double advance;
937 int glyphs = 0;
938 bool new_glyph;
939 gunichar c;
941 g_array_set_size (word->break_ops, 0);
942 word->type = WORD_TYPE_UNKNOWN;
943 word->advance = 0.0;
944 word->count = 0;
946 d(printf ("layout_word_wrap():\n"));
947 d(debug = g_string_new (""));
949 while (inptr < inend) {
950 start = inptr;
951 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
952 // ignore invalid chars
953 continue;
956 if (UnicharIsLineBreak (c)) {
957 inptr = start;
958 break;
961 // check the previous break-type
962 if (btype == G_UNICODE_BREAK_CLOSE_PUNCTUATION) {
963 // if anything other than an infix separator come after a close-punctuation, then the 'word' is done
964 btype = g_unichar_break_type (c);
965 if (btype != G_UNICODE_BREAK_INFIX_SEPARATOR) {
966 inptr = start;
967 break;
969 } else if (btype == G_UNICODE_BREAK_INFIX_SEPARATOR) {
970 btype = g_unichar_break_type (c);
971 if (word->type == WORD_TYPE_NUMERIC) {
972 // only accept numbers after the infix
973 if (btype != G_UNICODE_BREAK_NUMERIC) {
974 inptr = start;
975 break;
977 } else if (word->type == WORD_TYPE_UNKNOWN) {
978 // only accept alphanumerics after the infix
979 if (btype != G_UNICODE_BREAK_ALPHABETIC && btype != G_UNICODE_BREAK_NUMERIC) {
980 inptr = start;
981 break;
984 fixed = true;
986 } else if (btype == G_UNICODE_BREAK_WORD_JOINER) {
987 btype = g_unichar_break_type (c);
988 fixed = true;
989 } else {
990 btype = g_unichar_break_type (c);
993 if (BreakSpace (c, btype)) {
994 inptr = start;
995 break;
998 ctype = g_unichar_type (c);
1000 if (word->type == WORD_TYPE_UNKNOWN) {
1001 // record our word-type
1002 word->type = word_type (ctype, btype);
1003 } else if (btype == G_UNICODE_BREAK_OPEN_PUNCTUATION) {
1004 // this is a good place to break
1005 inptr = start;
1006 break;
1007 } else if (word_type_changed (word->type, c, ctype, btype)) {
1008 // changing word-types, don't continue
1009 inptr = start;
1010 break;
1013 d(g_string_append_unichar (debug, c));
1014 word->count++;
1016 // a Combining Class of 0 means start of a new glyph
1017 if (glyphs > 0 && unichar_combining_class (c) != 0) {
1018 // this char gets combined with the previous glyph
1019 new_glyph = false;
1020 } else {
1021 new_glyph = true;
1022 glyphs++;
1025 #if DEBUG
1026 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1027 if (c < 128 && isprint ((int) c))
1028 printf ("\tunichar = %c; btype = %s, new glyph = %s; cc = %d; isspace = %s\n", (char) c,
1029 unicode_break_types[btype], new_glyph ? "true" : "false", unichar_combining_class (c),
1030 unicode_char_types[ctype]);
1031 else
1032 printf ("\tunichar = 0x%.4X; btype = %s, new glyph = %s; cc = %d; isspace = %s\n", c,
1033 unicode_break_types[btype], new_glyph ? "true" : "false", unichar_combining_class (c),
1034 unicode_char_types[ctype]);
1036 #endif
1038 if ((glyph = word->font->GetGlyphInfo (c))) {
1039 // calculate total glyph advance
1040 advance = glyph->metrics.horiAdvance;
1041 if ((prev != 0) && APPLY_KERNING (c))
1042 advance += word->font->Kerning (prev, glyph->index);
1043 else if (glyph->metrics.horiBearingX < 0)
1044 advance -= glyph->metrics.horiBearingX;
1046 word->line_advance += advance;
1047 word->advance += advance;
1048 prev = glyph->index;
1049 } else {
1050 advance = 0.0;
1053 if (new_glyph) {
1054 op.index = glyph ? glyph->index : 0;
1055 op.advance = word->advance;
1056 op.count = word->count;
1057 op.inptr = inptr;
1058 op.btype = btype;
1059 op.prev = prev;
1060 op.c = c;
1061 } else {
1062 g_array_remove_index (word->break_ops, word->break_ops->len - 1);
1063 op.advance += advance;
1064 op.inptr = inptr;
1065 op.prev = prev;
1066 op.count++;
1069 g_array_append_val (word->break_ops, op);
1071 if (!isinf (max_width) && word->line_advance >= max_width) {
1072 d(printf ("\tjust exceeded max width (%fpx): %s\n", max_width, debug->str));
1073 wrap = true;
1074 break;
1078 if (!wrap) {
1079 d(g_string_free (debug, true));
1080 word->length = (inptr - in);
1081 word->prev = prev;
1082 return false;
1085 d(printf ("\tcollecting any/all decomposed chars and figuring out the break-type for the next glyph\n"));
1087 // pretend btype is SPACE here in case inptr is at the end of the run
1088 if (inptr == inend)
1089 btype = G_UNICODE_BREAK_SPACE;
1091 // keep going until we reach a new distinct glyph. we also
1092 // need to know the btype of the char after the char that
1093 // exceeded the width limit.
1094 while (inptr < inend) {
1095 start = inptr;
1096 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1) {
1097 // ignore invalid chars
1098 continue;
1101 if (UnicharIsLineBreak (c)) {
1102 btype = G_UNICODE_BREAK_SPACE;
1103 inptr = start;
1104 break;
1107 btype = g_unichar_break_type (c);
1108 if (BreakSpace (c, btype) || unichar_combining_class (c) == 0) {
1109 inptr = start;
1110 break;
1113 d(g_string_append_unichar (debug, c));
1114 word->count++;
1116 if ((glyph = word->font->GetGlyphInfo (c))) {
1117 // calculate total glyph advance
1118 advance = glyph->metrics.horiAdvance;
1119 if ((prev != 0) && APPLY_KERNING (c))
1120 advance += word->font->Kerning (prev, glyph->index);
1121 else if (glyph->metrics.horiBearingX < 0)
1122 advance -= glyph->metrics.horiBearingX;
1124 word->line_advance += advance;
1125 word->advance += advance;
1126 prev = glyph->index;
1127 } else {
1128 advance = 0.0;
1131 g_array_remove_index (word->break_ops, word->break_ops->len - 1);
1132 op.advance += advance;
1133 op.inptr = inptr;
1134 op.prev = prev;
1135 op.count++;
1136 g_array_append_val (word->break_ops, op);
1139 d(printf ("\tok, at this point we have: %s\n", debug->str));
1140 d(printf ("\tnext break-type is %s\n", unicode_break_types[btype]));
1141 d(g_string_free (debug, true));
1143 // at this point, we're going to break the word so we can reset kerning
1144 word->prev = 0;
1146 // we can't break any smaller than a single glyph
1147 if (line_start && glyphs == 1) {
1148 d(printf ("\tsince this is the first glyph on the line, can't break any smaller than that...\n"));
1149 word->length = (inptr - in);
1150 word->prev = prev;
1151 return true;
1154 retry:
1156 // search backwards for the best break point
1157 d(printf ("\tscanning over %d break opportunities...\n", word->break_ops->len));
1158 for (guint i = word->break_ops->len; i > 0; i--) {
1159 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 1);
1161 #if DEBUG
1162 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1163 if (op.c < 128 && isprint ((int) op.c))
1164 printf ("\tunichar = %c; btype = %s; i = %d\n", (char) op.c, unicode_break_types[op.btype], i);
1165 else
1166 printf ("\tunichar = 0x%.4X; btype = %s; i = %d\n", op.c, unicode_break_types[op.btype], i);
1168 #endif
1170 switch (op.btype) {
1171 case G_UNICODE_BREAK_BEFORE_AND_AFTER:
1172 if (i > 1 && i == word->break_ops->len) {
1173 // break after the previous glyph
1174 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1175 word->length = (op.inptr - in);
1176 word->advance = op.advance;
1177 word->count = op.count;
1178 word->prev = op.prev;
1180 return true;
1181 } else if (i < word->break_ops->len) {
1182 // break after this glyph
1183 word->length = (op.inptr - in);
1184 word->advance = op.advance;
1185 word->count = op.count;
1186 word->prev = op.prev;
1188 return true;
1190 case G_UNICODE_BREAK_NON_BREAKING_GLUE:
1191 case G_UNICODE_BREAK_WORD_JOINER:
1192 // cannot break before or after this character (unless forced)
1193 if (force && i < word->break_ops->len) {
1194 word->length = (op.inptr - in);
1195 word->advance = op.advance;
1196 word->count = op.count;
1197 word->prev = op.prev;
1199 return true;
1202 if (i > 1) {
1203 // skip past previous glyph
1204 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1205 i--;
1207 break;
1208 case G_UNICODE_BREAK_INSEPARABLE:
1209 // only restriction is no breaking between inseparables unless we have to
1210 if (line_start && i < word->break_ops->len) {
1211 word->length = (op.inptr - in);
1212 word->advance = op.advance;
1213 word->count = op.count;
1214 word->prev = op.prev;
1216 return true;
1218 break;
1219 case G_UNICODE_BREAK_BEFORE:
1220 if (i > 1) {
1221 // break after the previous glyph
1222 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1223 word->length = (op.inptr - in);
1224 word->advance = op.advance;
1225 word->count = op.count;
1226 word->prev = op.prev;
1228 return true;
1230 break;
1231 case G_UNICODE_BREAK_CLOSE_PUNCTUATION:
1232 if (i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_INFIX_SEPARATOR)) {
1233 // we can safely break after this character
1234 word->length = (op.inptr - in);
1235 word->advance = op.advance;
1236 word->count = op.count;
1237 word->prev = op.prev;
1239 return true;
1242 if (i > 1 && !force) {
1243 // we can never break before a closing punctuation, so skip past prev char
1244 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1245 i--;
1247 break;
1248 case G_UNICODE_BREAK_INFIX_SEPARATOR:
1249 if (i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_NUMERIC)) {
1250 // we can safely break after this character
1251 word->length = (op.inptr - in);
1252 word->advance = op.advance;
1253 word->count = op.count;
1254 word->prev = op.prev;
1256 return true;
1259 if (i > 1 && !force) {
1260 // we can never break before an infix, skip past prev char
1261 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 2);
1262 if (op.btype == G_UNICODE_BREAK_INFIX_SEPARATOR ||
1263 op.btype == G_UNICODE_BREAK_CLOSE_PUNCTUATION) {
1264 // unless previous char is one of these special types...
1265 op = g_array_index (word->break_ops, WordBreakOpportunity, i - 1);
1266 } else {
1267 i--;
1270 break;
1271 case G_UNICODE_BREAK_ALPHABETIC:
1272 // only break if we have no choice...
1273 if ((line_start || fixed || force) && i < word->break_ops->len) {
1274 word->length = (op.inptr - in);
1275 word->advance = op.advance;
1276 word->count = op.count;
1277 word->prev = op.prev;
1279 return true;
1281 break;
1282 case G_UNICODE_BREAK_IDEOGRAPHIC:
1283 if (i < word->break_ops->len && btype != G_UNICODE_BREAK_NON_STARTER) {
1284 // we can safely break after this character
1285 word->length = (op.inptr - in);
1286 word->advance = op.advance;
1287 word->count = op.count;
1288 word->prev = op.prev;
1290 return true;
1292 break;
1293 case G_UNICODE_BREAK_NUMERIC:
1294 // only break if we have no choice...
1295 if (line_start && i < word->break_ops->len && (force || btype != G_UNICODE_BREAK_INFIX_SEPARATOR)) {
1296 word->length = (op.inptr - in);
1297 word->advance = op.advance;
1298 word->count = op.count;
1299 word->prev = op.prev;
1301 return true;
1303 break;
1304 case G_UNICODE_BREAK_OPEN_PUNCTUATION:
1305 case G_UNICODE_BREAK_COMBINING_MARK:
1306 case G_UNICODE_BREAK_CONTINGENT:
1307 case G_UNICODE_BREAK_AMBIGUOUS:
1308 case G_UNICODE_BREAK_QUOTATION:
1309 case G_UNICODE_BREAK_PREFIX:
1310 // do not break after characters with these break-types (unless forced)
1311 if (force && i < word->break_ops->len) {
1312 word->length = (op.inptr - in);
1313 word->advance = op.advance;
1314 word->count = op.count;
1315 word->prev = op.prev;
1317 return true;
1319 break;
1320 default:
1321 d(printf ("Unhandled Unicode break-type: %s\n", unicode_break_types[op.btype]));
1322 // fall thru to the "default" behavior
1324 #if GLIB_CHECK_VERSION (2,10,0)
1325 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE:
1326 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE:
1327 case G_UNICODE_BREAK_HANGUL_L_JAMO:
1328 case G_UNICODE_BREAK_HANGUL_V_JAMO:
1329 case G_UNICODE_BREAK_HANGUL_T_JAMO:
1330 #endif
1331 case G_UNICODE_BREAK_NON_STARTER:
1332 case G_UNICODE_BREAK_EXCLAMATION:
1333 case G_UNICODE_BREAK_MANDATORY:
1334 case G_UNICODE_BREAK_NEXT_LINE:
1335 case G_UNICODE_BREAK_UNKNOWN:
1336 case G_UNICODE_BREAK_POSTFIX:
1337 case G_UNICODE_BREAK_HYPHEN:
1338 case G_UNICODE_BREAK_AFTER:
1339 if (i < word->break_ops->len) {
1340 // we can safely break after this character
1341 word->length = (op.inptr - in);
1342 word->advance = op.advance;
1343 word->count = op.count;
1344 word->prev = op.prev;
1346 return true;
1348 break;
1351 btype = op.btype;
1352 c = op.c;
1355 if (line_start && !force) {
1356 d(printf ("\tcouldn't find a good place to break but we must force a break, retrying...\n"));
1357 force = true;
1358 goto retry;
1361 d(printf ("\tcouldn't find a good place to break, defaulting to breaking before the word start\n"));
1363 word->advance = 0.0;
1364 word->length = 0;
1365 word->count = 0;
1367 return true;
1370 #if DEBUG
1371 static const char *wrap_modes[3] = {
1372 "WrapWithOverflow",
1373 "NoWrap",
1374 "Wrap"
1377 static void
1378 print_lines (GPtrArray *lines)
1380 TextLayoutLine *line;
1381 TextLayoutRun *run;
1382 const char *text;
1383 double y = 0.0;
1385 for (guint i = 0; i < lines->len; i++) {
1386 line = (TextLayoutLine *) lines->pdata[i];
1388 printf ("Line (top=%f, height=%f, advance=%f, offset=%d, count=%d):\n", y, line->height, line->advance, line->offset, line->count);
1389 for (guint j = 0; j < line->runs->len; j++) {
1390 run = (TextLayoutRun *) line->runs->pdata[j];
1392 text = line->layout->GetText () + run->start;
1394 printf ("\tRun (advance=%f, start=%d, length=%d): \"", run->advance, run->start, run->length);
1395 for (const char *s = text; s < text + run->length; s++) {
1396 switch (*s) {
1397 case '\r':
1398 fputs ("\\r", stdout);
1399 break;
1400 case '\n':
1401 fputs ("\\n", stdout);
1402 break;
1403 case '\t':
1404 fputs ("\\t", stdout);
1405 break;
1406 case '"':
1407 fputs ("\\\"", stdout);
1408 break;
1409 default:
1410 fputc (*s, stdout);
1411 break;
1414 printf ("\"\n");
1417 y += line->height;
1420 #endif
1422 static LayoutWordCallback layout_word_behavior[] = {
1423 layout_word_wrap,
1424 layout_word_nowrap,
1425 layout_word_wrap
1428 void
1429 TextLayout::Layout ()
1431 TextLayoutAttributes *attrs, *nattrs;
1432 LayoutWordCallback layout_word;
1433 const char *inptr, *inend;
1434 size_t n_bytes, n_chars;
1435 TextLayoutLine *line;
1436 TextLayoutRun *run;
1437 LayoutWord word;
1438 TextFont *font;
1439 bool linebreak;
1440 int offset = 0;
1441 guint32 prev;
1442 bool wrapped;
1444 if (!isnan (actual_width))
1445 return;
1447 actual_height = 0.0;
1448 actual_width = 0.0;
1449 is_wrapped = false;
1450 ClearLines ();
1451 count = 0;
1453 if (!text || !(attrs = (TextLayoutAttributes *) attributes->First ()) || attrs->start != 0)
1454 return;
1456 d(printf ("TextLayout::Layout(): wrap mode = %s, wrapping to %f pixels\n", wrap_modes[wrapping], max_width));
1458 if (wrapping == TextWrappingWrap)
1459 word.break_ops = g_array_new (false, false, sizeof (WordBreakOpportunity));
1460 else
1461 word.break_ops = NULL;
1463 layout_word = layout_word_behavior[wrapping];
1465 line = new TextLayoutLine (this, 0, 0);
1466 if (OverrideLineHeight ())
1467 line->height = line_height;
1469 g_ptr_array_add (lines, line);
1470 inptr = text;
1472 do {
1473 nattrs = (TextLayoutAttributes *) attrs->next;
1474 inend = text + (nattrs ? nattrs->start : length);
1475 run = new TextLayoutRun (line, attrs, inptr - text);
1476 g_ptr_array_add (line->runs, run);
1478 word.font = font = attrs->Font ();
1480 if (!OverrideLineHeight ()) {
1481 line->descend = MIN (line->descend, font->Descender ());
1482 line->height = MAX (line->height, font->Height ());
1485 if (*inptr == '\0') {
1486 actual_height += line->height;
1487 break;
1490 // layout until attrs change
1491 while (inptr < inend) {
1492 linebreak = false;
1493 wrapped = false;
1494 prev = 0;
1496 // layout until eoln or until we reach max_width
1497 while (inptr < inend) {
1498 // check for line-breaks
1499 if (IsLineBreak (inptr, inend - inptr, &n_bytes, &n_chars)) {
1500 line->length += n_bytes;
1501 run->length += n_bytes;
1502 line->count += n_chars;
1503 run->count += n_chars;
1504 offset += n_chars;
1505 inptr += n_bytes;
1506 linebreak = true;
1507 break;
1510 layout_word_init (&word, line->advance, prev);
1512 // lay out the next word
1513 if (layout_word (&word, inptr, inend, max_width)) {
1514 // force a line wrap...
1515 is_wrapped = true;
1516 wrapped = true;
1519 if (word.length > 0) {
1520 // append the word to the run/line
1521 line->advance += word.advance;
1522 run->advance += word.advance;
1523 line->width = line->advance;
1524 line->length += word.length;
1525 run->length += word.length;
1526 line->count += word.count;
1527 run->count += word.count;
1529 offset += word.count;
1530 inptr += word.length;
1531 prev = word.prev;
1534 if (wrapped)
1535 break;
1537 // now append any trailing lwsp
1538 layout_word_init (&word, line->advance, prev);
1540 layout_lwsp (&word, inptr, inend);
1542 if (word.length > 0) {
1543 line->advance += word.advance;
1544 run->advance += word.advance;
1545 line->length += word.length;
1546 run->length += word.length;
1547 line->count += word.count;
1548 run->count += word.count;
1550 offset += word.count;
1551 inptr += word.length;
1552 prev = word.prev;
1556 if (linebreak || wrapped || *inptr == '\0') {
1557 // update actual width extents
1558 if (*inptr == '\0') {
1559 // ActualWidth extents only include trailing lwsp on the last line
1560 actual_width = MAX (actual_width, line->advance);
1561 } else {
1562 // not the last line, so don't include trailing lwsp
1563 actual_width = MAX (actual_width, line->width);
1566 // update actual height extents
1567 actual_height += line->height;
1569 if (linebreak || wrapped) {
1570 // more text to layout... which means we'll need a new line
1571 line = new TextLayoutLine (this, inptr - text, offset);
1573 if (!OverrideLineHeight ()) {
1574 if (*inptr == '\0' || inptr < inend) {
1575 line->descend = font->Descender ();
1576 line->height = font->Height ();
1578 } else {
1579 line->height = line_height;
1582 g_ptr_array_add (lines, line);
1583 prev = 0;
1586 if (inptr < inend) {
1587 if (!OverrideLineHeight ()) {
1588 line->descend = font->Descender ();
1589 line->height = font->Height ();
1592 // more text to layout with the current attrs...
1593 run = new TextLayoutRun (line, attrs, inptr - text);
1594 g_ptr_array_add (line->runs, run);
1599 attrs = nattrs;
1600 } while (*inptr != '\0');
1602 if (word.break_ops != NULL)
1603 g_array_free (word.break_ops, true);
1605 count = offset;
1607 #if DEBUG
1608 if (debug_flags & RUNTIME_DEBUG_LAYOUT) {
1609 print_lines (lines);
1610 printf ("actualWidth = %f, actualHeight = %f\n\n", actual_width, actual_height);
1612 #endif
1615 static inline TextLayoutGlyphCluster *
1616 GenerateGlyphCluster (TextFont *font, guint32 *kern, const char *text, int start, int length)
1618 TextLayoutGlyphCluster *cluster = new TextLayoutGlyphCluster (start, length);
1619 const char *inend = text + start + length;
1620 const char *inptr = text + start;
1621 guint32 prev = *kern;
1622 double x0, x1, y0;
1623 GlyphInfo *glyph;
1624 int size = 0;
1625 gunichar c;
1627 // set y0 to the baseline
1628 y0 = font->Ascender ();
1629 x0 = 0.0;
1630 x1 = 0.0;
1632 // count how many path data items we'll need to allocate
1633 while (inptr < inend) {
1634 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
1635 continue;
1637 if (UnicharIsLineBreak (c))
1638 break;
1640 // treat tab as a single space
1641 if (c == '\t')
1642 c = ' ';
1644 if (!(glyph = font->GetGlyphInfo (c)))
1645 continue;
1647 if (glyph->path)
1648 size += glyph->path->cairo.num_data + 1;
1651 if (size > 0) {
1652 // generate the cached path for the cluster
1653 cluster->path = moon_path_new (size);
1654 inptr = text + start;
1656 while (inptr < inend) {
1657 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
1658 continue;
1660 if (UnicharIsLineBreak (c))
1661 break;
1663 // treat tab as a single space
1664 if (c == '\t')
1665 c = ' ';
1667 if (!(glyph = font->GetGlyphInfo (c)))
1668 continue;
1670 if ((prev != 0) && APPLY_KERNING (c))
1671 x0 += font->Kerning (prev, glyph->index);
1672 else if (glyph->metrics.horiBearingX < 0)
1673 x0 += glyph->metrics.horiBearingX;
1675 font->AppendPath (cluster->path, glyph, x0, y0);
1676 x0 += glyph->metrics.horiAdvance;
1677 prev = glyph->index;
1679 if (!g_unichar_isspace (c))
1680 x1 = x0;
1683 moon_close_path (cluster->path);
1686 cluster->uadvance = x1;
1687 cluster->advance = x0;
1689 *kern = prev;
1691 return cluster;
1694 void
1695 TextLayoutRun::GenerateCache ()
1697 int selection_length = line->layout->GetSelectionLength ();
1698 int selection_start = line->layout->GetSelectionStart ();
1699 const char *text = line->layout->GetText ();
1700 const char *inend = text + start + length;
1701 const char *inptr = text + start;
1702 TextFont *font = attrs->Font ();
1703 TextLayoutGlyphCluster *cluster;
1704 const char *selection_end;
1705 guint32 prev = 0;
1706 int len;
1708 // cache the glyph cluster leading up to the selection
1709 if (selection_length == 0 || start < selection_start) {
1710 if (selection_length > 0)
1711 len = MIN (selection_start - start, length);
1712 else
1713 len = length;
1715 cluster = GenerateGlyphCluster (font, &prev, text, start, len);
1716 g_ptr_array_add (clusters, cluster);
1717 inptr += len;
1720 // cache the selected glyph cluster
1721 selection_end = text + selection_start + selection_length;
1722 if (inptr < inend && inptr < selection_end) {
1723 len = MIN (inend, selection_end) - inptr;
1724 cluster = GenerateGlyphCluster (font, &prev, text, inptr - text, len);
1725 g_ptr_array_add (clusters, cluster);
1726 cluster->selected = true;
1727 inptr += len;
1730 // cache the glyph cluster following the selection
1731 if (inptr < inend) {
1732 cluster = GenerateGlyphCluster (font, &prev, text, inptr - text, inend - inptr);
1733 g_ptr_array_add (clusters, cluster);
1734 inptr = inend;
1738 void
1739 TextLayoutGlyphCluster::Render (cairo_t *cr, const Point &origin, TextLayoutAttributes *attrs, const char *text, double x, double y, bool uline_full)
1741 TextFont *font = attrs->Font ();
1742 const char *inend, *prev;
1743 GlyphInfo *glyph;
1744 Brush *brush;
1745 gunichar c;
1746 double y0;
1747 Rect area;
1749 if (length == 0)
1750 return;
1752 // y is the baseline, set the origin to the top-left
1753 cairo_translate (cr, x, y - font->Ascender ());
1755 // set y0 to the baseline relative to the translation matrix
1756 y0 = font->Ascender ();
1758 if (selected) {
1759 area = Rect (origin.x, origin.y, advance, font->Height ());
1761 // extend the selection background by the width of a SPACE if it includes CRLF
1762 inend = text + start + length;
1763 if ((prev = g_utf8_find_prev_char (text + start, inend)))
1764 c = utf8_getc (&prev, inend - prev);
1765 else
1766 c = (gunichar) -1;
1768 if (UnicharIsLineBreak (c)) {
1769 if ((glyph = font->GetGlyphInfo (' ')))
1770 area.width += glyph->metrics.horiAdvance;
1773 // render the selection background
1774 brush = attrs->Background (true);
1775 brush->SetupBrush (cr, area);
1776 cairo_new_path (cr);
1777 cairo_rectangle (cr, area.x, area.y, area.width, area.height);
1778 brush->Fill (cr);
1781 // setup the foreground brush
1782 area = Rect (origin.x, origin.y, advance, font->Height ());
1783 brush = attrs->Foreground (selected);
1784 brush->SetupBrush (cr, area);
1785 cairo_new_path (cr);
1787 if (path && path->cairo.data)
1788 cairo_append_path (cr, &path->cairo);
1790 brush->Fill (cr);
1792 if (attrs->IsUnderlined ()) {
1793 double thickness = font->UnderlineThickness ();
1794 double pos = y0 + font->UnderlinePosition ();
1796 cairo_set_line_width (cr, thickness);
1798 cairo_new_path (cr);
1799 Rect underline = Rect (0.0, pos - thickness * 0.5, uline_full ? advance : uadvance, thickness);
1800 underline.Draw (cr);
1802 brush->Fill (cr);
1806 void
1807 TextLayoutRun::Render (cairo_t *cr, const Point &origin, double x, double y, bool is_last_run)
1809 const char *text = line->layout->GetText ();
1810 TextLayoutGlyphCluster *cluster;
1811 double x0 = x;
1813 if (clusters->len == 0)
1814 GenerateCache ();
1816 for (guint i = 0; i < clusters->len; i++) {
1817 cluster = (TextLayoutGlyphCluster *) clusters->pdata[i];
1819 cairo_save (cr);
1820 cluster->Render (cr, origin, attrs, text, x0, y, is_last_run && ((i + 1) < clusters->len));
1821 cairo_restore (cr);
1823 x0 += cluster->advance;
1827 void
1828 TextLayoutLine::Render (cairo_t *cr, const Point &origin, double left, double top)
1830 TextLayoutRun *run;
1831 double x0, y0;
1833 // set y0 to the line's baseline (descend is a negative value)
1834 y0 = top + height + descend;
1835 x0 = left;
1837 for (guint i = 0; i < runs->len; i++) {
1838 run = (TextLayoutRun *) runs->pdata[i];
1839 run->Render (cr, origin, x0, y0, (i + 1) < runs->len);
1840 x0 += run->advance;
1844 static double
1845 GetWidthConstraint (double avail_width, double max_width, double actual_width)
1847 if (isinf (avail_width)) {
1848 // find an upper width constraint
1849 if (isinf (max_width))
1850 return actual_width;
1851 else
1852 return max_width;
1855 return avail_width;
1858 double
1859 TextLayout::HorizontalAlignment (double line_width)
1861 double deltax;
1862 double width;
1864 switch (alignment) {
1865 case TextAlignmentCenter:
1866 width = GetWidthConstraint (avail_width, max_width, actual_width);
1867 if (line_width < width)
1868 deltax = (width - line_width) / 2.0;
1869 else
1870 deltax = 0.0;
1871 break;
1872 case TextAlignmentRight:
1873 width = GetWidthConstraint (avail_width, max_width, actual_width);
1874 if (line_width < width)
1875 deltax = width - line_width;
1876 else
1877 deltax = 0.0;
1878 break;
1879 default:
1880 deltax = 0.0;
1881 break;
1884 return deltax;
1887 void
1888 TextLayout::Render (cairo_t *cr, const Point &origin, const Point &offset)
1890 TextLayoutLine *line;
1891 double x, y;
1893 y = offset.y;
1895 Layout ();
1897 for (guint i = 0; i < lines->len; i++) {
1898 line = (TextLayoutLine *) lines->pdata[i];
1900 x = offset.x + HorizontalAlignment (line->advance);
1901 line->Render (cr, origin, x, y);
1902 y += (double) line->height;
1906 #ifdef USE_BINARY_SEARCH
1908 * MID:
1909 * @lo: the low bound
1910 * @hi: the high bound
1912 * Finds the midpoint between positive integer values, @lo and @hi.
1914 * Notes: Typically expressed as '(@lo + @hi) / 2', this is incorrect
1915 * when @lo and @hi are sufficiently large enough that combining them
1916 * would overflow their integer type. To work around this, we use the
1917 * formula, '@lo + ((@hi - @lo) / 2)', thus preventing this problem
1918 * from occuring.
1920 * Returns the midpoint between @lo and @hi (rounded down).
1922 #define MID(lo, hi) (lo + ((hi - lo) >> 1))
1924 TextLayoutLine *
1925 TextLayout::GetLineFromY (const Point &offset, double y, int *index)
1927 register guint lo, hi;
1928 TextLayoutLine *line;
1929 double y0;
1930 guint m;
1932 if (lines->len == 0)
1933 return NULL;
1935 lo = 0, hi = lines->len;
1936 y0 = y - offset.y;
1938 do {
1939 m = MID (lo, hi);
1941 line = (TextLayoutLine *) lines->pdata[m];
1943 if (m > 0 && y0 < line->top) {
1944 // y is on some line above us
1945 hi = m;
1946 } else if (y0 > line->top + line->height) {
1947 // y is on some line below us
1948 lo = m + 1;
1949 m = lo;
1950 } else {
1951 // y is on this line
1952 break;
1955 line = NULL;
1956 } while (lo < hi);
1958 if (line && index)
1959 *index = m;
1961 return line;
1963 #else /* linear search */
1964 TextLayoutLine *
1965 TextLayout::GetLineFromY (const Point &offset, double y, int *index)
1967 TextLayoutLine *line = NULL;
1968 double y0, y1;
1970 //printf ("TextLayout::GetLineFromY (%.2g)\n", y);
1972 y0 = offset.y;
1974 for (guint i = 0; i < lines->len; i++) {
1975 line = (TextLayoutLine *) lines->pdata[i];
1977 // set y1 the top of the next line
1978 y1 = y0 + line->height;
1980 if (y < y1) {
1981 // we found the line that the point is located on
1982 if (index)
1983 *index = (int) i;
1985 return line;
1988 y0 = y1;
1991 return NULL;
1993 #endif
1995 TextLayoutLine *
1996 TextLayout::GetLineFromIndex (int index)
1998 if (index >= (int) lines->len || index < 0)
1999 return NULL;
2001 return (TextLayoutLine *) lines->pdata[index];
2005 TextLayoutLine::GetCursorFromX (const Point &offset, double x)
2007 const char *text, *inend, *ch, *inptr;
2008 TextLayoutRun *run = NULL;
2009 GlyphInfo *glyph;
2010 guint32 prev = 0;
2011 TextFont *font;
2012 int cursor;
2013 gunichar c;
2014 double x0;
2015 double m;
2016 guint i;
2018 // adjust x0 for horizontal alignment
2019 x0 = offset.x + layout->HorizontalAlignment (advance);
2021 text = layout->GetText ();
2022 inptr = text + start;
2023 cursor = this->offset;
2025 for (i = 0; i < runs->len; i++) {
2026 run = (TextLayoutRun *) runs->pdata[i];
2028 if (x < x0 + run->advance) {
2029 // x is in somewhere inside this run
2030 break;
2033 // x is beyond this run
2034 cursor += run->count;
2035 inptr += run->length;
2036 x0 += run->advance;
2037 run = NULL;
2040 if (run != NULL) {
2041 inptr = text + run->start;
2042 inend = inptr + run->length;
2043 font = run->attrs->Font ();
2045 while (inptr < inend) {
2046 ch = inptr;
2047 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
2048 continue;
2050 if (UnicharIsLineBreak (c)) {
2051 inptr = ch;
2052 break;
2055 cursor++;
2057 // we treat tabs as a single space
2058 if (c == '\t')
2059 c = ' ';
2061 if (!(glyph = font->GetGlyphInfo (c)))
2062 continue;
2064 if ((prev != 0) && APPLY_KERNING (c))
2065 x0 += font->Kerning (prev, glyph->index);
2066 else if (glyph->metrics.horiBearingX < 0)
2067 x0 += glyph->metrics.horiBearingX;
2069 // calculate midpoint of the character
2070 m = glyph->metrics.horiAdvance / 2.0;
2072 // if x is <= the midpoint, then the cursor is
2073 // considered to be at the start of this character.
2074 if (x <= x0 + m) {
2075 inptr = ch;
2076 cursor--;
2077 break;
2080 x0 += glyph->metrics.horiAdvance;
2081 prev = glyph->index;
2083 } else if (i > 0) {
2084 // x is beyond the end of the last run
2085 run = (TextLayoutRun *) runs->pdata[i - 1];
2086 inend = text + run->start + run->length;
2087 inptr = text + run->start;
2089 if ((ch = g_utf8_find_prev_char (inptr, inend)))
2090 c = utf8_getc (&ch, inend - ch);
2091 else
2092 c = (gunichar) -1;
2094 if (c == '\n') {
2095 cursor--;
2096 inend--;
2098 if (inend > inptr && inend[-1] == '\r') {
2099 cursor--;
2100 inend--;
2102 } else if (UnicharIsLineBreak (c)) {
2103 cursor--;
2104 inend--;
2108 return cursor;
2112 TextLayout::GetCursorFromXY (const Point &offset, double x, double y)
2114 TextLayoutLine *line;
2116 //printf ("TextLayout::GetCursorFromXY (%.2g, %.2g)\n", x, y);
2118 if (y < offset.y)
2119 return 0;
2121 if (!(line = GetLineFromY (offset, y)))
2122 return count;
2124 return line->GetCursorFromX (offset, x);
2127 Rect
2128 TextLayout::GetCursor (const Point &offset, int index)
2130 const char *inptr, *inend, *pchar;
2131 double height, x0, y0, y1;
2132 TextLayoutLine *line;
2133 TextLayoutRun *run;
2134 GlyphInfo *glyph;
2135 TextFont *font;
2136 int cursor = 0;
2137 guint32 prev;
2138 gunichar c;
2140 //printf ("TextLayout::GetCursor (%d)\n", index);
2142 x0 = offset.x;
2143 y0 = offset.y;
2144 height = 0.0;
2145 y1 = 0.0;
2147 for (guint i = 0; i < lines->len; i++) {
2148 line = (TextLayoutLine *) lines->pdata[i];
2149 inend = text + line->start + line->length;
2151 // adjust x0 for horizontal alignment
2152 x0 = offset.x + HorizontalAlignment (line->advance);
2154 // set y1 to the baseline (descend is a negative value)
2155 y1 = y0 + line->height + line->descend;
2156 height = line->height;
2158 //printf ("\tline: left=%.2f, top=%.2f, baseline=%.2f, start index=%d\n", x0, y0, y1, line->offset);
2160 if (index >= cursor + line->count) {
2161 // maybe the cursor is on the next line...
2162 if ((i + 1) == lines->len) {
2163 // we are on the last line... get the previous unichar
2164 inptr = text + line->start;
2165 inend = inptr + line->length;
2167 if ((pchar = g_utf8_find_prev_char (text + line->start, inend)))
2168 c = utf8_getc (&pchar, inend - pchar);
2169 else
2170 c = (gunichar) -1;
2172 if (UnicharIsLineBreak (c)) {
2173 // cursor is on the next line by itself
2174 x0 = offset.x + HorizontalAlignment (0.0);
2175 y0 += line->height;
2176 } else {
2177 // cursor at the end of the last line
2178 x0 += line->advance;
2181 break;
2184 cursor += line->count;
2185 y0 += line->height;
2186 continue;
2189 // cursor is on this line...
2190 for (guint j = 0; j < line->runs->len; j++) {
2191 run = (TextLayoutRun *) line->runs->pdata[j];
2192 inend = text + run->start + run->length;
2194 if (index >= cursor + run->count) {
2195 // maybe the cursor is in the next run...
2196 cursor += run->count;
2197 x0 += run->advance;
2198 continue;
2201 // cursor is in this run...
2202 font = run->attrs->Font ();
2203 inptr = text + run->start;
2204 prev = 0;
2206 while (cursor < index) {
2207 if ((c = utf8_getc (&inptr, inend - inptr)) == (gunichar) -1)
2208 continue;
2210 cursor++;
2212 // we treat tabs as a single space
2213 if (c == '\t')
2214 c = ' ';
2216 if (!(glyph = font->GetGlyphInfo (c)))
2217 continue;
2219 if ((prev != 0) && APPLY_KERNING (c))
2220 x0 += font->Kerning (prev, glyph->index);
2221 else if (glyph->metrics.horiBearingX < 0)
2222 x0 += glyph->metrics.horiBearingX;
2224 x0 += glyph->metrics.horiAdvance;
2225 prev = glyph->index;
2228 break;
2231 break;
2234 return Rect (x0, y0, 1.0, height);