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.
20 #include "moon-path.h"
26 #define d(x) if (debug_flags & RUNTIME_DEBUG_LAYOUT) x
32 static const char *unicode_break_types
[] = {
33 "G_UNICODE_BREAK_MANDATORY",
34 "G_UNICODE_BREAK_CARRIAGE_RETURN",
35 "G_UNICODE_BREAK_LINE_FEED",
36 "G_UNICODE_BREAK_COMBINING_MARK",
37 "G_UNICODE_BREAK_SURROGATE",
38 "G_UNICODE_BREAK_ZERO_WIDTH_SPACE",
39 "G_UNICODE_BREAK_INSEPARABLE",
40 "G_UNICODE_BREAK_NON_BREAKING_GLUE",
41 "G_UNICODE_BREAK_CONTINGENT",
42 "G_UNICODE_BREAK_SPACE",
43 "G_UNICODE_BREAK_AFTER",
44 "G_UNICODE_BREAK_BEFORE",
45 "G_UNICODE_BREAK_BEFORE_AND_AFTER",
46 "G_UNICODE_BREAK_HYPHEN",
47 "G_UNICODE_BREAK_NON_STARTER",
48 "G_UNICODE_BREAK_OPEN_PUNCTUATION",
49 "G_UNICODE_BREAK_CLOSE_PUNCTUATION",
50 "G_UNICODE_BREAK_QUOTATION",
51 "G_UNICODE_BREAK_EXCLAMATION",
52 "G_UNICODE_BREAK_IDEOGRAPHIC",
53 "G_UNICODE_BREAK_NUMERIC",
54 "G_UNICODE_BREAK_INFIX_SEPARATOR",
55 "G_UNICODE_BREAK_SYMBOL",
56 "G_UNICODE_BREAK_ALPHABETIC",
57 "G_UNICODE_BREAK_PREFIX",
58 "G_UNICODE_BREAK_POSTFIX",
59 "G_UNICODE_BREAK_COMPLEX_CONTEXT",
60 "G_UNICODE_BREAK_AMBIGUOUS",
61 "G_UNICODE_BREAK_UNKNOWN",
62 "G_UNICODE_BREAK_NEXT_LINE",
63 "G_UNICODE_BREAK_WORD_JOINER",
64 "G_UNICODE_BREAK_HANGUL_L_JAMO",
65 "G_UNICODE_BREAK_HANGUL_V_JAMO",
66 "G_UNICODE_BREAK_HANGUL_T_JAMO",
67 "G_UNICODE_BREAK_HANGUL_LV_SYLLABLE",
68 "G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE"
71 static const char *unicode_char_types
[] = {
74 "G_UNICODE_UNASSIGNED",
75 "G_UNICODE_PRIVATE_USE",
76 "G_UNICODE_SURROGATE",
77 "G_UNICODE_LOWERCASE_LETTER",
78 "G_UNICODE_MODIFIER_LETTER",
79 "G_UNICODE_OTHER_LETTER",
80 "G_UNICODE_TITLECASE_LETTER",
81 "G_UNICODE_UPPERCASE_LETTER",
82 "G_UNICODE_COMBINING_MARK",
83 "G_UNICODE_ENCLOSING_MARK",
84 "G_UNICODE_NON_SPACING_MARK",
85 "G_UNICODE_DECIMAL_NUMBER",
86 "G_UNICODE_LETTER_NUMBER",
87 "G_UNICODE_OTHER_NUMBER",
88 "G_UNICODE_CONNECT_PUNCTUATION",
89 "G_UNICODE_DASH_PUNCTUATION",
90 "G_UNICODE_CLOSE_PUNCTUATION",
91 "G_UNICODE_FINAL_PUNCTUATION",
92 "G_UNICODE_INITIAL_PUNCTUATION",
93 "G_UNICODE_OTHER_PUNCTUATION",
94 "G_UNICODE_OPEN_PUNCTUATION",
95 "G_UNICODE_CURRENCY_SYMBOL",
96 "G_UNICODE_MODIFIER_SYMBOL",
97 "G_UNICODE_MATH_SYMBOL",
98 "G_UNICODE_OTHER_SYMBOL",
99 "G_UNICODE_LINE_SEPARATOR",
100 "G_UNICODE_PARAGRAPH_SEPARATOR",
101 "G_UNICODE_SPACE_SEPARATOR"
105 #define UnicharIsLineBreak(c) ((c) == '\r' || (c) == '\n' || (c) == 0x2028)
107 #define BreakSpace(c, btype) (c == '\t' || btype == G_UNICODE_BREAK_SPACE || btype == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
111 * Silverlight does not apply any kerning on a DOT, so we exclude them
113 * U+06D4 ARABIC FULL STOP
114 * U+3002 IDEOGRAPHIC FULL STOP
115 * Note: this is different than using the "sliding dot" algorithm from
116 * http://www.freetype.org/freetype2/docs/glyphs/glyphs-4.html
118 #define APPLY_KERNING(uc) ((uc != 0x002E) && (uc != 0x06D4) && (uc != 3002))
121 static inline gunichar
122 utf8_getc (const char **in
, size_t inlen
)
124 register const unsigned char *inptr
= (const unsigned char *) *in
;
125 const unsigned char *inend
= inptr
+ inlen
;
126 register unsigned char c
, r
;
127 register gunichar m
, u
= 0;
134 *in
= (const char *) inptr
;
136 } else if (r
< 0xfe) { /* valid start char? */
138 m
= 0x7f80; /* used to mask out the length bits */
140 if (inptr
>= inend
) {
141 *in
= (const char *) inend
;
146 if ((c
& 0xc0) != 0x80)
149 u
= (u
<< 6) | (c
& 0x3f);
154 *in
= (const char *) inptr
;
168 // TextLayoutGlyphCluster
171 TextLayoutGlyphCluster::TextLayoutGlyphCluster (int _start
, int _length
)
180 TextLayoutGlyphCluster::~TextLayoutGlyphCluster ()
183 moon_path_destroy (path
);
191 TextLayoutRun::TextLayoutRun (TextLayoutLine
*_line
, TextLayoutAttributes
*_attrs
, int _start
)
193 clusters
= g_ptr_array_new ();
202 TextLayoutRun::~TextLayoutRun ()
204 for (guint i
= 0; i
< clusters
->len
; i
++)
205 delete (TextLayoutGlyphCluster
*) clusters
->pdata
[i
];
207 g_ptr_array_free (clusters
, true);
211 TextLayoutRun::ClearCache ()
213 for (guint i
= 0; i
< clusters
->len
; i
++)
214 delete (TextLayoutGlyphCluster
*) clusters
->pdata
[i
];
216 g_ptr_array_set_size (clusters
, 0);
224 TextLayoutLine::TextLayoutLine (TextLayout
*_layout
, int _start
, int _offset
)
226 runs
= g_ptr_array_new ();
238 TextLayoutLine::~TextLayoutLine ()
240 for (guint i
= 0; i
< runs
->len
; i
++)
241 delete (TextLayoutRun
*) runs
->pdata
[i
];
243 g_ptr_array_free (runs
, true);
252 TextLayout::TextLayout ()
254 // Note: TextBlock and TextBox assume their default values match these
255 strategy
= LineStackingStrategyMaxHeight
;
256 alignment
= TextAlignmentLeft
;
257 wrapping
= TextWrappingNoWrap
;
258 selection_length
= 0;
260 avail_width
= INFINITY
;
261 max_height
= INFINITY
;
262 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::SetLineStackingStrategy (LineStackingStrategy mode
)
306 if (strategy
== mode
)
317 TextLayout::SetTextAlignment (TextAlignment align
)
319 if (alignment
== align
)
328 TextLayout::SetTextWrapping (TextWrapping mode
)
331 case TextWrappingNoWrap
:
332 case TextWrappingWrap
:
335 // Silverlight defaults to Wrap for unknown values
336 mode
= TextWrappingWrap
;
340 if (wrapping
== mode
)
351 TextLayout::SetLineHeight (double height
)
353 if (line_height
== height
)
356 line_height
= height
;
364 TextLayout::SetMaxHeight (double height
)
366 if (max_height
== height
)
377 TextLayout::SetMaxWidth (double width
)
379 if (max_width
== width
)
382 if (!is_wrapped
&& (isinf (width
) || width
> actual_width
)) {
383 // the new max_width won't change layout
396 TextLayout::SetTextAttributes (List
*attrs
)
399 attributes
->Clear (true);
411 TextLayout::SetText (const char *str
, int len
)
416 length
= len
== -1 ? strlen (str
) : len
;
417 text
= (char *) g_malloc (length
+ 1);
418 memcpy (text
, str
, length
);
433 TextLayout::ClearCache ()
435 TextLayoutLine
*line
;
438 for (guint i
= 0; i
< lines
->len
; i
++) {
439 line
= (TextLayoutLine
*) lines
->pdata
[i
];
441 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
442 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
454 UpdateSelection (GPtrArray
*lines
, TextRegion
*pre
, TextRegion
*post
)
456 TextLayoutGlyphCluster
*cluster
;
457 TextLayoutLine
*line
;
461 // first update pre-region
462 for (i
= 0; i
< lines
->len
; i
++) {
463 line
= (TextLayoutLine
*) lines
->pdata
[i
];
465 if (pre
->start
>= line
->start
+ line
->length
) {
466 // pre-region not on this line...
470 for (j
= 0; j
< line
->runs
->len
; j
++) {
471 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
473 if (pre
->start
>= run
->start
+ run
->length
) {
474 // pre-region not in this run...
478 if (pre
->start
<= run
->start
) {
479 if (pre
->start
+ pre
->length
>= run
->start
+ run
->length
) {
480 // run is fully contained within the pre-region
481 if (run
->clusters
->len
== 1) {
482 cluster
= (TextLayoutGlyphCluster
*) run
->clusters
->pdata
[0];
483 cluster
->selected
= pre
->select
;
494 if (pre
->start
+ pre
->length
<= run
->start
+ run
->length
)
499 // now update the post region...
500 for ( ; i
< lines
->len
; i
++, j
= 0) {
501 line
= (TextLayoutLine
*) lines
->pdata
[i
];
503 if (post
->start
>= line
->start
+ line
->length
) {
504 // pre-region not on this line...
508 for ( ; j
< line
->runs
->len
; j
++) {
509 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
511 if (post
->start
>= run
->start
+ run
->length
) {
512 // post-region not in this run...
516 if (post
->start
<= run
->start
) {
517 if (post
->start
+ post
->length
>= run
->start
+ run
->length
) {
518 // run is fully contained within the pre-region
519 if (run
->clusters
->len
== 1) {
520 cluster
= (TextLayoutGlyphCluster
*) run
->clusters
->pdata
[0];
521 cluster
->selected
= post
->select
;
532 if (post
->start
+ post
->length
<= run
->start
+ run
->length
)
539 TextLayout::Select (int start
, int length
, bool byte_offsets
)
541 int new_selection_length
;
542 int new_selection_start
;
543 int new_selection_end
;
545 TextRegion pre
, post
;
550 selection_length
= 0;
556 inptr
= g_utf8_offset_to_pointer (text
, start
);
557 new_selection_start
= inptr
- text
;
559 inend
= g_utf8_offset_to_pointer (inptr
, length
);
560 new_selection_length
= inend
- inptr
;
562 new_selection_length
= length
;
563 new_selection_start
= start
;
566 if (selection_start
== new_selection_start
&&
567 selection_length
== new_selection_length
) {
568 // no change in selection...
573 // compute the region between the 2 starts
574 pre
.length
= abs (new_selection_start
- selection_start
);
575 pre
.start
= MIN (selection_start
, new_selection_start
);
576 pre
.select
= (new_selection_start
< selection_start
) && (new_selection_length
> 0);
578 // compute the region between the 2 ends
579 new_selection_end
= new_selection_start
+ new_selection_length
;
580 selection_end
= selection_start
+ selection_length
;
581 post
.length
= abs (new_selection_end
- selection_end
);
582 post
.start
= MIN (selection_end
, new_selection_end
);
583 post
.select
= (new_selection_end
> selection_end
) && (new_selection_length
> 0);
585 UpdateSelection (lines
, &pre
, &post
);
587 selection_length
= new_selection_length
;
588 selection_start
= new_selection_start
;
590 if (selection_length
|| new_selection_length
)
593 selection_length
= new_selection_length
;
594 selection_start
= new_selection_start
;
599 * TextLayout::GetActualExtents:
603 * Gets the actual width and height extents required for rendering the
607 TextLayout::GetActualExtents (double *width
, double *height
)
609 *height
= actual_height
;
610 *width
= actual_width
;
615 unichar_combining_class (gunichar c
)
617 #if GLIB_CHECK_VERSION (2,14,0)
618 if (glib_check_version (2,14,0))
619 return g_unichar_combining_class (c
);
628 WORD_TYPE_ALPHABETIC
,
629 WORD_TYPE_IDEOGRAPHIC
,
630 WORD_TYPE_INSEPARABLE
,
635 struct WordBreakOpportunity
{
636 GUnicodeBreakType btype
;
647 GArray
*break_ops
; // TextWrappingWrap only
656 guint32 prev
; // previous glyph index; used for kerning
659 double advance
; // the advance-width of the 'word'
660 int length
; // length of the word in bytes
661 int count
; // length of the word in unichars
664 typedef bool (* LayoutWordCallback
) (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
);
667 IsLineBreak (const char *text
, size_t left
, size_t *n_bytes
, size_t *n_chars
)
669 const char *inptr
= text
;
672 if ((c
= utf8_getc (&inptr
, left
)) == (gunichar
) -1)
675 if (!UnicharIsLineBreak (c
))
678 if (c
== '\r' && *inptr
== '\n') {
682 *n_bytes
= (size_t) (inptr
- text
);
690 layout_word_init (LayoutWord
*word
, double line_advance
, guint32 prev
)
692 word
->line_advance
= line_advance
;
698 * @word: #LayoutWord context
700 * @inend: end of input text
702 * Measures a word containing nothing but LWSP.
705 layout_lwsp (LayoutWord
*word
, const char *in
, const char *inend
)
707 guint32 prev
= word
->prev
;
708 GUnicodeBreakType btype
;
709 const char *inptr
= in
;
715 d(printf ("layout_lwsp():\n"));
720 while (inptr
< inend
) {
722 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
723 // ignore invalid chars
727 if (UnicharIsLineBreak (c
)) {
732 btype
= g_unichar_break_type (c
);
733 if (!BreakSpace (c
, btype
)) {
739 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
740 if (c
< 128 && isprint ((int) c
))
741 printf ("\tunichar = %c; btype = %s, ctype = %s\n", (char) c
,
742 unicode_break_types
[btype
], unicode_char_types
[g_unichar_type (c
)]);
744 printf ("\tunichar = 0x%.4X; btype = %s, ctype = %s\n", c
,
745 unicode_break_types
[btype
], unicode_char_types
[g_unichar_type (c
)]);
751 // treat tab as a single space
755 // ignore glyphs the font doesn't contain...
756 if (!(glyph
= word
->font
->GetGlyphInfo (c
)))
759 // calculate total glyph advance
760 advance
= glyph
->metrics
.horiAdvance
;
761 if ((prev
!= 0) && APPLY_KERNING (c
))
762 advance
+= word
->font
->Kerning (prev
, glyph
->index
);
763 else if (glyph
->metrics
.horiBearingX
< 0)
764 advance
-= glyph
->metrics
.horiBearingX
;
766 word
->line_advance
+= advance
;
767 word
->advance
+= advance
;
771 word
->length
= (inptr
- in
);
776 * layout_word_nowrap:
777 * @word: #LayoutWord context
779 * @inend = end of input text
780 * @max_width: max allowable width for a line
782 * Calculates the advance of the current word.
784 * Returns: %true if the caller should create a new line for the
785 * remainder of the word or %false otherwise.
788 layout_word_nowrap (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
)
790 GUnicodeBreakType btype
= G_UNICODE_BREAK_UNKNOWN
;
791 guint32 prev
= word
->prev
;
792 const char *inptr
= in
;
798 // Note: since we don't ever need to wrap, no need to keep track of word-type
799 word
->type
= WORD_TYPE_UNKNOWN
;
803 while (inptr
< inend
) {
805 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
806 // ignore invalid chars
810 if (UnicharIsLineBreak (c
)) {
815 if (btype
== G_UNICODE_BREAK_COMBINING_MARK
) {
816 // ignore zero-width spaces
817 if ((btype
= g_unichar_break_type (c
)) == G_UNICODE_BREAK_ZERO_WIDTH_SPACE
)
818 btype
= G_UNICODE_BREAK_COMBINING_MARK
;
820 btype
= g_unichar_break_type (c
);
823 if (BreakSpace (c
, btype
)) {
830 // ignore glyphs the font doesn't contain...
831 if (!(glyph
= word
->font
->GetGlyphInfo (c
)))
834 // calculate total glyph advance
835 advance
= glyph
->metrics
.horiAdvance
;
836 if ((prev
!= 0) && APPLY_KERNING (c
))
837 advance
+= word
->font
->Kerning (prev
, glyph
->index
);
838 else if (glyph
->metrics
.horiBearingX
< 0)
839 advance
-= glyph
->metrics
.horiBearingX
;
841 word
->line_advance
+= advance
;
842 word
->advance
+= advance
;
846 word
->length
= (inptr
- in
);
853 word_type (GUnicodeType ctype
, GUnicodeBreakType btype
)
856 case G_UNICODE_BREAK_ALPHABETIC
:
857 return WORD_TYPE_ALPHABETIC
;
858 case G_UNICODE_BREAK_IDEOGRAPHIC
:
859 return WORD_TYPE_IDEOGRAPHIC
;
860 case G_UNICODE_BREAK_NUMERIC
:
861 if (ctype
== G_UNICODE_OTHER_PUNCTUATION
)
862 return WORD_TYPE_UNKNOWN
;
863 return WORD_TYPE_NUMERIC
;
864 case G_UNICODE_BREAK_INSEPARABLE
:
865 return WORD_TYPE_INSEPARABLE
;
866 #if GLIB_CHECK_VERSION (2,10,0)
867 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE
:
868 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE
:
869 case G_UNICODE_BREAK_HANGUL_L_JAMO
:
870 case G_UNICODE_BREAK_HANGUL_V_JAMO
:
871 case G_UNICODE_BREAK_HANGUL_T_JAMO
:
872 return WORD_TYPE_HANGUL
;
875 return WORD_TYPE_UNKNOWN
;
880 word_type_changed (WordType wtype
, gunichar c
, GUnicodeType ctype
, GUnicodeBreakType btype
)
884 if (wtype
== WORD_TYPE_ALPHABETIC
&& c
>= '0' && c
<= '9')
887 if ((type
= word_type (ctype
, btype
)) != WORD_TYPE_UNKNOWN
)
888 return type
!= wtype
;
891 case WORD_TYPE_INSEPARABLE
:
892 // only allow inseparables in an "inseparable" word.
894 case WORD_TYPE_NUMERIC
:
895 // if btype is anything other than an infix, then the word
897 if (btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)
911 * @inend = end of word
912 * @max_width: max allowable width for a line
914 * Calculates the advance of the current word, breaking if needed.
916 * Returns: %true if the caller should create a new line for the
917 * remainder of the word or %false otherwise.
920 layout_word_wrap (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
)
922 GUnicodeBreakType btype
= G_UNICODE_BREAK_UNKNOWN
;
923 bool line_start
= word
->line_advance
== 0.0;
924 guint32 prev
= word
->prev
;
925 WordBreakOpportunity op
;
926 const char *inptr
= in
;
941 g_array_set_size (word
->break_ops
, 0);
942 word
->type
= WORD_TYPE_UNKNOWN
;
946 d(printf ("layout_word_wrap():\n"));
947 d(debug
= g_string_new (""));
949 while (inptr
< inend
) {
951 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
952 // ignore invalid chars
956 if (UnicharIsLineBreak (c
)) {
961 // check the previous break-type
962 if (btype
== G_UNICODE_BREAK_CLOSE_PUNCTUATION
) {
963 // if anything other than an infix separator come after a close-punctuation, then the 'word' is done
964 btype
= g_unichar_break_type (c
);
965 if (btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
) {
969 } else if (btype
== G_UNICODE_BREAK_INFIX_SEPARATOR
) {
970 btype
= g_unichar_break_type (c
);
971 if (word
->type
== WORD_TYPE_NUMERIC
) {
972 // only accept numbers after the infix
973 if (btype
!= G_UNICODE_BREAK_NUMERIC
) {
977 } else if (word
->type
== WORD_TYPE_UNKNOWN
) {
978 // only accept alphanumerics after the infix
979 if (btype
!= G_UNICODE_BREAK_ALPHABETIC
&& btype
!= G_UNICODE_BREAK_NUMERIC
) {
986 } else if (btype
== G_UNICODE_BREAK_WORD_JOINER
) {
987 btype
= g_unichar_break_type (c
);
990 btype
= g_unichar_break_type (c
);
993 if (BreakSpace (c
, btype
)) {
998 ctype
= g_unichar_type (c
);
1000 if (word
->type
== WORD_TYPE_UNKNOWN
) {
1001 // record our word-type
1002 word
->type
= word_type (ctype
, btype
);
1003 } else if (btype
== G_UNICODE_BREAK_OPEN_PUNCTUATION
) {
1004 // this is a good place to break
1007 } else if (word_type_changed (word
->type
, c
, ctype
, btype
)) {
1008 // changing word-types, don't continue
1013 d(g_string_append_unichar (debug
, c
));
1016 // a Combining Class of 0 means start of a new glyph
1017 if (glyphs
> 0 && unichar_combining_class (c
) != 0) {
1018 // this char gets combined with the previous glyph
1026 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1027 if (c
< 128 && isprint ((int) c
))
1028 printf ("\tunichar = %c; btype = %s, new glyph = %s; cc = %d; isspace = %s\n", (char) c
,
1029 unicode_break_types
[btype
], new_glyph
? "true" : "false", unichar_combining_class (c
),
1030 unicode_char_types
[ctype
]);
1032 printf ("\tunichar = 0x%.4X; btype = %s, new glyph = %s; cc = %d; isspace = %s\n", c
,
1033 unicode_break_types
[btype
], new_glyph
? "true" : "false", unichar_combining_class (c
),
1034 unicode_char_types
[ctype
]);
1038 if ((glyph
= word
->font
->GetGlyphInfo (c
))) {
1039 // calculate total glyph advance
1040 advance
= glyph
->metrics
.horiAdvance
;
1041 if ((prev
!= 0) && APPLY_KERNING (c
))
1042 advance
+= word
->font
->Kerning (prev
, glyph
->index
);
1043 else if (glyph
->metrics
.horiBearingX
< 0)
1044 advance
-= glyph
->metrics
.horiBearingX
;
1046 word
->line_advance
+= advance
;
1047 word
->advance
+= advance
;
1048 prev
= glyph
->index
;
1054 op
.index
= glyph
? glyph
->index
: 0;
1055 op
.advance
= word
->advance
;
1056 op
.count
= word
->count
;
1062 g_array_remove_index (word
->break_ops
, word
->break_ops
->len
- 1);
1063 op
.advance
+= advance
;
1069 g_array_append_val (word
->break_ops
, op
);
1071 if (!isinf (max_width
) && word
->line_advance
>= max_width
) {
1072 d(printf ("\tjust exceeded max width (%fpx): %s\n", max_width
, debug
->str
));
1079 d(g_string_free (debug
, true));
1080 word
->length
= (inptr
- in
);
1085 d(printf ("\tcollecting any/all decomposed chars and figuring out the break-type for the next glyph\n"));
1087 // pretend btype is SPACE here in case inptr is at the end of the run
1089 btype
= G_UNICODE_BREAK_SPACE
;
1091 // keep going until we reach a new distinct glyph. we also
1092 // need to know the btype of the char after the char that
1093 // exceeded the width limit.
1094 while (inptr
< inend
) {
1096 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
1097 // ignore invalid chars
1101 if (UnicharIsLineBreak (c
)) {
1102 btype
= G_UNICODE_BREAK_SPACE
;
1107 btype
= g_unichar_break_type (c
);
1108 if (BreakSpace (c
, btype
) || unichar_combining_class (c
) == 0) {
1113 d(g_string_append_unichar (debug
, c
));
1116 if ((glyph
= word
->font
->GetGlyphInfo (c
))) {
1117 // calculate total glyph advance
1118 advance
= glyph
->metrics
.horiAdvance
;
1119 if ((prev
!= 0) && APPLY_KERNING (c
))
1120 advance
+= word
->font
->Kerning (prev
, glyph
->index
);
1121 else if (glyph
->metrics
.horiBearingX
< 0)
1122 advance
-= glyph
->metrics
.horiBearingX
;
1124 word
->line_advance
+= advance
;
1125 word
->advance
+= advance
;
1126 prev
= glyph
->index
;
1131 g_array_remove_index (word
->break_ops
, word
->break_ops
->len
- 1);
1132 op
.advance
+= advance
;
1136 g_array_append_val (word
->break_ops
, op
);
1139 d(printf ("\tok, at this point we have: %s\n", debug
->str
));
1140 d(printf ("\tnext break-type is %s\n", unicode_break_types
[btype
]));
1141 d(g_string_free (debug
, true));
1143 // at this point, we're going to break the word so we can reset kerning
1146 // we can't break any smaller than a single glyph
1147 if (line_start
&& glyphs
== 1) {
1148 d(printf ("\tsince this is the first glyph on the line, can't break any smaller than that...\n"));
1149 word
->length
= (inptr
- in
);
1156 // search backwards for the best break point
1157 d(printf ("\tscanning over %d break opportunities...\n", word
->break_ops
->len
));
1158 for (guint i
= word
->break_ops
->len
; i
> 0; i
--) {
1159 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 1);
1162 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1163 if (op
.c
< 128 && isprint ((int) op
.c
))
1164 printf ("\tunichar = %c; btype = %s; i = %d\n", (char) op
.c
, unicode_break_types
[op
.btype
], i
);
1166 printf ("\tunichar = 0x%.4X; btype = %s; i = %d\n", op
.c
, unicode_break_types
[op
.btype
], i
);
1171 case G_UNICODE_BREAK_BEFORE_AND_AFTER
:
1172 if (i
> 1 && i
== word
->break_ops
->len
) {
1173 // break after the previous glyph
1174 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1175 word
->length
= (op
.inptr
- in
);
1176 word
->advance
= op
.advance
;
1177 word
->count
= op
.count
;
1178 word
->prev
= op
.prev
;
1181 } else if (i
< word
->break_ops
->len
) {
1182 // break after this glyph
1183 word
->length
= (op
.inptr
- in
);
1184 word
->advance
= op
.advance
;
1185 word
->count
= op
.count
;
1186 word
->prev
= op
.prev
;
1190 case G_UNICODE_BREAK_NON_BREAKING_GLUE
:
1191 case G_UNICODE_BREAK_WORD_JOINER
:
1192 // cannot break before or after this character (unless forced)
1193 if (force
&& i
< word
->break_ops
->len
) {
1194 word
->length
= (op
.inptr
- in
);
1195 word
->advance
= op
.advance
;
1196 word
->count
= op
.count
;
1197 word
->prev
= op
.prev
;
1203 // skip past previous glyph
1204 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1208 case G_UNICODE_BREAK_INSEPARABLE
:
1209 // only restriction is no breaking between inseparables unless we have to
1210 if (line_start
&& i
< word
->break_ops
->len
) {
1211 word
->length
= (op
.inptr
- in
);
1212 word
->advance
= op
.advance
;
1213 word
->count
= op
.count
;
1214 word
->prev
= op
.prev
;
1219 case G_UNICODE_BREAK_BEFORE
:
1221 // break after the previous glyph
1222 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1223 word
->length
= (op
.inptr
- in
);
1224 word
->advance
= op
.advance
;
1225 word
->count
= op
.count
;
1226 word
->prev
= op
.prev
;
1231 case G_UNICODE_BREAK_CLOSE_PUNCTUATION
:
1232 if (i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)) {
1233 // we can safely break after this character
1234 word
->length
= (op
.inptr
- in
);
1235 word
->advance
= op
.advance
;
1236 word
->count
= op
.count
;
1237 word
->prev
= op
.prev
;
1242 if (i
> 1 && !force
) {
1243 // we can never break before a closing punctuation, so skip past prev char
1244 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1248 case G_UNICODE_BREAK_INFIX_SEPARATOR
:
1249 if (i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_NUMERIC
)) {
1250 // we can safely break after this character
1251 word
->length
= (op
.inptr
- in
);
1252 word
->advance
= op
.advance
;
1253 word
->count
= op
.count
;
1254 word
->prev
= op
.prev
;
1259 if (i
> 1 && !force
) {
1260 // we can never break before an infix, skip past prev char
1261 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1262 if (op
.btype
== G_UNICODE_BREAK_INFIX_SEPARATOR
||
1263 op
.btype
== G_UNICODE_BREAK_CLOSE_PUNCTUATION
) {
1264 // unless previous char is one of these special types...
1265 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 1);
1271 case G_UNICODE_BREAK_ALPHABETIC
:
1272 // only break if we have no choice...
1273 if ((line_start
|| fixed
|| force
) && i
< word
->break_ops
->len
) {
1274 word
->length
= (op
.inptr
- in
);
1275 word
->advance
= op
.advance
;
1276 word
->count
= op
.count
;
1277 word
->prev
= op
.prev
;
1282 case G_UNICODE_BREAK_IDEOGRAPHIC
:
1283 if (i
< word
->break_ops
->len
&& btype
!= G_UNICODE_BREAK_NON_STARTER
) {
1284 // we can safely break after this character
1285 word
->length
= (op
.inptr
- in
);
1286 word
->advance
= op
.advance
;
1287 word
->count
= op
.count
;
1288 word
->prev
= op
.prev
;
1293 case G_UNICODE_BREAK_NUMERIC
:
1294 // only break if we have no choice...
1295 if (line_start
&& i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)) {
1296 word
->length
= (op
.inptr
- in
);
1297 word
->advance
= op
.advance
;
1298 word
->count
= op
.count
;
1299 word
->prev
= op
.prev
;
1304 case G_UNICODE_BREAK_OPEN_PUNCTUATION
:
1305 case G_UNICODE_BREAK_COMBINING_MARK
:
1306 case G_UNICODE_BREAK_CONTINGENT
:
1307 case G_UNICODE_BREAK_AMBIGUOUS
:
1308 case G_UNICODE_BREAK_QUOTATION
:
1309 case G_UNICODE_BREAK_PREFIX
:
1310 // do not break after characters with these break-types (unless forced)
1311 if (force
&& i
< word
->break_ops
->len
) {
1312 word
->length
= (op
.inptr
- in
);
1313 word
->advance
= op
.advance
;
1314 word
->count
= op
.count
;
1315 word
->prev
= op
.prev
;
1321 d(printf ("Unhandled Unicode break-type: %s\n", unicode_break_types
[op
.btype
]));
1322 // fall thru to the "default" behavior
1324 #if GLIB_CHECK_VERSION (2,10,0)
1325 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE
:
1326 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE
:
1327 case G_UNICODE_BREAK_HANGUL_L_JAMO
:
1328 case G_UNICODE_BREAK_HANGUL_V_JAMO
:
1329 case G_UNICODE_BREAK_HANGUL_T_JAMO
:
1331 case G_UNICODE_BREAK_NON_STARTER
:
1332 case G_UNICODE_BREAK_EXCLAMATION
:
1333 case G_UNICODE_BREAK_MANDATORY
:
1334 case G_UNICODE_BREAK_NEXT_LINE
:
1335 case G_UNICODE_BREAK_UNKNOWN
:
1336 case G_UNICODE_BREAK_POSTFIX
:
1337 case G_UNICODE_BREAK_HYPHEN
:
1338 case G_UNICODE_BREAK_AFTER
:
1339 if (i
< word
->break_ops
->len
) {
1340 // we can safely break after this character
1341 word
->length
= (op
.inptr
- in
);
1342 word
->advance
= op
.advance
;
1343 word
->count
= op
.count
;
1344 word
->prev
= op
.prev
;
1355 if (line_start
&& !force
) {
1356 d(printf ("\tcouldn't find a good place to break but we must force a break, retrying...\n"));
1361 d(printf ("\tcouldn't find a good place to break, defaulting to breaking before the word start\n"));
1363 word
->advance
= 0.0;
1371 static const char *wrap_modes
[3] = {
1378 print_lines (GPtrArray
*lines
)
1380 TextLayoutLine
*line
;
1385 for (guint i
= 0; i
< lines
->len
; i
++) {
1386 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1388 printf ("Line (top=%f, height=%f, advance=%f, offset=%d, count=%d):\n", y
, line
->height
, line
->advance
, line
->offset
, line
->count
);
1389 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
1390 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
1392 text
= line
->layout
->GetText () + run
->start
;
1394 printf ("\tRun (advance=%f, start=%d, length=%d): \"", run
->advance
, run
->start
, run
->length
);
1395 for (const char *s
= text
; s
< text
+ run
->length
; s
++) {
1398 fputs ("\\r", stdout
);
1401 fputs ("\\n", stdout
);
1404 fputs ("\\t", stdout
);
1407 fputs ("\\\"", stdout
);
1422 static LayoutWordCallback layout_word_behavior
[] = {
1429 TextLayout::Layout ()
1431 TextLayoutAttributes
*attrs
, *nattrs
;
1432 LayoutWordCallback layout_word
;
1433 const char *inptr
, *inend
;
1434 size_t n_bytes
, n_chars
;
1435 TextLayoutLine
*line
;
1444 if (!isnan (actual_width
))
1447 actual_height
= 0.0;
1453 if (!text
|| !(attrs
= (TextLayoutAttributes
*) attributes
->First ()) || attrs
->start
!= 0)
1456 d(printf ("TextLayout::Layout(): wrap mode = %s, wrapping to %f pixels\n", wrap_modes
[wrapping
], max_width
));
1458 if (wrapping
== TextWrappingWrap
)
1459 word
.break_ops
= g_array_new (false, false, sizeof (WordBreakOpportunity
));
1461 word
.break_ops
= NULL
;
1463 layout_word
= layout_word_behavior
[wrapping
];
1465 line
= new TextLayoutLine (this, 0, 0);
1466 if (OverrideLineHeight ())
1467 line
->height
= line_height
;
1469 g_ptr_array_add (lines
, line
);
1473 nattrs
= (TextLayoutAttributes
*) attrs
->next
;
1474 inend
= text
+ (nattrs
? nattrs
->start
: length
);
1475 run
= new TextLayoutRun (line
, attrs
, inptr
- text
);
1476 g_ptr_array_add (line
->runs
, run
);
1478 word
.font
= font
= attrs
->Font ();
1480 if (!OverrideLineHeight ()) {
1481 line
->descend
= MIN (line
->descend
, font
->Descender ());
1482 line
->height
= MAX (line
->height
, font
->Height ());
1485 if (*inptr
== '\0') {
1486 actual_height
+= line
->height
;
1490 // layout until attrs change
1491 while (inptr
< inend
) {
1496 // layout until eoln or until we reach max_width
1497 while (inptr
< inend
) {
1498 // check for line-breaks
1499 if (IsLineBreak (inptr
, inend
- inptr
, &n_bytes
, &n_chars
)) {
1500 line
->length
+= n_bytes
;
1501 run
->length
+= n_bytes
;
1502 line
->count
+= n_chars
;
1503 run
->count
+= n_chars
;
1510 layout_word_init (&word
, line
->advance
, prev
);
1512 // lay out the next word
1513 if (layout_word (&word
, inptr
, inend
, max_width
)) {
1514 // force a line wrap...
1519 if (word
.length
> 0) {
1520 // append the word to the run/line
1521 line
->advance
+= word
.advance
;
1522 run
->advance
+= word
.advance
;
1523 line
->width
= line
->advance
;
1524 line
->length
+= word
.length
;
1525 run
->length
+= word
.length
;
1526 line
->count
+= word
.count
;
1527 run
->count
+= word
.count
;
1529 offset
+= word
.count
;
1530 inptr
+= word
.length
;
1537 // now append any trailing lwsp
1538 layout_word_init (&word
, line
->advance
, prev
);
1540 layout_lwsp (&word
, inptr
, inend
);
1542 if (word
.length
> 0) {
1543 line
->advance
+= word
.advance
;
1544 run
->advance
+= word
.advance
;
1545 line
->length
+= word
.length
;
1546 run
->length
+= word
.length
;
1547 line
->count
+= word
.count
;
1548 run
->count
+= word
.count
;
1550 offset
+= word
.count
;
1551 inptr
+= word
.length
;
1556 if (linebreak
|| wrapped
|| *inptr
== '\0') {
1557 // update actual width extents
1558 if (*inptr
== '\0') {
1559 // ActualWidth extents only include trailing lwsp on the last line
1560 actual_width
= MAX (actual_width
, line
->advance
);
1562 // not the last line, so don't include trailing lwsp
1563 actual_width
= MAX (actual_width
, line
->width
);
1566 // update actual height extents
1567 actual_height
+= line
->height
;
1569 if (linebreak
|| wrapped
) {
1570 // more text to layout... which means we'll need a new line
1571 line
= new TextLayoutLine (this, inptr
- text
, offset
);
1573 if (!OverrideLineHeight ()) {
1574 if (*inptr
== '\0' || inptr
< inend
) {
1575 line
->descend
= font
->Descender ();
1576 line
->height
= font
->Height ();
1579 line
->height
= line_height
;
1582 g_ptr_array_add (lines
, line
);
1586 if (inptr
< inend
) {
1587 if (!OverrideLineHeight ()) {
1588 line
->descend
= font
->Descender ();
1589 line
->height
= font
->Height ();
1592 // more text to layout with the current attrs...
1593 run
= new TextLayoutRun (line
, attrs
, inptr
- text
);
1594 g_ptr_array_add (line
->runs
, run
);
1600 } while (*inptr
!= '\0');
1602 if (word
.break_ops
!= NULL
)
1603 g_array_free (word
.break_ops
, true);
1608 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1609 print_lines (lines
);
1610 printf ("actualWidth = %f, actualHeight = %f\n\n", actual_width
, actual_height
);
1615 static inline TextLayoutGlyphCluster
*
1616 GenerateGlyphCluster (TextFont
*font
, guint32
*kern
, const char *text
, int start
, int length
)
1618 TextLayoutGlyphCluster
*cluster
= new TextLayoutGlyphCluster (start
, length
);
1619 const char *inend
= text
+ start
+ length
;
1620 const char *inptr
= text
+ start
;
1621 guint32 prev
= *kern
;
1627 // set y0 to the baseline
1628 y0
= font
->Ascender ();
1632 // count how many path data items we'll need to allocate
1633 while (inptr
< inend
) {
1634 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
1637 if (UnicharIsLineBreak (c
))
1640 // treat tab as a single space
1644 if (!(glyph
= font
->GetGlyphInfo (c
)))
1648 size
+= glyph
->path
->cairo
.num_data
+ 1;
1652 // generate the cached path for the cluster
1653 cluster
->path
= moon_path_new (size
);
1654 inptr
= text
+ start
;
1656 while (inptr
< inend
) {
1657 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
1660 if (UnicharIsLineBreak (c
))
1663 // treat tab as a single space
1667 if (!(glyph
= font
->GetGlyphInfo (c
)))
1670 if ((prev
!= 0) && APPLY_KERNING (c
))
1671 x0
+= font
->Kerning (prev
, glyph
->index
);
1672 else if (glyph
->metrics
.horiBearingX
< 0)
1673 x0
+= glyph
->metrics
.horiBearingX
;
1675 font
->AppendPath (cluster
->path
, glyph
, x0
, y0
);
1676 x0
+= glyph
->metrics
.horiAdvance
;
1677 prev
= glyph
->index
;
1679 if (!g_unichar_isspace (c
))
1683 moon_close_path (cluster
->path
);
1686 cluster
->uadvance
= x1
;
1687 cluster
->advance
= x0
;
1695 TextLayoutRun::GenerateCache ()
1697 int selection_length
= line
->layout
->GetSelectionLength ();
1698 int selection_start
= line
->layout
->GetSelectionStart ();
1699 const char *text
= line
->layout
->GetText ();
1700 const char *inend
= text
+ start
+ length
;
1701 const char *inptr
= text
+ start
;
1702 TextFont
*font
= attrs
->Font ();
1703 TextLayoutGlyphCluster
*cluster
;
1704 const char *selection_end
;
1708 // cache the glyph cluster leading up to the selection
1709 if (selection_length
== 0 || start
< selection_start
) {
1710 if (selection_length
> 0)
1711 len
= MIN (selection_start
- start
, length
);
1715 cluster
= GenerateGlyphCluster (font
, &prev
, text
, start
, len
);
1716 g_ptr_array_add (clusters
, cluster
);
1720 // cache the selected glyph cluster
1721 selection_end
= text
+ selection_start
+ selection_length
;
1722 if (inptr
< inend
&& inptr
< selection_end
) {
1723 len
= MIN (inend
, selection_end
) - inptr
;
1724 cluster
= GenerateGlyphCluster (font
, &prev
, text
, inptr
- text
, len
);
1725 g_ptr_array_add (clusters
, cluster
);
1726 cluster
->selected
= true;
1730 // cache the glyph cluster following the selection
1731 if (inptr
< inend
) {
1732 cluster
= GenerateGlyphCluster (font
, &prev
, text
, inptr
- text
, inend
- inptr
);
1733 g_ptr_array_add (clusters
, cluster
);
1739 TextLayoutGlyphCluster::Render (cairo_t
*cr
, const Point
&origin
, TextLayoutAttributes
*attrs
, const char *text
, double x
, double y
, bool uline_full
)
1741 TextFont
*font
= attrs
->Font ();
1742 const char *inend
, *prev
;
1752 // y is the baseline, set the origin to the top-left
1753 cairo_translate (cr
, x
, y
- font
->Ascender ());
1755 // set y0 to the baseline relative to the translation matrix
1756 y0
= font
->Ascender ();
1759 area
= Rect (origin
.x
, origin
.y
, advance
, font
->Height ());
1761 // extend the selection background by the width of a SPACE if it includes CRLF
1762 inend
= text
+ start
+ length
;
1763 if ((prev
= g_utf8_find_prev_char (text
+ start
, inend
)))
1764 c
= utf8_getc (&prev
, inend
- prev
);
1768 if (UnicharIsLineBreak (c
)) {
1769 if ((glyph
= font
->GetGlyphInfo (' ')))
1770 area
.width
+= glyph
->metrics
.horiAdvance
;
1773 // render the selection background
1774 brush
= attrs
->Background (true);
1775 brush
->SetupBrush (cr
, area
);
1776 cairo_new_path (cr
);
1777 cairo_rectangle (cr
, area
.x
, area
.y
, area
.width
, area
.height
);
1781 // setup the foreground brush
1782 area
= Rect (origin
.x
, origin
.y
, advance
, font
->Height ());
1783 brush
= attrs
->Foreground (selected
);
1784 brush
->SetupBrush (cr
, area
);
1785 cairo_new_path (cr
);
1787 if (path
&& path
->cairo
.data
)
1788 cairo_append_path (cr
, &path
->cairo
);
1792 if (attrs
->IsUnderlined ()) {
1793 double thickness
= font
->UnderlineThickness ();
1794 double pos
= y0
+ font
->UnderlinePosition ();
1796 cairo_set_line_width (cr
, thickness
);
1798 cairo_new_path (cr
);
1799 Rect underline
= Rect (0.0, pos
- thickness
* 0.5, uline_full
? advance
: uadvance
, thickness
);
1800 underline
.Draw (cr
);
1807 TextLayoutRun::Render (cairo_t
*cr
, const Point
&origin
, double x
, double y
, bool is_last_run
)
1809 const char *text
= line
->layout
->GetText ();
1810 TextLayoutGlyphCluster
*cluster
;
1813 if (clusters
->len
== 0)
1816 for (guint i
= 0; i
< clusters
->len
; i
++) {
1817 cluster
= (TextLayoutGlyphCluster
*) clusters
->pdata
[i
];
1820 cluster
->Render (cr
, origin
, attrs
, text
, x0
, y
, is_last_run
&& ((i
+ 1) < clusters
->len
));
1823 x0
+= cluster
->advance
;
1828 TextLayoutLine::Render (cairo_t
*cr
, const Point
&origin
, double left
, double top
)
1833 // set y0 to the line's baseline (descend is a negative value)
1834 y0
= top
+ height
+ descend
;
1837 for (guint i
= 0; i
< runs
->len
; i
++) {
1838 run
= (TextLayoutRun
*) runs
->pdata
[i
];
1839 run
->Render (cr
, origin
, x0
, y0
, (i
+ 1) < runs
->len
);
1845 GetWidthConstraint (double avail_width
, double max_width
, double actual_width
)
1847 if (isinf (avail_width
)) {
1848 // find an upper width constraint
1849 if (isinf (max_width
))
1850 return actual_width
;
1859 TextLayout::HorizontalAlignment (double line_width
)
1864 switch (alignment
) {
1865 case TextAlignmentCenter
:
1866 width
= GetWidthConstraint (avail_width
, max_width
, actual_width
);
1867 if (line_width
< width
)
1868 deltax
= (width
- line_width
) / 2.0;
1872 case TextAlignmentRight
:
1873 width
= GetWidthConstraint (avail_width
, max_width
, actual_width
);
1874 if (line_width
< width
)
1875 deltax
= width
- line_width
;
1888 TextLayout::Render (cairo_t
*cr
, const Point
&origin
, const Point
&offset
)
1890 TextLayoutLine
*line
;
1897 for (guint i
= 0; i
< lines
->len
; i
++) {
1898 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1900 x
= offset
.x
+ HorizontalAlignment (line
->advance
);
1901 line
->Render (cr
, origin
, x
, y
);
1902 y
+= (double) line
->height
;
1906 #ifdef USE_BINARY_SEARCH
1909 * @lo: the low bound
1910 * @hi: the high bound
1912 * Finds the midpoint between positive integer values, @lo and @hi.
1914 * Notes: Typically expressed as '(@lo + @hi) / 2', this is incorrect
1915 * when @lo and @hi are sufficiently large enough that combining them
1916 * would overflow their integer type. To work around this, we use the
1917 * formula, '@lo + ((@hi - @lo) / 2)', thus preventing this problem
1920 * Returns the midpoint between @lo and @hi (rounded down).
1922 #define MID(lo, hi) (lo + ((hi - lo) >> 1))
1925 TextLayout::GetLineFromY (const Point
&offset
, double y
, int *index
)
1927 register guint lo
, hi
;
1928 TextLayoutLine
*line
;
1932 if (lines
->len
== 0)
1935 lo
= 0, hi
= lines
->len
;
1941 line
= (TextLayoutLine
*) lines
->pdata
[m
];
1943 if (m
> 0 && y0
< line
->top
) {
1944 // y is on some line above us
1946 } else if (y0
> line
->top
+ line
->height
) {
1947 // y is on some line below us
1951 // y is on this line
1963 #else /* linear search */
1965 TextLayout::GetLineFromY (const Point
&offset
, double y
, int *index
)
1967 TextLayoutLine
*line
= NULL
;
1970 //printf ("TextLayout::GetLineFromY (%.2g)\n", y);
1974 for (guint i
= 0; i
< lines
->len
; i
++) {
1975 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1977 // set y1 the top of the next line
1978 y1
= y0
+ line
->height
;
1981 // we found the line that the point is located on
1996 TextLayout::GetLineFromIndex (int index
)
1998 if (index
>= (int) lines
->len
|| index
< 0)
2001 return (TextLayoutLine
*) lines
->pdata
[index
];
2005 TextLayoutLine::GetCursorFromX (const Point
&offset
, double x
)
2007 const char *text
, *inend
, *ch
, *inptr
;
2008 TextLayoutRun
*run
= NULL
;
2018 // adjust x0 for horizontal alignment
2019 x0
= offset
.x
+ layout
->HorizontalAlignment (advance
);
2021 text
= layout
->GetText ();
2022 inptr
= text
+ start
;
2023 cursor
= this->offset
;
2025 for (i
= 0; i
< runs
->len
; i
++) {
2026 run
= (TextLayoutRun
*) runs
->pdata
[i
];
2028 if (x
< x0
+ run
->advance
) {
2029 // x is in somewhere inside this run
2033 // x is beyond this run
2034 cursor
+= run
->count
;
2035 inptr
+= run
->length
;
2041 inptr
= text
+ run
->start
;
2042 inend
= inptr
+ run
->length
;
2043 font
= run
->attrs
->Font ();
2045 while (inptr
< inend
) {
2047 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
2050 if (UnicharIsLineBreak (c
)) {
2057 // we treat tabs as a single space
2061 if (!(glyph
= font
->GetGlyphInfo (c
)))
2064 if ((prev
!= 0) && APPLY_KERNING (c
))
2065 x0
+= font
->Kerning (prev
, glyph
->index
);
2066 else if (glyph
->metrics
.horiBearingX
< 0)
2067 x0
+= glyph
->metrics
.horiBearingX
;
2069 // calculate midpoint of the character
2070 m
= glyph
->metrics
.horiAdvance
/ 2.0;
2072 // if x is <= the midpoint, then the cursor is
2073 // considered to be at the start of this character.
2080 x0
+= glyph
->metrics
.horiAdvance
;
2081 prev
= glyph
->index
;
2084 // x is beyond the end of the last run
2085 run
= (TextLayoutRun
*) runs
->pdata
[i
- 1];
2086 inend
= text
+ run
->start
+ run
->length
;
2087 inptr
= text
+ run
->start
;
2089 if ((ch
= g_utf8_find_prev_char (inptr
, inend
)))
2090 c
= utf8_getc (&ch
, inend
- ch
);
2098 if (inend
> inptr
&& inend
[-1] == '\r') {
2102 } else if (UnicharIsLineBreak (c
)) {
2112 TextLayout::GetCursorFromXY (const Point
&offset
, double x
, double y
)
2114 TextLayoutLine
*line
;
2116 //printf ("TextLayout::GetCursorFromXY (%.2g, %.2g)\n", x, y);
2121 if (!(line
= GetLineFromY (offset
, y
)))
2124 return line
->GetCursorFromX (offset
, x
);
2128 TextLayout::GetCursor (const Point
&offset
, int index
)
2130 const char *inptr
, *inend
, *pchar
;
2131 double height
, x0
, y0
, y1
;
2132 TextLayoutLine
*line
;
2140 //printf ("TextLayout::GetCursor (%d)\n", index);
2147 for (guint i
= 0; i
< lines
->len
; i
++) {
2148 line
= (TextLayoutLine
*) lines
->pdata
[i
];
2149 inend
= text
+ line
->start
+ line
->length
;
2151 // adjust x0 for horizontal alignment
2152 x0
= offset
.x
+ HorizontalAlignment (line
->advance
);
2154 // set y1 to the baseline (descend is a negative value)
2155 y1
= y0
+ line
->height
+ line
->descend
;
2156 height
= line
->height
;
2158 //printf ("\tline: left=%.2f, top=%.2f, baseline=%.2f, start index=%d\n", x0, y0, y1, line->offset);
2160 if (index
>= cursor
+ line
->count
) {
2161 // maybe the cursor is on the next line...
2162 if ((i
+ 1) == lines
->len
) {
2163 // we are on the last line... get the previous unichar
2164 inptr
= text
+ line
->start
;
2165 inend
= inptr
+ line
->length
;
2167 if ((pchar
= g_utf8_find_prev_char (text
+ line
->start
, inend
)))
2168 c
= utf8_getc (&pchar
, inend
- pchar
);
2172 if (UnicharIsLineBreak (c
)) {
2173 // cursor is on the next line by itself
2174 x0
= offset
.x
+ HorizontalAlignment (0.0);
2177 // cursor at the end of the last line
2178 x0
+= line
->advance
;
2184 cursor
+= line
->count
;
2189 // cursor is on this line...
2190 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
2191 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
2192 inend
= text
+ run
->start
+ run
->length
;
2194 if (index
>= cursor
+ run
->count
) {
2195 // maybe the cursor is in the next run...
2196 cursor
+= run
->count
;
2201 // cursor is in this run...
2202 font
= run
->attrs
->Font ();
2203 inptr
= text
+ run
->start
;
2206 while (cursor
< index
) {
2207 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
2212 // we treat tabs as a single space
2216 if (!(glyph
= font
->GetGlyphInfo (c
)))
2219 if ((prev
!= 0) && APPLY_KERNING (c
))
2220 x0
+= font
->Kerning (prev
, glyph
->index
);
2221 else if (glyph
->metrics
.horiBearingX
< 0)
2222 x0
+= glyph
->metrics
.horiBearingX
;
2224 x0
+= glyph
->metrics
.horiAdvance
;
2225 prev
= glyph
->index
;
2234 return Rect (x0
, y0
, 1.0, height
);