1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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.
18 #include "moon-path.h"
24 #define d(x) if (debug_flags & RUNTIME_DEBUG_LAYOUT) x
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
[] = {
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"
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
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;
132 *in
= (const char *) inptr
;
134 } else if (r
< 0xfe) { /* valid start char? */
136 m
= 0x7f80; /* used to mask out the length bits */
138 if (inptr
>= inend
) {
139 *in
= (const char *) inend
;
144 if ((c
& 0xc0) != 0x80)
147 u
= (u
<< 6) | (c
& 0x3f);
152 *in
= (const char *) inptr
;
166 // TextLayoutGlyphCluster
169 TextLayoutGlyphCluster::TextLayoutGlyphCluster (int _start
, int _length
)
178 TextLayoutGlyphCluster::~TextLayoutGlyphCluster ()
181 moon_path_destroy (path
);
189 TextLayoutRun::TextLayoutRun (TextLayoutLine
*_line
, TextLayoutAttributes
*_attrs
, int _start
)
191 clusters
= g_ptr_array_new ();
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);
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);
222 TextLayoutLine::TextLayoutLine (TextLayout
*_layout
, int _start
, int _offset
)
224 runs
= g_ptr_array_new ();
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);
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;
258 avail_width
= INFINITY
;
259 max_height
= INFINITY
;
260 max_width
= INFINITY
;
267 lines
= g_ptr_array_new ();
274 TextLayout::~TextLayout ()
277 attributes
->Clear (true);
282 g_ptr_array_free (lines
, true);
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);
297 TextLayout::ResetState ()
304 TextLayout::SetBaseFont (const TextFont
*font
)
307 base_descent
= font
->Descender ();
308 base_height
= font
->Height ();
316 TextLayout::SetLineStackingStrategy (LineStackingStrategy mode
)
318 if (strategy
== mode
)
329 TextLayout::SetTextAlignment (TextAlignment align
)
331 if (alignment
== align
)
340 TextLayout::SetTextWrapping (TextWrapping mode
)
343 case TextWrappingNoWrap
:
344 case TextWrappingWrap
:
347 // Silverlight defaults to Wrap for unknown values
348 mode
= TextWrappingWrap
;
352 if (wrapping
== mode
)
363 TextLayout::SetLineHeight (double height
)
365 if (line_height
== height
)
368 line_height
= height
;
376 TextLayout::SetMaxHeight (double height
)
378 if (max_height
== height
)
389 TextLayout::SetMaxWidth (double width
)
394 if (max_width
== width
)
397 if (!is_wrapped
&& (isinf (width
) || width
> actual_width
)) {
398 // the new max_width won't change layout
411 TextLayout::SetTextAttributes (List
*attrs
)
414 attributes
->Clear (true);
426 TextLayout::SetText (const char *str
, int len
)
431 length
= len
== -1 ? strlen (str
) : len
;
432 text
= (char *) g_malloc (length
+ 1);
433 memcpy (text
, str
, length
);
448 TextLayout::ClearCache ()
450 TextLayoutLine
*line
;
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
];
469 UpdateSelection (GPtrArray
*lines
, TextRegion
*pre
, TextRegion
*post
)
471 TextLayoutGlyphCluster
*cluster
;
472 TextLayoutLine
*line
;
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...
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...
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
;
509 if (pre
->start
+ pre
->length
<= run
->start
+ run
->length
)
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...
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...
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
;
547 if (post
->start
+ post
->length
<= run
->start
+ run
->length
)
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
;
560 TextRegion pre
, post
;
565 selection_length
= 0;
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
;
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...
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
;
605 if (selection_length
|| new_selection_length
)
608 selection_length
= new_selection_length
;
609 selection_start
= new_selection_start
;
614 TextLayout::GetActualExtents (double *width
, double *height
)
616 *height
= actual_height
;
617 *width
= actual_width
;
621 unichar_combining_class (gunichar c
)
623 #if GLIB_CHECK_VERSION (2,14,0)
624 if (glib_check_version (2,14,0))
625 return g_unichar_combining_class (c
);
634 WORD_TYPE_ALPHABETIC
,
635 WORD_TYPE_IDEOGRAPHIC
,
636 WORD_TYPE_INSEPARABLE
,
641 struct WordBreakOpportunity
{
642 GUnicodeBreakType btype
;
652 GArray
*break_ops
; // TextWrappingWrap only
661 GlyphInfo
*prev
; // previous glyph; used for kerning
664 double advance
; // the advance-width of the 'word'
665 int length
; // length of the word in bytes
666 int count
; // length of the word in unichars
669 typedef bool (* LayoutWordCallback
) (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
);
672 IsLineBreak (const char *text
, size_t left
, size_t *n_bytes
, size_t *n_chars
)
674 const char *inptr
= text
;
677 if ((c
= utf8_getc (&inptr
, left
)) == (gunichar
) -1)
680 if (!UnicharIsLineBreak (c
))
683 if (c
== '\r' && *inptr
== '\n') {
687 *n_bytes
= (size_t) (inptr
- text
);
695 layout_word_init (LayoutWord
*word
, double line_advance
, GlyphInfo
*prev
)
697 word
->line_advance
= line_advance
;
703 * @word: #LayoutWord context
705 * @inend: end of input text
707 * Measures a word containing nothing but LWSP.
710 layout_lwsp (LayoutWord
*word
, const char *in
, const char *inend
)
712 GlyphInfo
*prev
= word
->prev
;
713 GUnicodeBreakType btype
;
714 const char *inptr
= in
;
720 d(printf ("layout_lwsp():\n"));
725 while (inptr
< inend
) {
727 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
728 // ignore invalid chars
732 if (UnicharIsLineBreak (c
)) {
737 btype
= g_unichar_break_type (c
);
738 if (!BreakSpace (c
, btype
)) {
744 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
745 if (c
< 128 && isprint ((int) c
))
746 printf ("\tunichar = %c; btype = %s, ctype = %s\n", (char) c
,
747 unicode_break_types
[btype
], unicode_char_types
[g_unichar_type (c
)]);
749 printf ("\tunichar = 0x%.4X; btype = %s, ctype = %s\n", c
,
750 unicode_break_types
[btype
], unicode_char_types
[g_unichar_type (c
)]);
756 // treat tab as a single space
760 // ignore glyphs the font doesn't contain...
761 if (!(glyph
= word
->font
->GetGlyphInfo (c
)))
764 // calculate total glyph advance
765 advance
= glyph
->metrics
.horiAdvance
;
766 if ((prev
!= NULL
) && APPLY_KERNING (c
))
767 advance
+= word
->font
->Kerning (prev
, glyph
);
768 else if (glyph
->metrics
.horiBearingX
< 0)
769 advance
-= glyph
->metrics
.horiBearingX
;
771 word
->line_advance
+= advance
;
772 word
->advance
+= advance
;
776 word
->length
= (inptr
- in
);
781 * layout_word_nowrap:
782 * @word: #LayoutWord context
784 * @inend = end of input text
785 * @max_width: max allowable width for a line
787 * Calculates the advance of the current word.
789 * Returns: %true if the caller should create a new line for the
790 * remainder of the word or %false otherwise.
793 layout_word_nowrap (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
)
795 GUnicodeBreakType btype
= G_UNICODE_BREAK_UNKNOWN
;
796 GlyphInfo
*prev
= word
->prev
;
797 const char *inptr
= in
;
803 // Note: since we don't ever need to wrap, no need to keep track of word-type
804 word
->type
= WORD_TYPE_UNKNOWN
;
808 while (inptr
< inend
) {
810 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
811 // ignore invalid chars
815 if (UnicharIsLineBreak (c
)) {
820 if (btype
== G_UNICODE_BREAK_COMBINING_MARK
) {
821 // ignore zero-width spaces
822 if ((btype
= g_unichar_break_type (c
)) == G_UNICODE_BREAK_ZERO_WIDTH_SPACE
)
823 btype
= G_UNICODE_BREAK_COMBINING_MARK
;
825 btype
= g_unichar_break_type (c
);
828 if (BreakSpace (c
, btype
)) {
835 // ignore glyphs the font doesn't contain...
836 if (!(glyph
= word
->font
->GetGlyphInfo (c
)))
839 // calculate total glyph advance
840 advance
= glyph
->metrics
.horiAdvance
;
841 if ((prev
!= NULL
) && APPLY_KERNING (c
))
842 advance
+= word
->font
->Kerning (prev
, glyph
);
843 else if (glyph
->metrics
.horiBearingX
< 0)
844 advance
-= glyph
->metrics
.horiBearingX
;
846 word
->line_advance
+= advance
;
847 word
->advance
+= advance
;
851 word
->length
= (inptr
- in
);
858 word_type (GUnicodeType ctype
, GUnicodeBreakType btype
)
861 case G_UNICODE_BREAK_ALPHABETIC
:
862 return WORD_TYPE_ALPHABETIC
;
863 case G_UNICODE_BREAK_IDEOGRAPHIC
:
864 return WORD_TYPE_IDEOGRAPHIC
;
865 case G_UNICODE_BREAK_NUMERIC
:
866 if (ctype
== G_UNICODE_OTHER_PUNCTUATION
)
867 return WORD_TYPE_UNKNOWN
;
868 return WORD_TYPE_NUMERIC
;
869 case G_UNICODE_BREAK_INSEPARABLE
:
870 return WORD_TYPE_INSEPARABLE
;
871 #if GLIB_CHECK_VERSION (2,10,0)
872 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE
:
873 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE
:
874 case G_UNICODE_BREAK_HANGUL_L_JAMO
:
875 case G_UNICODE_BREAK_HANGUL_V_JAMO
:
876 case G_UNICODE_BREAK_HANGUL_T_JAMO
:
877 return WORD_TYPE_HANGUL
;
880 return WORD_TYPE_UNKNOWN
;
885 word_type_changed (WordType wtype
, gunichar c
, GUnicodeType ctype
, GUnicodeBreakType btype
)
889 // compare this character's word-type against the current word-type
890 if ((type
= word_type (ctype
, btype
)) == wtype
)
893 if (type
== WORD_TYPE_UNKNOWN
)
896 // word-types not identical... check if they are compatible
898 case WORD_TYPE_ALPHABETIC
:
899 return type
!= WORD_TYPE_NUMERIC
;
901 case WORD_TYPE_IDEOGRAPHIC
:
902 // this fixes drt #411 but breaks drt #208. I can't win.
903 return type
!= WORD_TYPE_ALPHABETIC
;
915 * @inend = end of word
916 * @max_width: max allowable width for a line
918 * Calculates the advance of the current word, breaking if needed.
920 * Returns: %true if the caller should create a new line for the
921 * remainder of the word or %false otherwise.
924 layout_word_wrap (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
)
926 GUnicodeBreakType btype
= G_UNICODE_BREAK_UNKNOWN
;
927 bool line_start
= word
->line_advance
== 0.0;
928 GlyphInfo
*prev
= word
->prev
;
929 WordBreakOpportunity op
;
930 const char *inptr
= in
;
945 g_array_set_size (word
->break_ops
, 0);
946 word
->type
= WORD_TYPE_UNKNOWN
;
950 d(printf ("layout_word_wrap():\n"));
951 d(debug
= g_string_new (""));
953 while (inptr
< inend
) {
955 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
956 // ignore invalid chars
960 if (UnicharIsLineBreak (c
)) {
965 // check the previous break-type
966 if (btype
== G_UNICODE_BREAK_CLOSE_PUNCTUATION
) {
967 // if anything other than an infix separator come after a close-punctuation, then the 'word' is done
968 btype
= g_unichar_break_type (c
);
969 if (btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
) {
973 } else if (btype
== G_UNICODE_BREAK_INFIX_SEPARATOR
) {
974 btype
= g_unichar_break_type (c
);
975 if (word
->type
== WORD_TYPE_NUMERIC
) {
976 // only accept numbers after the infix
977 if (btype
!= G_UNICODE_BREAK_NUMERIC
) {
981 } else if (word
->type
== WORD_TYPE_UNKNOWN
) {
982 // only accept alphanumerics after the infix
983 if (btype
!= G_UNICODE_BREAK_ALPHABETIC
&& btype
!= G_UNICODE_BREAK_NUMERIC
) {
990 } else if (btype
== G_UNICODE_BREAK_WORD_JOINER
) {
991 btype
= g_unichar_break_type (c
);
994 btype
= g_unichar_break_type (c
);
997 if (BreakSpace (c
, btype
)) {
1002 ctype
= g_unichar_type (c
);
1004 if (word
->type
== WORD_TYPE_UNKNOWN
) {
1005 // record our word-type
1006 word
->type
= word_type (ctype
, btype
);
1007 } else if (btype
== G_UNICODE_BREAK_OPEN_PUNCTUATION
) {
1008 // this is a good place to break
1011 } else if (word_type_changed (word
->type
, c
, ctype
, btype
)) {
1012 // changing word-types, don't continue
1017 d(g_string_append_unichar (debug
, c
));
1020 // a Combining Class of 0 means start of a new glyph
1021 if (glyphs
> 0 && unichar_combining_class (c
) != 0) {
1022 // this char gets combined with the previous glyph
1030 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1031 if (c
< 128 && isprint ((int) c
))
1032 printf ("\tunichar = %c; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", (char) c
,
1033 unicode_break_types
[btype
], new_glyph
? "true" : "false", unichar_combining_class (c
),
1034 unicode_char_types
[ctype
]);
1036 printf ("\tunichar = 0x%.4X; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", c
,
1037 unicode_break_types
[btype
], new_glyph
? "true" : "false", unichar_combining_class (c
),
1038 unicode_char_types
[ctype
]);
1042 if ((glyph
= word
->font
->GetGlyphInfo (c
))) {
1043 // calculate total glyph advance
1044 advance
= glyph
->metrics
.horiAdvance
;
1045 if ((prev
!= NULL
) && APPLY_KERNING (c
))
1046 advance
+= word
->font
->Kerning (prev
, glyph
);
1047 else if (glyph
->metrics
.horiBearingX
< 0)
1048 advance
-= glyph
->metrics
.horiBearingX
;
1050 word
->line_advance
+= advance
;
1051 word
->advance
+= advance
;
1058 op
.index
= glyph
? glyph
->index
: 0;
1059 op
.advance
= word
->advance
;
1060 op
.count
= word
->count
;
1065 g_array_remove_index (word
->break_ops
, word
->break_ops
->len
- 1);
1066 op
.advance
+= advance
;
1071 g_array_append_val (word
->break_ops
, op
);
1073 if (!isinf (max_width
) && word
->line_advance
> max_width
) {
1074 d(printf ("\tjust exceeded max width (%fpx): %s\n", max_width
, debug
->str
));
1081 d(g_string_free (debug
, true));
1082 word
->length
= (inptr
- in
);
1087 d(printf ("\tcollecting any/all decomposed chars and figuring out the break-type for the next glyph\n"));
1089 // pretend btype is SPACE here in case inptr is at the end of the run
1091 btype
= G_UNICODE_BREAK_SPACE
;
1093 // keep going until we reach a new distinct glyph. we also
1094 // need to know the btype of the char after the char that
1095 // exceeded the width limit.
1096 while (inptr
< inend
) {
1098 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
1099 // ignore invalid chars
1103 if (UnicharIsLineBreak (c
)) {
1104 btype
= G_UNICODE_BREAK_SPACE
;
1109 btype
= g_unichar_break_type (c
);
1110 if (BreakSpace (c
, btype
) || unichar_combining_class (c
) == 0) {
1115 d(g_string_append_unichar (debug
, c
));
1118 if ((glyph
= word
->font
->GetGlyphInfo (c
))) {
1119 // calculate total glyph advance
1120 advance
= glyph
->metrics
.horiAdvance
;
1121 if ((prev
!= NULL
) && APPLY_KERNING (c
))
1122 advance
+= word
->font
->Kerning (prev
, glyph
);
1123 else if (glyph
->metrics
.horiBearingX
< 0)
1124 advance
-= glyph
->metrics
.horiBearingX
;
1126 word
->line_advance
+= advance
;
1127 word
->advance
+= advance
;
1133 g_array_remove_index (word
->break_ops
, word
->break_ops
->len
- 1);
1134 op
.advance
+= advance
;
1137 g_array_append_val (word
->break_ops
, op
);
1140 d(printf ("\tok, at this point we have: %s\n", debug
->str
));
1141 d(printf ("\tnext break-type is %s\n", unicode_break_types
[btype
]));
1142 d(g_string_free (debug
, true));
1144 // at this point, we're going to break the word so we can reset kerning
1147 // we can't break any smaller than a single glyph
1148 if (line_start
&& glyphs
== 1) {
1149 d(printf ("\tsince this is the first glyph on the line, can't break any smaller than that...\n"));
1150 word
->length
= (inptr
- in
);
1157 // search backwards for the best break point
1158 d(printf ("\tscanning over %d break opportunities...\n", word
->break_ops
->len
));
1159 for (guint i
= word
->break_ops
->len
; i
> 0; i
--) {
1160 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 1);
1163 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1164 if (op
.c
< 128 && isprint ((int) op
.c
))
1165 printf ("\tunichar = %c; btype = %s; i = %d\n", (char) op
.c
, unicode_break_types
[op
.btype
], i
);
1167 printf ("\tunichar = 0x%.4X; btype = %s; i = %d\n", op
.c
, unicode_break_types
[op
.btype
], i
);
1172 case G_UNICODE_BREAK_BEFORE_AND_AFTER
:
1173 if (i
> 1 && i
== word
->break_ops
->len
) {
1174 // break after the previous glyph
1175 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1176 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1177 word
->length
= (op
.inptr
- in
);
1178 word
->advance
= op
.advance
;
1179 word
->count
= op
.count
;
1182 } else if (i
< word
->break_ops
->len
) {
1183 // break after this glyph
1184 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1185 word
->length
= (op
.inptr
- in
);
1186 word
->advance
= op
.advance
;
1187 word
->count
= op
.count
;
1191 case G_UNICODE_BREAK_NON_BREAKING_GLUE
:
1192 case G_UNICODE_BREAK_WORD_JOINER
:
1193 // cannot break before or after this character (unless forced)
1194 if (force
&& i
< word
->break_ops
->len
) {
1195 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1196 word
->length
= (op
.inptr
- in
);
1197 word
->advance
= op
.advance
;
1198 word
->count
= op
.count
;
1204 // skip past previous glyph
1205 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1209 case G_UNICODE_BREAK_INSEPARABLE
:
1210 // only restriction is no breaking between inseparables unless we have to
1211 if (line_start
&& i
< word
->break_ops
->len
) {
1212 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1213 word
->length
= (op
.inptr
- in
);
1214 word
->advance
= op
.advance
;
1215 word
->count
= op
.count
;
1220 case G_UNICODE_BREAK_BEFORE
:
1222 // break after the previous glyph
1223 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1224 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1225 word
->length
= (op
.inptr
- in
);
1226 word
->advance
= op
.advance
;
1227 word
->count
= op
.count
;
1232 case G_UNICODE_BREAK_CLOSE_PUNCTUATION
:
1233 if (i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)) {
1234 // we can safely break after this character
1235 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1236 word
->length
= (op
.inptr
- in
);
1237 word
->advance
= op
.advance
;
1238 word
->count
= op
.count
;
1243 if (i
> 1 && !force
) {
1244 // we can never break before a closing punctuation, so skip past prev char
1245 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1249 case G_UNICODE_BREAK_INFIX_SEPARATOR
:
1250 if (i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_NUMERIC
)) {
1251 // we can safely break after this character
1252 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1253 word
->length
= (op
.inptr
- in
);
1254 word
->advance
= op
.advance
;
1255 word
->count
= op
.count
;
1260 if (i
> 1 && !force
) {
1261 // we can never break before an infix, skip past prev char
1262 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1263 if (op
.btype
== G_UNICODE_BREAK_INFIX_SEPARATOR
||
1264 op
.btype
== G_UNICODE_BREAK_CLOSE_PUNCTUATION
) {
1265 // unless previous char is one of these special types...
1266 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 1);
1272 case G_UNICODE_BREAK_ALPHABETIC
:
1273 // only break if we have no choice...
1274 if ((line_start
|| fixed
|| force
) && i
< word
->break_ops
->len
) {
1275 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1276 word
->length
= (op
.inptr
- in
);
1277 word
->advance
= op
.advance
;
1278 word
->count
= op
.count
;
1283 case G_UNICODE_BREAK_IDEOGRAPHIC
:
1284 if (i
< word
->break_ops
->len
&& btype
!= G_UNICODE_BREAK_NON_STARTER
) {
1285 // we can safely break after this character
1286 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1287 word
->length
= (op
.inptr
- in
);
1288 word
->advance
= op
.advance
;
1289 word
->count
= op
.count
;
1294 case G_UNICODE_BREAK_NUMERIC
:
1295 // only break if we have no choice...
1296 if (line_start
&& i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)) {
1297 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1298 word
->length
= (op
.inptr
- in
);
1299 word
->advance
= op
.advance
;
1300 word
->count
= op
.count
;
1305 case G_UNICODE_BREAK_OPEN_PUNCTUATION
:
1306 case G_UNICODE_BREAK_COMBINING_MARK
:
1307 case G_UNICODE_BREAK_CONTINGENT
:
1308 case G_UNICODE_BREAK_AMBIGUOUS
:
1309 case G_UNICODE_BREAK_QUOTATION
:
1310 case G_UNICODE_BREAK_PREFIX
:
1311 // do not break after characters with these break-types (unless forced)
1312 if (force
&& i
< word
->break_ops
->len
) {
1313 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1314 word
->length
= (op
.inptr
- in
);
1315 word
->advance
= op
.advance
;
1316 word
->count
= op
.count
;
1322 d(printf ("Unhandled Unicode break-type: %s\n", unicode_break_types
[op
.btype
]));
1323 // fall thru to the "default" behavior
1325 #if GLIB_CHECK_VERSION (2,10,0)
1326 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE
:
1327 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE
:
1328 case G_UNICODE_BREAK_HANGUL_L_JAMO
:
1329 case G_UNICODE_BREAK_HANGUL_V_JAMO
:
1330 case G_UNICODE_BREAK_HANGUL_T_JAMO
:
1332 case G_UNICODE_BREAK_NON_STARTER
:
1333 case G_UNICODE_BREAK_EXCLAMATION
:
1334 case G_UNICODE_BREAK_MANDATORY
:
1335 case G_UNICODE_BREAK_NEXT_LINE
:
1336 case G_UNICODE_BREAK_UNKNOWN
:
1337 case G_UNICODE_BREAK_POSTFIX
:
1338 case G_UNICODE_BREAK_HYPHEN
:
1339 case G_UNICODE_BREAK_AFTER
:
1340 if (i
< word
->break_ops
->len
) {
1341 // we can safely break after this character
1342 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1343 word
->length
= (op
.inptr
- in
);
1344 word
->advance
= op
.advance
;
1345 word
->count
= op
.count
;
1356 if (line_start
&& !force
) {
1357 d(printf ("\tcouldn't find a good place to break but we must force a break, retrying...\n"));
1362 d(printf ("\tcouldn't find a good place to break, defaulting to breaking before the word start\n"));
1364 word
->advance
= 0.0;
1372 static const char *wrap_modes
[3] = {
1379 print_lines (GPtrArray
*lines
)
1381 TextLayoutLine
*line
;
1386 for (guint i
= 0; i
< lines
->len
; i
++) {
1387 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1389 printf ("Line (top=%f, height=%f, advance=%f, offset=%d, count=%d):\n", y
, line
->height
, line
->advance
, line
->offset
, line
->count
);
1390 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
1391 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
1393 text
= line
->layout
->GetText () + run
->start
;
1395 printf ("\tRun (advance=%f, start=%d, length=%d): \"", run
->advance
, run
->start
, run
->length
);
1396 for (const char *s
= text
; s
< text
+ run
->length
; s
++) {
1399 fputs ("\\r", stdout
);
1402 fputs ("\\n", stdout
);
1405 fputs ("\\t", stdout
);
1408 fputs ("\\\"", stdout
);
1424 TextLayout::LineHeightOverride ()
1426 if (isnan (line_height
))
1433 TextLayout::DescendOverride ()
1435 if (isnan (line_height
))
1436 return base_descent
;
1438 if (base_height
== 0.0)
1441 return line_height
* (base_descent
/ base_height
);
1444 static LayoutWordCallback layout_word_behavior
[] = {
1451 validate_attrs (List
*attributes
)
1453 TextLayoutAttributes
*attrs
;
1455 // if no attributes or first attribute doesn't start at 0, we can't layout any text
1456 if (!(attrs
= (TextLayoutAttributes
*) attributes
->First ()) || attrs
->start
!= 0)
1459 while (attrs
!= NULL
) {
1460 if (!attrs
->Font ()) {
1461 // we can't layout any text if any of the attributes
1462 // weren't able to load their font
1466 attrs
= (TextLayoutAttributes
*) attrs
->next
;
1473 TextLayout::Layout ()
1475 TextLayoutAttributes
*attrs
, *nattrs
;
1476 LayoutWordCallback layout_word
;
1477 const char *inptr
, *inend
;
1478 size_t n_bytes
, n_chars
;
1479 TextLayoutLine
*line
;
1488 if (!isnan (actual_width
))
1491 actual_height
= 0.0;
1497 if (!text
|| !validate_attrs (attributes
))
1500 d(printf ("TextLayout::Layout(): wrap mode = %s, wrapping to %f pixels\n", wrap_modes
[wrapping
], max_width
));
1502 if (wrapping
== TextWrappingWrap
)
1503 word
.break_ops
= g_array_new (false, false, sizeof (WordBreakOpportunity
));
1505 word
.break_ops
= NULL
;
1507 layout_word
= layout_word_behavior
[wrapping
];
1509 attrs
= (TextLayoutAttributes
*) attributes
->First ();
1510 line
= new TextLayoutLine (this, 0, 0);
1511 if (OverrideLineHeight ()) {
1512 line
->descend
= DescendOverride ();
1513 line
->height
= LineHeightOverride ();
1516 g_ptr_array_add (lines
, line
);
1520 nattrs
= (TextLayoutAttributes
*) attrs
->next
;
1521 inend
= text
+ (nattrs
? nattrs
->start
: length
);
1522 run
= new TextLayoutRun (line
, attrs
, inptr
- text
);
1523 g_ptr_array_add (line
->runs
, run
);
1525 word
.font
= font
= attrs
->Font ();
1527 //if (!OverrideLineHeight ()) {
1528 // line->descend = MIN (line->descend, font->Descender ());
1529 // line->height = MAX (line->height, font->Height ());
1532 if (*inptr
== '\0') {
1533 if (!OverrideLineHeight ()) {
1534 line
->descend
= MIN (line
->descend
, font
->Descender ());
1535 line
->height
= MAX (line
->height
, font
->Height ());
1538 actual_height
+= line
->height
;
1542 // layout until attrs change
1543 while (inptr
< inend
) {
1548 // layout until eoln or until we reach max_width
1549 while (inptr
< inend
) {
1550 // check for line-breaks
1551 if (IsLineBreak (inptr
, inend
- inptr
, &n_bytes
, &n_chars
)) {
1552 if (line
->length
== 0 && !OverrideLineHeight ()) {
1553 line
->descend
= font
->Descender ();
1554 line
->height
= font
->Height ();
1557 line
->length
+= n_bytes
;
1558 run
->length
+= n_bytes
;
1559 line
->count
+= n_chars
;
1560 run
->count
+= n_chars
;
1567 layout_word_init (&word
, line
->advance
, prev
);
1569 // lay out the next word
1570 if (layout_word (&word
, inptr
, inend
, max_width
)) {
1571 // force a line wrap...
1576 if (word
.length
> 0) {
1577 // append the word to the run/line
1578 if (!OverrideLineHeight ()) {
1579 line
->descend
= MIN (line
->descend
, font
->Descender ());
1580 line
->height
= MAX (line
->height
, font
->Height ());
1583 line
->advance
+= word
.advance
;
1584 run
->advance
+= word
.advance
;
1585 line
->width
= line
->advance
;
1586 line
->length
+= word
.length
;
1587 run
->length
+= word
.length
;
1588 line
->count
+= word
.count
;
1589 run
->count
+= word
.count
;
1591 offset
+= word
.count
;
1592 inptr
+= word
.length
;
1599 // now append any trailing lwsp
1600 layout_word_init (&word
, line
->advance
, prev
);
1602 layout_lwsp (&word
, inptr
, inend
);
1604 if (word
.length
> 0) {
1605 if (!OverrideLineHeight ()) {
1606 line
->descend
= MIN (line
->descend
, font
->Descender ());
1607 line
->height
= MAX (line
->height
, font
->Height ());
1610 line
->advance
+= word
.advance
;
1611 run
->advance
+= word
.advance
;
1612 line
->length
+= word
.length
;
1613 run
->length
+= word
.length
;
1614 line
->count
+= word
.count
;
1615 run
->count
+= word
.count
;
1617 offset
+= word
.count
;
1618 inptr
+= word
.length
;
1623 if (linebreak
|| wrapped
|| *inptr
== '\0') {
1624 // update actual width extents
1625 if (*inptr
== '\0') {
1626 // ActualWidth extents only include trailing lwsp on the last line
1627 actual_width
= MAX (actual_width
, line
->advance
);
1629 // not the last line, so don't include trailing lwsp
1630 actual_width
= MAX (actual_width
, line
->width
);
1633 // update actual height extents
1634 actual_height
+= line
->height
;
1636 if (linebreak
|| wrapped
) {
1637 // more text to layout... which means we'll need a new line
1638 line
= new TextLayoutLine (this, inptr
- text
, offset
);
1640 if (!OverrideLineHeight ()) {
1641 if (*inptr
== '\0') {
1642 line
->descend
= font
->Descender ();
1643 line
->height
= font
->Height ();
1646 line
->descend
= DescendOverride ();
1647 line
->height
= LineHeightOverride ();
1650 if (linebreak
&& *inptr
== '\0')
1651 actual_height
+= line
->height
;
1653 g_ptr_array_add (lines
, line
);
1657 if (inptr
< inend
) {
1658 // more text to layout with the current attrs...
1659 run
= new TextLayoutRun (line
, attrs
, inptr
- text
);
1660 g_ptr_array_add (line
->runs
, run
);
1666 } while (*inptr
!= '\0');
1668 if (word
.break_ops
!= NULL
)
1669 g_array_free (word
.break_ops
, true);
1674 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1675 print_lines (lines
);
1676 printf ("actualWidth = %f, actualHeight = %f\n\n", actual_width
, actual_height
);
1681 static inline TextLayoutGlyphCluster
*
1682 GenerateGlyphCluster (TextFont
*font
, GlyphInfo
**pglyph
, const char *text
, int start
, int length
)
1684 TextLayoutGlyphCluster
*cluster
= new TextLayoutGlyphCluster (start
, length
);
1685 const char *inend
= text
+ start
+ length
;
1686 const char *inptr
= text
+ start
;
1687 GlyphInfo
*prev
= *pglyph
;
1693 // set y0 to the baseline
1694 y0
= font
->Ascender ();
1698 // count how many path data items we'll need to allocate
1699 while (inptr
< inend
) {
1700 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
1703 if (UnicharIsLineBreak (c
))
1706 // treat tab as a single space
1710 if (!(glyph
= font
->GetGlyphInfo (c
)))
1714 size
+= glyph
->path
->cairo
.num_data
+ 1;
1718 // generate the cached path for the cluster
1719 cluster
->path
= moon_path_new (size
);
1720 inptr
= text
+ start
;
1722 while (inptr
< inend
) {
1723 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
1726 if (UnicharIsLineBreak (c
))
1729 // treat tab as a single space
1733 if (!(glyph
= font
->GetGlyphInfo (c
)))
1737 if (APPLY_KERNING (c
))
1738 x0
+= font
->Kerning (prev
, glyph
);
1739 else if (glyph
->metrics
.horiBearingX
< 0)
1740 x0
+= glyph
->metrics
.horiBearingX
;
1743 font
->AppendPath (cluster
->path
, glyph
, x0
, y0
);
1744 x0
+= glyph
->metrics
.horiAdvance
;
1747 if (!g_unichar_isspace (c
))
1751 moon_close_path (cluster
->path
);
1754 cluster
->uadvance
= x1
;
1755 cluster
->advance
= x0
;
1763 TextLayoutRun::GenerateCache ()
1765 int selection_length
= line
->layout
->GetSelectionLength ();
1766 int selection_start
= line
->layout
->GetSelectionStart ();
1767 const char *text
= line
->layout
->GetText ();
1768 const char *inend
= text
+ start
+ length
;
1769 const char *inptr
= text
+ start
;
1770 TextFont
*font
= attrs
->Font ();
1771 TextLayoutGlyphCluster
*cluster
;
1772 const char *selection_end
;
1773 GlyphInfo
*prev
= NULL
;
1776 // cache the glyph cluster leading up to the selection
1777 if (selection_length
== 0 || start
< selection_start
) {
1778 if (selection_length
> 0)
1779 len
= MIN (selection_start
- start
, length
);
1783 cluster
= GenerateGlyphCluster (font
, &prev
, text
, start
, len
);
1784 g_ptr_array_add (clusters
, cluster
);
1788 // cache the selected glyph cluster
1789 selection_end
= text
+ selection_start
+ selection_length
;
1790 if (inptr
< inend
&& inptr
< selection_end
) {
1791 len
= MIN (inend
, selection_end
) - inptr
;
1792 cluster
= GenerateGlyphCluster (font
, &prev
, text
, inptr
- text
, len
);
1793 g_ptr_array_add (clusters
, cluster
);
1794 cluster
->selected
= true;
1798 // cache the glyph cluster following the selection
1799 if (inptr
< inend
) {
1800 cluster
= GenerateGlyphCluster (font
, &prev
, text
, inptr
- text
, inend
- inptr
);
1801 g_ptr_array_add (clusters
, cluster
);
1807 TextLayoutGlyphCluster::Render (cairo_t
*cr
, const Point
&origin
, TextLayoutAttributes
*attrs
, const char *text
, double x
, double y
, bool uline_full
)
1809 TextFont
*font
= attrs
->Font ();
1810 const char *inend
, *prev
;
1817 if (length
== 0 || advance
== 0.0)
1820 // y is the baseline, set the origin to the top-left
1821 cairo_translate (cr
, x
, y
- font
->Ascender ());
1823 // set y0 to the baseline relative to the translation matrix
1824 y0
= font
->Ascender ();
1826 if (selected
&& (brush
= attrs
->Background (true))) {
1827 area
= Rect (origin
.x
, origin
.y
, advance
, font
->Height ());
1829 // extend the selection background by the width of a SPACE if it includes CRLF
1830 inend
= text
+ start
+ length
;
1831 if ((prev
= g_utf8_find_prev_char (text
+ start
, inend
)))
1832 c
= utf8_getc (&prev
, inend
- prev
);
1836 if (UnicharIsLineBreak (c
)) {
1837 if ((glyph
= font
->GetGlyphInfo (' ')))
1838 area
.width
+= glyph
->metrics
.horiAdvance
;
1841 // render the selection background
1842 brush
->SetupBrush (cr
, area
);
1843 cairo_new_path (cr
);
1844 cairo_rectangle (cr
, area
.x
, area
.y
, area
.width
, area
.height
);
1848 // setup the foreground brush
1849 if (!(brush
= attrs
->Foreground (selected
)))
1852 area
= Rect (origin
.x
, origin
.y
, advance
, font
->Height ());
1853 brush
->SetupBrush (cr
, area
);
1854 cairo_new_path (cr
);
1856 if (path
&& path
->cairo
.data
)
1857 cairo_append_path (cr
, &path
->cairo
);
1861 if (attrs
->IsUnderlined ()) {
1862 double thickness
= font
->UnderlineThickness ();
1863 double pos
= y0
+ font
->UnderlinePosition ();
1865 cairo_set_line_width (cr
, thickness
);
1867 cairo_new_path (cr
);
1868 Rect underline
= Rect (0.0, pos
- thickness
* 0.5, uline_full
? advance
: uadvance
, thickness
);
1869 underline
.Draw (cr
);
1876 TextLayoutRun::Render (cairo_t
*cr
, const Point
&origin
, double x
, double y
, bool is_last_run
)
1878 const char *text
= line
->layout
->GetText ();
1879 TextLayoutGlyphCluster
*cluster
;
1882 if (clusters
->len
== 0)
1885 for (guint i
= 0; i
< clusters
->len
; i
++) {
1886 cluster
= (TextLayoutGlyphCluster
*) clusters
->pdata
[i
];
1889 cluster
->Render (cr
, origin
, attrs
, text
, x0
, y
, is_last_run
&& ((i
+ 1) < clusters
->len
));
1892 x0
+= cluster
->advance
;
1897 TextLayoutLine::Render (cairo_t
*cr
, const Point
&origin
, double left
, double top
)
1902 // set y0 to the line's baseline (descend is a negative value)
1903 y0
= top
+ height
+ descend
;
1906 for (guint i
= 0; i
< runs
->len
; i
++) {
1907 run
= (TextLayoutRun
*) runs
->pdata
[i
];
1908 run
->Render (cr
, origin
, x0
, y0
, (i
+ 1) < runs
->len
);
1914 GetWidthConstraint (double avail_width
, double max_width
, double actual_width
)
1916 if (isinf (avail_width
)) {
1917 // find an upper width constraint
1918 if (isinf (max_width
))
1919 return actual_width
;
1921 return MIN (actual_width
, max_width
);
1928 TextLayout::HorizontalAlignment (double line_width
)
1933 switch (alignment
) {
1934 case TextAlignmentCenter
:
1935 width
= GetWidthConstraint (avail_width
, max_width
, actual_width
);
1936 if (line_width
< width
)
1937 deltax
= (width
- line_width
) / 2.0;
1941 case TextAlignmentRight
:
1942 width
= GetWidthConstraint (avail_width
, max_width
, actual_width
);
1943 if (line_width
< width
)
1944 deltax
= width
- line_width
;
1957 TextLayout::Render (cairo_t
*cr
, const Point
&origin
, const Point
&offset
)
1959 TextLayoutLine
*line
;
1966 for (guint i
= 0; i
< lines
->len
; i
++) {
1967 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1969 x
= offset
.x
+ HorizontalAlignment (line
->advance
);
1970 line
->Render (cr
, origin
, x
, y
);
1974 if (moonlight_flags
& RUNTIME_INIT_SHOW_TEXTBOXES
) {
1975 Rect rect
= GetRenderExtents ();
1980 cairo_set_source_rgba (cr
, 0.0, 1.0, 0.0, 1.0);
1981 cairo_set_line_width (cr
, 1);
1988 TextLayout::GetRenderExtents ()
1990 return Rect (HorizontalAlignment (actual_width
), 0.0, actual_width
, actual_height
);
1993 #ifdef USE_BINARY_SEARCH
1996 * @lo: the low bound
1997 * @hi: the high bound
1999 * Finds the midpoint between positive integer values, @lo and @hi.
2001 * Notes: Typically expressed as '(@lo + @hi) / 2', this is incorrect
2002 * when @lo and @hi are sufficiently large enough that combining them
2003 * would overflow their integer type. To work around this, we use the
2004 * formula, '@lo + ((@hi - @lo) / 2)', thus preventing this problem
2007 * Returns the midpoint between @lo and @hi (rounded down).
2009 #define MID(lo, hi) (lo + ((hi - lo) >> 1))
2012 TextLayout::GetLineFromY (const Point
&offset
, double y
, int *index
)
2014 register guint lo
, hi
;
2015 TextLayoutLine
*line
;
2019 if (lines
->len
== 0)
2022 lo
= 0, hi
= lines
->len
;
2028 line
= (TextLayoutLine
*) lines
->pdata
[m
];
2030 if (m
> 0 && y0
< line
->top
) {
2031 // y is on some line above us
2033 } else if (y0
> line
->top
+ line
->height
) {
2034 // y is on some line below us
2038 // y is on this line
2050 #else /* linear search */
2052 TextLayout::GetLineFromY (const Point
&offset
, double y
, int *index
)
2054 TextLayoutLine
*line
= NULL
;
2057 //printf ("TextLayout::GetLineFromY (%.2g)\n", y);
2061 for (guint i
= 0; i
< lines
->len
; i
++) {
2062 line
= (TextLayoutLine
*) lines
->pdata
[i
];
2064 // set y1 the top of the next line
2065 y1
= y0
+ line
->height
;
2068 // we found the line that the point is located on
2083 TextLayout::GetLineFromIndex (int index
)
2085 if (index
>= (int) lines
->len
|| index
< 0)
2088 return (TextLayoutLine
*) lines
->pdata
[index
];
2092 TextLayoutLine::GetCursorFromX (const Point
&offset
, double x
)
2094 const char *text
, *inend
, *ch
, *inptr
;
2095 TextLayoutRun
*run
= NULL
;
2096 GlyphInfo
*prev
, *glyph
;
2104 // adjust x0 for horizontal alignment
2105 x0
= offset
.x
+ layout
->HorizontalAlignment (advance
);
2107 text
= layout
->GetText ();
2108 inptr
= text
+ start
;
2109 cursor
= this->offset
;
2112 for (i
= 0; i
< runs
->len
; i
++) {
2113 run
= (TextLayoutRun
*) runs
->pdata
[i
];
2115 if (x
< x0
+ run
->advance
) {
2116 // x is in somewhere inside this run
2120 // x is beyond this run
2121 cursor
+= run
->count
;
2122 inptr
+= run
->length
;
2128 inptr
= text
+ run
->start
;
2129 inend
= inptr
+ run
->length
;
2130 font
= run
->attrs
->Font ();
2132 while (inptr
< inend
) {
2134 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
2137 if (UnicharIsLineBreak (c
)) {
2144 // we treat tabs as a single space
2148 if (!(glyph
= font
->GetGlyphInfo (c
)))
2151 if ((prev
!= NULL
) && APPLY_KERNING (c
))
2152 x0
+= font
->Kerning (prev
, glyph
);
2153 else if (glyph
->metrics
.horiBearingX
< 0)
2154 x0
+= glyph
->metrics
.horiBearingX
;
2156 // calculate midpoint of the character
2157 m
= glyph
->metrics
.horiAdvance
/ 2.0;
2159 // if x is <= the midpoint, then the cursor is
2160 // considered to be at the start of this character.
2167 x0
+= glyph
->metrics
.horiAdvance
;
2171 // x is beyond the end of the last run
2172 run
= (TextLayoutRun
*) runs
->pdata
[i
- 1];
2173 inend
= text
+ run
->start
+ run
->length
;
2174 inptr
= text
+ run
->start
;
2176 if ((ch
= g_utf8_find_prev_char (inptr
, inend
)))
2177 c
= utf8_getc (&ch
, inend
- ch
);
2185 if (inend
> inptr
&& inend
[-1] == '\r') {
2189 } else if (UnicharIsLineBreak (c
)) {
2199 TextLayout::GetCursorFromXY (const Point
&offset
, double x
, double y
)
2201 TextLayoutLine
*line
;
2203 //printf ("TextLayout::GetCursorFromXY (%.2g, %.2g)\n", x, y);
2208 if (!(line
= GetLineFromY (offset
, y
)))
2211 return line
->GetCursorFromX (offset
, x
);
2215 TextLayout::GetCursor (const Point
&offset
, int index
)
2217 const char *inptr
, *inend
, *pchar
;
2218 double height
, x0
, y0
, y1
;
2219 GlyphInfo
*prev
, *glyph
;
2220 TextLayoutLine
*line
;
2226 //printf ("TextLayout::GetCursor (%d)\n", index);
2233 for (guint i
= 0; i
< lines
->len
; i
++) {
2234 line
= (TextLayoutLine
*) lines
->pdata
[i
];
2235 inend
= text
+ line
->start
+ line
->length
;
2237 // adjust x0 for horizontal alignment
2238 x0
= offset
.x
+ HorizontalAlignment (line
->advance
);
2240 // set y1 to the baseline (descend is a negative value)
2241 y1
= y0
+ line
->height
+ line
->descend
;
2242 height
= line
->height
;
2244 //printf ("\tline: left=%.2f, top=%.2f, baseline=%.2f, start index=%d\n", x0, y0, y1, line->offset);
2246 if (index
>= cursor
+ line
->count
) {
2247 // maybe the cursor is on the next line...
2248 if ((i
+ 1) == lines
->len
) {
2249 // we are on the last line... get the previous unichar
2250 inptr
= text
+ line
->start
;
2251 inend
= inptr
+ line
->length
;
2253 if ((pchar
= g_utf8_find_prev_char (text
+ line
->start
, inend
)))
2254 c
= utf8_getc (&pchar
, inend
- pchar
);
2258 if (UnicharIsLineBreak (c
)) {
2259 // cursor is on the next line by itself
2260 x0
= offset
.x
+ HorizontalAlignment (0.0);
2263 // cursor at the end of the last line
2264 x0
+= line
->advance
;
2270 cursor
+= line
->count
;
2275 // cursor is on this line...
2276 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
2277 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
2278 inend
= text
+ run
->start
+ run
->length
;
2280 if (index
>= cursor
+ run
->count
) {
2281 // maybe the cursor is in the next run...
2282 cursor
+= run
->count
;
2287 // cursor is in this run...
2288 font
= run
->attrs
->Font ();
2289 inptr
= text
+ run
->start
;
2292 while (cursor
< index
) {
2293 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
2298 // we treat tabs as a single space
2302 if (!(glyph
= font
->GetGlyphInfo (c
)))
2305 if ((prev
!= NULL
) && APPLY_KERNING (c
))
2306 x0
+= font
->Kerning (prev
, glyph
);
2307 else if (glyph
->metrics
.horiBearingX
< 0)
2308 x0
+= glyph
->metrics
.horiBearingX
;
2310 x0
+= glyph
->metrics
.horiAdvance
;
2320 return Rect (x0
, y0
, 1.0, height
);