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:
618 * Gets the actual width and height extents required for rendering the
622 TextLayout::GetActualExtents (double *width
, double *height
)
624 *height
= actual_height
;
625 *width
= actual_width
;
630 unichar_combining_class (gunichar c
)
632 #if GLIB_CHECK_VERSION (2,14,0)
633 if (glib_check_version (2,14,0))
634 return g_unichar_combining_class (c
);
643 WORD_TYPE_ALPHABETIC
,
644 WORD_TYPE_IDEOGRAPHIC
,
645 WORD_TYPE_INSEPARABLE
,
650 struct WordBreakOpportunity
{
651 GUnicodeBreakType btype
;
661 GArray
*break_ops
; // TextWrappingWrap only
670 GlyphInfo
*prev
; // previous glyph; used for kerning
673 double advance
; // the advance-width of the 'word'
674 int length
; // length of the word in bytes
675 int count
; // length of the word in unichars
678 typedef bool (* LayoutWordCallback
) (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
);
681 IsLineBreak (const char *text
, size_t left
, size_t *n_bytes
, size_t *n_chars
)
683 const char *inptr
= text
;
686 if ((c
= utf8_getc (&inptr
, left
)) == (gunichar
) -1)
689 if (!UnicharIsLineBreak (c
))
692 if (c
== '\r' && *inptr
== '\n') {
696 *n_bytes
= (size_t) (inptr
- text
);
704 layout_word_init (LayoutWord
*word
, double line_advance
, GlyphInfo
*prev
)
706 word
->line_advance
= line_advance
;
712 * @word: #LayoutWord context
714 * @inend: end of input text
716 * Measures a word containing nothing but LWSP.
719 layout_lwsp (LayoutWord
*word
, const char *in
, const char *inend
)
721 GlyphInfo
*prev
= word
->prev
;
722 GUnicodeBreakType btype
;
723 const char *inptr
= in
;
729 d(printf ("layout_lwsp():\n"));
734 while (inptr
< inend
) {
736 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
737 // ignore invalid chars
741 if (UnicharIsLineBreak (c
)) {
746 btype
= g_unichar_break_type (c
);
747 if (!BreakSpace (c
, btype
)) {
753 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
754 if (c
< 128 && isprint ((int) c
))
755 printf ("\tunichar = %c; btype = %s, ctype = %s\n", (char) c
,
756 unicode_break_types
[btype
], unicode_char_types
[g_unichar_type (c
)]);
758 printf ("\tunichar = 0x%.4X; btype = %s, ctype = %s\n", c
,
759 unicode_break_types
[btype
], unicode_char_types
[g_unichar_type (c
)]);
765 // treat tab as a single space
769 // ignore glyphs the font doesn't contain...
770 if (!(glyph
= word
->font
->GetGlyphInfo (c
)))
773 // calculate total glyph advance
774 advance
= glyph
->metrics
.horiAdvance
;
775 if ((prev
!= NULL
) && APPLY_KERNING (c
))
776 advance
+= word
->font
->Kerning (prev
, glyph
);
777 else if (glyph
->metrics
.horiBearingX
< 0)
778 advance
-= glyph
->metrics
.horiBearingX
;
780 word
->line_advance
+= advance
;
781 word
->advance
+= advance
;
785 word
->length
= (inptr
- in
);
790 * layout_word_nowrap:
791 * @word: #LayoutWord context
793 * @inend = end of input text
794 * @max_width: max allowable width for a line
796 * Calculates the advance of the current word.
798 * Returns: %true if the caller should create a new line for the
799 * remainder of the word or %false otherwise.
802 layout_word_nowrap (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
)
804 GUnicodeBreakType btype
= G_UNICODE_BREAK_UNKNOWN
;
805 GlyphInfo
*prev
= word
->prev
;
806 const char *inptr
= in
;
812 // Note: since we don't ever need to wrap, no need to keep track of word-type
813 word
->type
= WORD_TYPE_UNKNOWN
;
817 while (inptr
< inend
) {
819 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
820 // ignore invalid chars
824 if (UnicharIsLineBreak (c
)) {
829 if (btype
== G_UNICODE_BREAK_COMBINING_MARK
) {
830 // ignore zero-width spaces
831 if ((btype
= g_unichar_break_type (c
)) == G_UNICODE_BREAK_ZERO_WIDTH_SPACE
)
832 btype
= G_UNICODE_BREAK_COMBINING_MARK
;
834 btype
= g_unichar_break_type (c
);
837 if (BreakSpace (c
, btype
)) {
844 // ignore glyphs the font doesn't contain...
845 if (!(glyph
= word
->font
->GetGlyphInfo (c
)))
848 // calculate total glyph advance
849 advance
= glyph
->metrics
.horiAdvance
;
850 if ((prev
!= NULL
) && APPLY_KERNING (c
))
851 advance
+= word
->font
->Kerning (prev
, glyph
);
852 else if (glyph
->metrics
.horiBearingX
< 0)
853 advance
-= glyph
->metrics
.horiBearingX
;
855 word
->line_advance
+= advance
;
856 word
->advance
+= advance
;
860 word
->length
= (inptr
- in
);
867 word_type (GUnicodeType ctype
, GUnicodeBreakType btype
)
870 case G_UNICODE_BREAK_ALPHABETIC
:
871 return WORD_TYPE_ALPHABETIC
;
872 case G_UNICODE_BREAK_IDEOGRAPHIC
:
873 return WORD_TYPE_IDEOGRAPHIC
;
874 case G_UNICODE_BREAK_NUMERIC
:
875 if (ctype
== G_UNICODE_OTHER_PUNCTUATION
)
876 return WORD_TYPE_UNKNOWN
;
877 return WORD_TYPE_NUMERIC
;
878 case G_UNICODE_BREAK_INSEPARABLE
:
879 return WORD_TYPE_INSEPARABLE
;
880 #if GLIB_CHECK_VERSION (2,10,0)
881 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE
:
882 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE
:
883 case G_UNICODE_BREAK_HANGUL_L_JAMO
:
884 case G_UNICODE_BREAK_HANGUL_V_JAMO
:
885 case G_UNICODE_BREAK_HANGUL_T_JAMO
:
886 return WORD_TYPE_HANGUL
;
889 return WORD_TYPE_UNKNOWN
;
894 word_type_changed (WordType wtype
, gunichar c
, GUnicodeType ctype
, GUnicodeBreakType btype
)
898 // compare this character's word-type against the current word-type
899 if ((type
= word_type (ctype
, btype
)) == wtype
)
902 if (type
== WORD_TYPE_UNKNOWN
)
905 // word-types not identical... check if they are compatible
907 case WORD_TYPE_ALPHABETIC
:
908 return type
!= WORD_TYPE_NUMERIC
;
910 case WORD_TYPE_IDEOGRAPHIC
:
911 // this fixes drt #411 but breaks drt #208. I can't win.
912 return type
!= WORD_TYPE_ALPHABETIC
;
924 * @inend = end of word
925 * @max_width: max allowable width for a line
927 * Calculates the advance of the current word, breaking if needed.
929 * Returns: %true if the caller should create a new line for the
930 * remainder of the word or %false otherwise.
933 layout_word_wrap (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
)
935 GUnicodeBreakType btype
= G_UNICODE_BREAK_UNKNOWN
;
936 bool line_start
= word
->line_advance
== 0.0;
937 GlyphInfo
*prev
= word
->prev
;
938 WordBreakOpportunity op
;
939 const char *inptr
= in
;
954 g_array_set_size (word
->break_ops
, 0);
955 word
->type
= WORD_TYPE_UNKNOWN
;
959 d(printf ("layout_word_wrap():\n"));
960 d(debug
= g_string_new (""));
962 while (inptr
< inend
) {
964 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
965 // ignore invalid chars
969 if (UnicharIsLineBreak (c
)) {
974 // check the previous break-type
975 if (btype
== G_UNICODE_BREAK_CLOSE_PUNCTUATION
) {
976 // if anything other than an infix separator come after a close-punctuation, then the 'word' is done
977 btype
= g_unichar_break_type (c
);
978 if (btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
) {
982 } else if (btype
== G_UNICODE_BREAK_INFIX_SEPARATOR
) {
983 btype
= g_unichar_break_type (c
);
984 if (word
->type
== WORD_TYPE_NUMERIC
) {
985 // only accept numbers after the infix
986 if (btype
!= G_UNICODE_BREAK_NUMERIC
) {
990 } else if (word
->type
== WORD_TYPE_UNKNOWN
) {
991 // only accept alphanumerics after the infix
992 if (btype
!= G_UNICODE_BREAK_ALPHABETIC
&& btype
!= G_UNICODE_BREAK_NUMERIC
) {
999 } else if (btype
== G_UNICODE_BREAK_WORD_JOINER
) {
1000 btype
= g_unichar_break_type (c
);
1003 btype
= g_unichar_break_type (c
);
1006 if (BreakSpace (c
, btype
)) {
1011 ctype
= g_unichar_type (c
);
1013 if (word
->type
== WORD_TYPE_UNKNOWN
) {
1014 // record our word-type
1015 word
->type
= word_type (ctype
, btype
);
1016 } else if (btype
== G_UNICODE_BREAK_OPEN_PUNCTUATION
) {
1017 // this is a good place to break
1020 } else if (word_type_changed (word
->type
, c
, ctype
, btype
)) {
1021 // changing word-types, don't continue
1026 d(g_string_append_unichar (debug
, c
));
1029 // a Combining Class of 0 means start of a new glyph
1030 if (glyphs
> 0 && unichar_combining_class (c
) != 0) {
1031 // this char gets combined with the previous glyph
1039 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1040 if (c
< 128 && isprint ((int) c
))
1041 printf ("\tunichar = %c; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", (char) c
,
1042 unicode_break_types
[btype
], new_glyph
? "true" : "false", unichar_combining_class (c
),
1043 unicode_char_types
[ctype
]);
1045 printf ("\tunichar = 0x%.4X; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", c
,
1046 unicode_break_types
[btype
], new_glyph
? "true" : "false", unichar_combining_class (c
),
1047 unicode_char_types
[ctype
]);
1051 if ((glyph
= word
->font
->GetGlyphInfo (c
))) {
1052 // calculate total glyph advance
1053 advance
= glyph
->metrics
.horiAdvance
;
1054 if ((prev
!= NULL
) && APPLY_KERNING (c
))
1055 advance
+= word
->font
->Kerning (prev
, glyph
);
1056 else if (glyph
->metrics
.horiBearingX
< 0)
1057 advance
-= glyph
->metrics
.horiBearingX
;
1059 word
->line_advance
+= advance
;
1060 word
->advance
+= advance
;
1067 op
.index
= glyph
? glyph
->index
: 0;
1068 op
.advance
= word
->advance
;
1069 op
.count
= word
->count
;
1074 g_array_remove_index (word
->break_ops
, word
->break_ops
->len
- 1);
1075 op
.advance
+= advance
;
1080 g_array_append_val (word
->break_ops
, op
);
1082 if (!isinf (max_width
) && word
->line_advance
> max_width
) {
1083 d(printf ("\tjust exceeded max width (%fpx): %s\n", max_width
, debug
->str
));
1090 d(g_string_free (debug
, true));
1091 word
->length
= (inptr
- in
);
1096 d(printf ("\tcollecting any/all decomposed chars and figuring out the break-type for the next glyph\n"));
1098 // pretend btype is SPACE here in case inptr is at the end of the run
1100 btype
= G_UNICODE_BREAK_SPACE
;
1102 // keep going until we reach a new distinct glyph. we also
1103 // need to know the btype of the char after the char that
1104 // exceeded the width limit.
1105 while (inptr
< inend
) {
1107 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
1108 // ignore invalid chars
1112 if (UnicharIsLineBreak (c
)) {
1113 btype
= G_UNICODE_BREAK_SPACE
;
1118 btype
= g_unichar_break_type (c
);
1119 if (BreakSpace (c
, btype
) || unichar_combining_class (c
) == 0) {
1124 d(g_string_append_unichar (debug
, c
));
1127 if ((glyph
= word
->font
->GetGlyphInfo (c
))) {
1128 // calculate total glyph advance
1129 advance
= glyph
->metrics
.horiAdvance
;
1130 if ((prev
!= NULL
) && APPLY_KERNING (c
))
1131 advance
+= word
->font
->Kerning (prev
, glyph
);
1132 else if (glyph
->metrics
.horiBearingX
< 0)
1133 advance
-= glyph
->metrics
.horiBearingX
;
1135 word
->line_advance
+= advance
;
1136 word
->advance
+= advance
;
1142 g_array_remove_index (word
->break_ops
, word
->break_ops
->len
- 1);
1143 op
.advance
+= advance
;
1146 g_array_append_val (word
->break_ops
, op
);
1149 d(printf ("\tok, at this point we have: %s\n", debug
->str
));
1150 d(printf ("\tnext break-type is %s\n", unicode_break_types
[btype
]));
1151 d(g_string_free (debug
, true));
1153 // at this point, we're going to break the word so we can reset kerning
1156 // we can't break any smaller than a single glyph
1157 if (line_start
&& glyphs
== 1) {
1158 d(printf ("\tsince this is the first glyph on the line, can't break any smaller than that...\n"));
1159 word
->length
= (inptr
- in
);
1166 // search backwards for the best break point
1167 d(printf ("\tscanning over %d break opportunities...\n", word
->break_ops
->len
));
1168 for (guint i
= word
->break_ops
->len
; i
> 0; i
--) {
1169 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 1);
1172 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1173 if (op
.c
< 128 && isprint ((int) op
.c
))
1174 printf ("\tunichar = %c; btype = %s; i = %d\n", (char) op
.c
, unicode_break_types
[op
.btype
], i
);
1176 printf ("\tunichar = 0x%.4X; btype = %s; i = %d\n", op
.c
, unicode_break_types
[op
.btype
], i
);
1181 case G_UNICODE_BREAK_BEFORE_AND_AFTER
:
1182 if (i
> 1 && i
== word
->break_ops
->len
) {
1183 // break after the previous glyph
1184 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1185 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1186 word
->length
= (op
.inptr
- in
);
1187 word
->advance
= op
.advance
;
1188 word
->count
= op
.count
;
1191 } else if (i
< word
->break_ops
->len
) {
1192 // break after this glyph
1193 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1194 word
->length
= (op
.inptr
- in
);
1195 word
->advance
= op
.advance
;
1196 word
->count
= op
.count
;
1200 case G_UNICODE_BREAK_NON_BREAKING_GLUE
:
1201 case G_UNICODE_BREAK_WORD_JOINER
:
1202 // cannot break before or after this character (unless forced)
1203 if (force
&& i
< word
->break_ops
->len
) {
1204 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1205 word
->length
= (op
.inptr
- in
);
1206 word
->advance
= op
.advance
;
1207 word
->count
= op
.count
;
1213 // skip past previous glyph
1214 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1218 case G_UNICODE_BREAK_INSEPARABLE
:
1219 // only restriction is no breaking between inseparables unless we have to
1220 if (line_start
&& i
< word
->break_ops
->len
) {
1221 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1222 word
->length
= (op
.inptr
- in
);
1223 word
->advance
= op
.advance
;
1224 word
->count
= op
.count
;
1229 case G_UNICODE_BREAK_BEFORE
:
1231 // break after the previous glyph
1232 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1233 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1234 word
->length
= (op
.inptr
- in
);
1235 word
->advance
= op
.advance
;
1236 word
->count
= op
.count
;
1241 case G_UNICODE_BREAK_CLOSE_PUNCTUATION
:
1242 if (i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)) {
1243 // we can safely break after this character
1244 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1245 word
->length
= (op
.inptr
- in
);
1246 word
->advance
= op
.advance
;
1247 word
->count
= op
.count
;
1252 if (i
> 1 && !force
) {
1253 // we can never break before a closing punctuation, so skip past prev char
1254 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1258 case G_UNICODE_BREAK_INFIX_SEPARATOR
:
1259 if (i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_NUMERIC
)) {
1260 // we can safely break after this character
1261 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1262 word
->length
= (op
.inptr
- in
);
1263 word
->advance
= op
.advance
;
1264 word
->count
= op
.count
;
1269 if (i
> 1 && !force
) {
1270 // we can never break before an infix, skip past prev char
1271 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1272 if (op
.btype
== G_UNICODE_BREAK_INFIX_SEPARATOR
||
1273 op
.btype
== G_UNICODE_BREAK_CLOSE_PUNCTUATION
) {
1274 // unless previous char is one of these special types...
1275 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 1);
1281 case G_UNICODE_BREAK_ALPHABETIC
:
1282 // only break if we have no choice...
1283 if ((line_start
|| fixed
|| force
) && i
< word
->break_ops
->len
) {
1284 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1285 word
->length
= (op
.inptr
- in
);
1286 word
->advance
= op
.advance
;
1287 word
->count
= op
.count
;
1292 case G_UNICODE_BREAK_IDEOGRAPHIC
:
1293 if (i
< word
->break_ops
->len
&& btype
!= G_UNICODE_BREAK_NON_STARTER
) {
1294 // we can safely break after this character
1295 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1296 word
->length
= (op
.inptr
- in
);
1297 word
->advance
= op
.advance
;
1298 word
->count
= op
.count
;
1303 case G_UNICODE_BREAK_NUMERIC
:
1304 // only break if we have no choice...
1305 if (line_start
&& i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)) {
1306 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1307 word
->length
= (op
.inptr
- in
);
1308 word
->advance
= op
.advance
;
1309 word
->count
= op
.count
;
1314 case G_UNICODE_BREAK_OPEN_PUNCTUATION
:
1315 case G_UNICODE_BREAK_COMBINING_MARK
:
1316 case G_UNICODE_BREAK_CONTINGENT
:
1317 case G_UNICODE_BREAK_AMBIGUOUS
:
1318 case G_UNICODE_BREAK_QUOTATION
:
1319 case G_UNICODE_BREAK_PREFIX
:
1320 // do not break after characters with these break-types (unless forced)
1321 if (force
&& i
< word
->break_ops
->len
) {
1322 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1323 word
->length
= (op
.inptr
- in
);
1324 word
->advance
= op
.advance
;
1325 word
->count
= op
.count
;
1331 d(printf ("Unhandled Unicode break-type: %s\n", unicode_break_types
[op
.btype
]));
1332 // fall thru to the "default" behavior
1334 #if GLIB_CHECK_VERSION (2,10,0)
1335 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE
:
1336 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE
:
1337 case G_UNICODE_BREAK_HANGUL_L_JAMO
:
1338 case G_UNICODE_BREAK_HANGUL_V_JAMO
:
1339 case G_UNICODE_BREAK_HANGUL_T_JAMO
:
1341 case G_UNICODE_BREAK_NON_STARTER
:
1342 case G_UNICODE_BREAK_EXCLAMATION
:
1343 case G_UNICODE_BREAK_MANDATORY
:
1344 case G_UNICODE_BREAK_NEXT_LINE
:
1345 case G_UNICODE_BREAK_UNKNOWN
:
1346 case G_UNICODE_BREAK_POSTFIX
:
1347 case G_UNICODE_BREAK_HYPHEN
:
1348 case G_UNICODE_BREAK_AFTER
:
1349 if (i
< word
->break_ops
->len
) {
1350 // we can safely break after this character
1351 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1352 word
->length
= (op
.inptr
- in
);
1353 word
->advance
= op
.advance
;
1354 word
->count
= op
.count
;
1365 if (line_start
&& !force
) {
1366 d(printf ("\tcouldn't find a good place to break but we must force a break, retrying...\n"));
1371 d(printf ("\tcouldn't find a good place to break, defaulting to breaking before the word start\n"));
1373 word
->advance
= 0.0;
1381 static const char *wrap_modes
[3] = {
1388 print_lines (GPtrArray
*lines
)
1390 TextLayoutLine
*line
;
1395 for (guint i
= 0; i
< lines
->len
; i
++) {
1396 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1398 printf ("Line (top=%f, height=%f, advance=%f, offset=%d, count=%d):\n", y
, line
->height
, line
->advance
, line
->offset
, line
->count
);
1399 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
1400 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
1402 text
= line
->layout
->GetText () + run
->start
;
1404 printf ("\tRun (advance=%f, start=%d, length=%d): \"", run
->advance
, run
->start
, run
->length
);
1405 for (const char *s
= text
; s
< text
+ run
->length
; s
++) {
1408 fputs ("\\r", stdout
);
1411 fputs ("\\n", stdout
);
1414 fputs ("\\t", stdout
);
1417 fputs ("\\\"", stdout
);
1433 TextLayout::LineHeightOverride ()
1435 if (isnan (line_height
))
1442 TextLayout::DescendOverride ()
1444 if (isnan (line_height
))
1445 return base_descent
;
1447 if (base_height
== 0.0)
1450 return line_height
* (base_descent
/ base_height
);
1453 static LayoutWordCallback layout_word_behavior
[] = {
1460 validate_attrs (List
*attributes
)
1462 TextLayoutAttributes
*attrs
;
1464 // if no attributes or first attribute doesn't start at 0, we can't layout any text
1465 if (!(attrs
= (TextLayoutAttributes
*) attributes
->First ()) || attrs
->start
!= 0)
1468 while (attrs
!= NULL
) {
1469 if (!attrs
->Font ()) {
1470 // we can't layout any text if any of the attributes
1471 // weren't able to load their font
1475 attrs
= (TextLayoutAttributes
*) attrs
->next
;
1482 TextLayout::Layout ()
1484 TextLayoutAttributes
*attrs
, *nattrs
;
1485 LayoutWordCallback layout_word
;
1486 const char *inptr
, *inend
;
1487 size_t n_bytes
, n_chars
;
1488 TextLayoutLine
*line
;
1497 if (!isnan (actual_width
))
1500 actual_height
= 0.0;
1506 if (!text
|| !validate_attrs (attributes
))
1509 d(printf ("TextLayout::Layout(): wrap mode = %s, wrapping to %f pixels\n", wrap_modes
[wrapping
], max_width
));
1511 if (wrapping
== TextWrappingWrap
)
1512 word
.break_ops
= g_array_new (false, false, sizeof (WordBreakOpportunity
));
1514 word
.break_ops
= NULL
;
1516 layout_word
= layout_word_behavior
[wrapping
];
1518 attrs
= (TextLayoutAttributes
*) attributes
->First ();
1519 line
= new TextLayoutLine (this, 0, 0);
1520 if (OverrideLineHeight ()) {
1521 line
->descend
= DescendOverride ();
1522 line
->height
= LineHeightOverride ();
1525 g_ptr_array_add (lines
, line
);
1529 nattrs
= (TextLayoutAttributes
*) attrs
->next
;
1530 inend
= text
+ (nattrs
? nattrs
->start
: length
);
1531 run
= new TextLayoutRun (line
, attrs
, inptr
- text
);
1532 g_ptr_array_add (line
->runs
, run
);
1534 word
.font
= font
= attrs
->Font ();
1536 //if (!OverrideLineHeight ()) {
1537 // line->descend = MIN (line->descend, font->Descender ());
1538 // line->height = MAX (line->height, font->Height ());
1541 if (*inptr
== '\0') {
1542 if (!OverrideLineHeight ()) {
1543 line
->descend
= MIN (line
->descend
, font
->Descender ());
1544 line
->height
= MAX (line
->height
, font
->Height ());
1547 actual_height
+= line
->height
;
1551 // layout until attrs change
1552 while (inptr
< inend
) {
1557 // layout until eoln or until we reach max_width
1558 while (inptr
< inend
) {
1559 // check for line-breaks
1560 if (IsLineBreak (inptr
, inend
- inptr
, &n_bytes
, &n_chars
)) {
1561 if (line
->length
== 0 && !OverrideLineHeight ()) {
1562 line
->descend
= font
->Descender ();
1563 line
->height
= font
->Height ();
1566 line
->length
+= n_bytes
;
1567 run
->length
+= n_bytes
;
1568 line
->count
+= n_chars
;
1569 run
->count
+= n_chars
;
1576 layout_word_init (&word
, line
->advance
, prev
);
1578 // lay out the next word
1579 if (layout_word (&word
, inptr
, inend
, max_width
)) {
1580 // force a line wrap...
1585 if (word
.length
> 0) {
1586 // append the word to the run/line
1587 if (!OverrideLineHeight ()) {
1588 line
->descend
= MIN (line
->descend
, font
->Descender ());
1589 line
->height
= MAX (line
->height
, font
->Height ());
1592 line
->advance
+= word
.advance
;
1593 run
->advance
+= word
.advance
;
1594 line
->width
= line
->advance
;
1595 line
->length
+= word
.length
;
1596 run
->length
+= word
.length
;
1597 line
->count
+= word
.count
;
1598 run
->count
+= word
.count
;
1600 offset
+= word
.count
;
1601 inptr
+= word
.length
;
1608 // now append any trailing lwsp
1609 layout_word_init (&word
, line
->advance
, prev
);
1611 layout_lwsp (&word
, inptr
, inend
);
1613 if (word
.length
> 0) {
1614 if (!OverrideLineHeight ()) {
1615 line
->descend
= MIN (line
->descend
, font
->Descender ());
1616 line
->height
= MAX (line
->height
, font
->Height ());
1619 line
->advance
+= word
.advance
;
1620 run
->advance
+= word
.advance
;
1621 line
->length
+= word
.length
;
1622 run
->length
+= word
.length
;
1623 line
->count
+= word
.count
;
1624 run
->count
+= word
.count
;
1626 offset
+= word
.count
;
1627 inptr
+= word
.length
;
1632 if (linebreak
|| wrapped
|| *inptr
== '\0') {
1633 // update actual width extents
1634 if (*inptr
== '\0') {
1635 // ActualWidth extents only include trailing lwsp on the last line
1636 actual_width
= MAX (actual_width
, line
->advance
);
1638 // not the last line, so don't include trailing lwsp
1639 actual_width
= MAX (actual_width
, line
->width
);
1642 // update actual height extents
1643 actual_height
+= line
->height
;
1645 if (linebreak
|| wrapped
) {
1646 // more text to layout... which means we'll need a new line
1647 line
= new TextLayoutLine (this, inptr
- text
, offset
);
1649 if (!OverrideLineHeight ()) {
1650 if (*inptr
== '\0') {
1651 line
->descend
= font
->Descender ();
1652 line
->height
= font
->Height ();
1655 line
->descend
= DescendOverride ();
1656 line
->height
= LineHeightOverride ();
1659 if (linebreak
&& *inptr
== '\0')
1660 actual_height
+= line
->height
;
1662 g_ptr_array_add (lines
, line
);
1666 if (inptr
< inend
) {
1667 // more text to layout with the current attrs...
1668 run
= new TextLayoutRun (line
, attrs
, inptr
- text
);
1669 g_ptr_array_add (line
->runs
, run
);
1675 } while (*inptr
!= '\0');
1677 if (word
.break_ops
!= NULL
)
1678 g_array_free (word
.break_ops
, true);
1683 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1684 print_lines (lines
);
1685 printf ("actualWidth = %f, actualHeight = %f\n\n", actual_width
, actual_height
);
1690 static inline TextLayoutGlyphCluster
*
1691 GenerateGlyphCluster (TextFont
*font
, GlyphInfo
**pglyph
, const char *text
, int start
, int length
)
1693 TextLayoutGlyphCluster
*cluster
= new TextLayoutGlyphCluster (start
, length
);
1694 const char *inend
= text
+ start
+ length
;
1695 const char *inptr
= text
+ start
;
1696 GlyphInfo
*prev
= *pglyph
;
1702 // set y0 to the baseline
1703 y0
= font
->Ascender ();
1707 // count how many path data items we'll need to allocate
1708 while (inptr
< inend
) {
1709 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
1712 if (UnicharIsLineBreak (c
))
1715 // treat tab as a single space
1719 if (!(glyph
= font
->GetGlyphInfo (c
)))
1723 size
+= glyph
->path
->cairo
.num_data
+ 1;
1727 // generate the cached path for the cluster
1728 cluster
->path
= moon_path_new (size
);
1729 inptr
= text
+ start
;
1731 while (inptr
< inend
) {
1732 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
1735 if (UnicharIsLineBreak (c
))
1738 // treat tab as a single space
1742 if (!(glyph
= font
->GetGlyphInfo (c
)))
1745 if ((prev
!= NULL
) && APPLY_KERNING (c
))
1746 x0
+= font
->Kerning (prev
, glyph
);
1747 else if (glyph
->metrics
.horiBearingX
< 0)
1748 x0
+= glyph
->metrics
.horiBearingX
;
1750 font
->AppendPath (cluster
->path
, glyph
, x0
, y0
);
1751 x0
+= glyph
->metrics
.horiAdvance
;
1754 if (!g_unichar_isspace (c
))
1758 moon_close_path (cluster
->path
);
1761 cluster
->uadvance
= x1
;
1762 cluster
->advance
= x0
;
1770 TextLayoutRun::GenerateCache ()
1772 int selection_length
= line
->layout
->GetSelectionLength ();
1773 int selection_start
= line
->layout
->GetSelectionStart ();
1774 const char *text
= line
->layout
->GetText ();
1775 const char *inend
= text
+ start
+ length
;
1776 const char *inptr
= text
+ start
;
1777 TextFont
*font
= attrs
->Font ();
1778 TextLayoutGlyphCluster
*cluster
;
1779 const char *selection_end
;
1780 GlyphInfo
*prev
= NULL
;
1783 // cache the glyph cluster leading up to the selection
1784 if (selection_length
== 0 || start
< selection_start
) {
1785 if (selection_length
> 0)
1786 len
= MIN (selection_start
- start
, length
);
1790 cluster
= GenerateGlyphCluster (font
, &prev
, text
, start
, len
);
1791 g_ptr_array_add (clusters
, cluster
);
1795 // cache the selected glyph cluster
1796 selection_end
= text
+ selection_start
+ selection_length
;
1797 if (inptr
< inend
&& inptr
< selection_end
) {
1798 len
= MIN (inend
, selection_end
) - inptr
;
1799 cluster
= GenerateGlyphCluster (font
, &prev
, text
, inptr
- text
, len
);
1800 g_ptr_array_add (clusters
, cluster
);
1801 cluster
->selected
= true;
1805 // cache the glyph cluster following the selection
1806 if (inptr
< inend
) {
1807 cluster
= GenerateGlyphCluster (font
, &prev
, text
, inptr
- text
, inend
- inptr
);
1808 g_ptr_array_add (clusters
, cluster
);
1814 TextLayoutGlyphCluster::Render (cairo_t
*cr
, const Point
&origin
, TextLayoutAttributes
*attrs
, const char *text
, double x
, double y
, bool uline_full
)
1816 TextFont
*font
= attrs
->Font ();
1817 const char *inend
, *prev
;
1824 if (length
== 0 || advance
== 0.0)
1827 // y is the baseline, set the origin to the top-left
1828 cairo_translate (cr
, x
, y
- font
->Ascender ());
1830 // set y0 to the baseline relative to the translation matrix
1831 y0
= font
->Ascender ();
1833 if (selected
&& (brush
= attrs
->Background (true))) {
1834 area
= Rect (origin
.x
, origin
.y
, advance
, font
->Height ());
1836 // extend the selection background by the width of a SPACE if it includes CRLF
1837 inend
= text
+ start
+ length
;
1838 if ((prev
= g_utf8_find_prev_char (text
+ start
, inend
)))
1839 c
= utf8_getc (&prev
, inend
- prev
);
1843 if (UnicharIsLineBreak (c
)) {
1844 if ((glyph
= font
->GetGlyphInfo (' ')))
1845 area
.width
+= glyph
->metrics
.horiAdvance
;
1848 // render the selection background
1849 brush
->SetupBrush (cr
, area
);
1850 cairo_new_path (cr
);
1851 cairo_rectangle (cr
, area
.x
, area
.y
, area
.width
, area
.height
);
1855 // setup the foreground brush
1856 if (!(brush
= attrs
->Foreground (selected
)))
1859 area
= Rect (origin
.x
, origin
.y
, advance
, font
->Height ());
1860 brush
->SetupBrush (cr
, area
);
1861 cairo_new_path (cr
);
1863 if (path
&& path
->cairo
.data
)
1864 cairo_append_path (cr
, &path
->cairo
);
1868 if (attrs
->IsUnderlined ()) {
1869 double thickness
= font
->UnderlineThickness ();
1870 double pos
= y0
+ font
->UnderlinePosition ();
1872 cairo_set_line_width (cr
, thickness
);
1874 cairo_new_path (cr
);
1875 Rect underline
= Rect (0.0, pos
- thickness
* 0.5, uline_full
? advance
: uadvance
, thickness
);
1876 underline
.Draw (cr
);
1883 TextLayoutRun::Render (cairo_t
*cr
, const Point
&origin
, double x
, double y
, bool is_last_run
)
1885 const char *text
= line
->layout
->GetText ();
1886 TextLayoutGlyphCluster
*cluster
;
1889 if (clusters
->len
== 0)
1892 for (guint i
= 0; i
< clusters
->len
; i
++) {
1893 cluster
= (TextLayoutGlyphCluster
*) clusters
->pdata
[i
];
1896 cluster
->Render (cr
, origin
, attrs
, text
, x0
, y
, is_last_run
&& ((i
+ 1) < clusters
->len
));
1899 x0
+= cluster
->advance
;
1904 TextLayoutLine::Render (cairo_t
*cr
, const Point
&origin
, double left
, double top
)
1909 // set y0 to the line's baseline (descend is a negative value)
1910 y0
= top
+ height
+ descend
;
1913 for (guint i
= 0; i
< runs
->len
; i
++) {
1914 run
= (TextLayoutRun
*) runs
->pdata
[i
];
1915 run
->Render (cr
, origin
, x0
, y0
, (i
+ 1) < runs
->len
);
1921 GetWidthConstraint (double avail_width
, double max_width
, double actual_width
)
1923 if (isinf (avail_width
)) {
1924 // find an upper width constraint
1925 if (isinf (max_width
))
1926 return actual_width
;
1935 TextLayout::HorizontalAlignment (double line_width
)
1940 switch (alignment
) {
1941 case TextAlignmentCenter
:
1942 width
= GetWidthConstraint (avail_width
, max_width
, actual_width
);
1943 if (line_width
< width
)
1944 deltax
= (width
- line_width
) / 2.0;
1948 case TextAlignmentRight
:
1949 width
= GetWidthConstraint (avail_width
, max_width
, actual_width
);
1950 if (line_width
< width
)
1951 deltax
= width
- line_width
;
1964 TextLayout::Render (cairo_t
*cr
, const Point
&origin
, const Point
&offset
)
1966 TextLayoutLine
*line
;
1973 for (guint i
= 0; i
< lines
->len
; i
++) {
1974 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1976 x
= offset
.x
+ HorizontalAlignment (line
->advance
);
1977 line
->Render (cr
, origin
, x
, y
);
1982 #ifdef USE_BINARY_SEARCH
1985 * @lo: the low bound
1986 * @hi: the high bound
1988 * Finds the midpoint between positive integer values, @lo and @hi.
1990 * Notes: Typically expressed as '(@lo + @hi) / 2', this is incorrect
1991 * when @lo and @hi are sufficiently large enough that combining them
1992 * would overflow their integer type. To work around this, we use the
1993 * formula, '@lo + ((@hi - @lo) / 2)', thus preventing this problem
1996 * Returns the midpoint between @lo and @hi (rounded down).
1998 #define MID(lo, hi) (lo + ((hi - lo) >> 1))
2001 TextLayout::GetLineFromY (const Point
&offset
, double y
, int *index
)
2003 register guint lo
, hi
;
2004 TextLayoutLine
*line
;
2008 if (lines
->len
== 0)
2011 lo
= 0, hi
= lines
->len
;
2017 line
= (TextLayoutLine
*) lines
->pdata
[m
];
2019 if (m
> 0 && y0
< line
->top
) {
2020 // y is on some line above us
2022 } else if (y0
> line
->top
+ line
->height
) {
2023 // y is on some line below us
2027 // y is on this line
2039 #else /* linear search */
2041 TextLayout::GetLineFromY (const Point
&offset
, double y
, int *index
)
2043 TextLayoutLine
*line
= NULL
;
2046 //printf ("TextLayout::GetLineFromY (%.2g)\n", y);
2050 for (guint i
= 0; i
< lines
->len
; i
++) {
2051 line
= (TextLayoutLine
*) lines
->pdata
[i
];
2053 // set y1 the top of the next line
2054 y1
= y0
+ line
->height
;
2057 // we found the line that the point is located on
2072 TextLayout::GetLineFromIndex (int index
)
2074 if (index
>= (int) lines
->len
|| index
< 0)
2077 return (TextLayoutLine
*) lines
->pdata
[index
];
2081 TextLayoutLine::GetCursorFromX (const Point
&offset
, double x
)
2083 const char *text
, *inend
, *ch
, *inptr
;
2084 TextLayoutRun
*run
= NULL
;
2085 GlyphInfo
*prev
, *glyph
;
2093 // adjust x0 for horizontal alignment
2094 x0
= offset
.x
+ layout
->HorizontalAlignment (advance
);
2096 text
= layout
->GetText ();
2097 inptr
= text
+ start
;
2098 cursor
= this->offset
;
2101 for (i
= 0; i
< runs
->len
; i
++) {
2102 run
= (TextLayoutRun
*) runs
->pdata
[i
];
2104 if (x
< x0
+ run
->advance
) {
2105 // x is in somewhere inside this run
2109 // x is beyond this run
2110 cursor
+= run
->count
;
2111 inptr
+= run
->length
;
2117 inptr
= text
+ run
->start
;
2118 inend
= inptr
+ run
->length
;
2119 font
= run
->attrs
->Font ();
2121 while (inptr
< inend
) {
2123 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
2126 if (UnicharIsLineBreak (c
)) {
2133 // we treat tabs as a single space
2137 if (!(glyph
= font
->GetGlyphInfo (c
)))
2140 if ((prev
!= NULL
) && APPLY_KERNING (c
))
2141 x0
+= font
->Kerning (prev
, glyph
);
2142 else if (glyph
->metrics
.horiBearingX
< 0)
2143 x0
+= glyph
->metrics
.horiBearingX
;
2145 // calculate midpoint of the character
2146 m
= glyph
->metrics
.horiAdvance
/ 2.0;
2148 // if x is <= the midpoint, then the cursor is
2149 // considered to be at the start of this character.
2156 x0
+= glyph
->metrics
.horiAdvance
;
2160 // x is beyond the end of the last run
2161 run
= (TextLayoutRun
*) runs
->pdata
[i
- 1];
2162 inend
= text
+ run
->start
+ run
->length
;
2163 inptr
= text
+ run
->start
;
2165 if ((ch
= g_utf8_find_prev_char (inptr
, inend
)))
2166 c
= utf8_getc (&ch
, inend
- ch
);
2174 if (inend
> inptr
&& inend
[-1] == '\r') {
2178 } else if (UnicharIsLineBreak (c
)) {
2188 TextLayout::GetCursorFromXY (const Point
&offset
, double x
, double y
)
2190 TextLayoutLine
*line
;
2192 //printf ("TextLayout::GetCursorFromXY (%.2g, %.2g)\n", x, y);
2197 if (!(line
= GetLineFromY (offset
, y
)))
2200 return line
->GetCursorFromX (offset
, x
);
2204 TextLayout::GetCursor (const Point
&offset
, int index
)
2206 const char *inptr
, *inend
, *pchar
;
2207 double height
, x0
, y0
, y1
;
2208 GlyphInfo
*prev
, *glyph
;
2209 TextLayoutLine
*line
;
2215 //printf ("TextLayout::GetCursor (%d)\n", index);
2222 for (guint i
= 0; i
< lines
->len
; i
++) {
2223 line
= (TextLayoutLine
*) lines
->pdata
[i
];
2224 inend
= text
+ line
->start
+ line
->length
;
2226 // adjust x0 for horizontal alignment
2227 x0
= offset
.x
+ HorizontalAlignment (line
->advance
);
2229 // set y1 to the baseline (descend is a negative value)
2230 y1
= y0
+ line
->height
+ line
->descend
;
2231 height
= line
->height
;
2233 //printf ("\tline: left=%.2f, top=%.2f, baseline=%.2f, start index=%d\n", x0, y0, y1, line->offset);
2235 if (index
>= cursor
+ line
->count
) {
2236 // maybe the cursor is on the next line...
2237 if ((i
+ 1) == lines
->len
) {
2238 // we are on the last line... get the previous unichar
2239 inptr
= text
+ line
->start
;
2240 inend
= inptr
+ line
->length
;
2242 if ((pchar
= g_utf8_find_prev_char (text
+ line
->start
, inend
)))
2243 c
= utf8_getc (&pchar
, inend
- pchar
);
2247 if (UnicharIsLineBreak (c
)) {
2248 // cursor is on the next line by itself
2249 x0
= offset
.x
+ HorizontalAlignment (0.0);
2252 // cursor at the end of the last line
2253 x0
+= line
->advance
;
2259 cursor
+= line
->count
;
2264 // cursor is on this line...
2265 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
2266 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
2267 inend
= text
+ run
->start
+ run
->length
;
2269 if (index
>= cursor
+ run
->count
) {
2270 // maybe the cursor is in the next run...
2271 cursor
+= run
->count
;
2276 // cursor is in this run...
2277 font
= run
->attrs
->Font ();
2278 inptr
= text
+ run
->start
;
2281 while (cursor
< index
) {
2282 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
2287 // we treat tabs as a single space
2291 if (!(glyph
= font
->GetGlyphInfo (c
)))
2294 if ((prev
!= NULL
) && APPLY_KERNING (c
))
2295 x0
+= font
->Kerning (prev
, glyph
);
2296 else if (glyph
->metrics
.horiBearingX
< 0)
2297 x0
+= glyph
->metrics
.horiBearingX
;
2299 x0
+= glyph
->metrics
.horiAdvance
;
2309 return Rect (x0
, y0
, 1.0, height
);