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
;
265 lines
= g_ptr_array_new ();
272 TextLayout::~TextLayout ()
275 attributes
->Clear (true);
280 g_ptr_array_free (lines
, true);
286 TextLayout::ClearLines ()
288 for (guint i
= 0; i
< lines
->len
; i
++)
289 delete (TextLayoutLine
*) lines
->pdata
[i
];
291 g_ptr_array_set_size (lines
, 0);
295 TextLayout::ResetState ()
302 TextLayout::SetLineStackingStrategy (LineStackingStrategy mode
)
304 if (strategy
== mode
)
315 TextLayout::SetTextAlignment (TextAlignment align
)
317 if (alignment
== align
)
326 TextLayout::SetTextWrapping (TextWrapping mode
)
329 case TextWrappingNoWrap
:
330 case TextWrappingWrap
:
333 // Silverlight defaults to Wrap for unknown values
334 mode
= TextWrappingWrap
;
338 if (wrapping
== mode
)
349 TextLayout::SetLineHeight (double height
)
351 if (line_height
== height
)
354 line_height
= height
;
362 TextLayout::SetMaxHeight (double height
)
364 if (max_height
== height
)
375 TextLayout::SetMaxWidth (double width
)
380 if (max_width
== width
)
383 if (!is_wrapped
&& (isinf (width
) || width
> actual_width
)) {
384 // the new max_width won't change layout
397 TextLayout::SetTextAttributes (List
*attrs
)
400 attributes
->Clear (true);
412 TextLayout::SetText (const char *str
, int len
)
417 length
= len
== -1 ? strlen (str
) : len
;
418 text
= (char *) g_malloc (length
+ 1);
419 memcpy (text
, str
, length
);
434 TextLayout::ClearCache ()
436 TextLayoutLine
*line
;
439 for (guint i
= 0; i
< lines
->len
; i
++) {
440 line
= (TextLayoutLine
*) lines
->pdata
[i
];
442 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
443 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
455 UpdateSelection (GPtrArray
*lines
, TextRegion
*pre
, TextRegion
*post
)
457 TextLayoutGlyphCluster
*cluster
;
458 TextLayoutLine
*line
;
462 // first update pre-region
463 for (i
= 0; i
< lines
->len
; i
++) {
464 line
= (TextLayoutLine
*) lines
->pdata
[i
];
466 if (pre
->start
>= line
->start
+ line
->length
) {
467 // pre-region not on this line...
471 for (j
= 0; j
< line
->runs
->len
; j
++) {
472 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
474 if (pre
->start
>= run
->start
+ run
->length
) {
475 // pre-region not in this run...
479 if (pre
->start
<= run
->start
) {
480 if (pre
->start
+ pre
->length
>= run
->start
+ run
->length
) {
481 // run is fully contained within the pre-region
482 if (run
->clusters
->len
== 1) {
483 cluster
= (TextLayoutGlyphCluster
*) run
->clusters
->pdata
[0];
484 cluster
->selected
= pre
->select
;
495 if (pre
->start
+ pre
->length
<= run
->start
+ run
->length
)
500 // now update the post region...
501 for ( ; i
< lines
->len
; i
++, j
= 0) {
502 line
= (TextLayoutLine
*) lines
->pdata
[i
];
504 if (post
->start
>= line
->start
+ line
->length
) {
505 // pre-region not on this line...
509 for ( ; j
< line
->runs
->len
; j
++) {
510 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
512 if (post
->start
>= run
->start
+ run
->length
) {
513 // post-region not in this run...
517 if (post
->start
<= run
->start
) {
518 if (post
->start
+ post
->length
>= run
->start
+ run
->length
) {
519 // run is fully contained within the pre-region
520 if (run
->clusters
->len
== 1) {
521 cluster
= (TextLayoutGlyphCluster
*) run
->clusters
->pdata
[0];
522 cluster
->selected
= post
->select
;
533 if (post
->start
+ post
->length
<= run
->start
+ run
->length
)
540 TextLayout::Select (int start
, int length
, bool byte_offsets
)
542 int new_selection_length
;
543 int new_selection_start
;
544 int new_selection_end
;
546 TextRegion pre
, post
;
551 selection_length
= 0;
557 inptr
= g_utf8_offset_to_pointer (text
, start
);
558 new_selection_start
= inptr
- text
;
560 inend
= g_utf8_offset_to_pointer (inptr
, length
);
561 new_selection_length
= inend
- inptr
;
563 new_selection_length
= length
;
564 new_selection_start
= start
;
567 if (selection_start
== new_selection_start
&&
568 selection_length
== new_selection_length
) {
569 // no change in selection...
574 // compute the region between the 2 starts
575 pre
.length
= abs (new_selection_start
- selection_start
);
576 pre
.start
= MIN (selection_start
, new_selection_start
);
577 pre
.select
= (new_selection_start
< selection_start
) && (new_selection_length
> 0);
579 // compute the region between the 2 ends
580 new_selection_end
= new_selection_start
+ new_selection_length
;
581 selection_end
= selection_start
+ selection_length
;
582 post
.length
= abs (new_selection_end
- selection_end
);
583 post
.start
= MIN (selection_end
, new_selection_end
);
584 post
.select
= (new_selection_end
> selection_end
) && (new_selection_length
> 0);
586 UpdateSelection (lines
, &pre
, &post
);
588 selection_length
= new_selection_length
;
589 selection_start
= new_selection_start
;
591 if (selection_length
|| new_selection_length
)
594 selection_length
= new_selection_length
;
595 selection_start
= new_selection_start
;
600 * TextLayout::GetActualExtents:
604 * Gets the actual width and height extents required for rendering the
608 TextLayout::GetActualExtents (double *width
, double *height
)
610 *height
= actual_height
;
611 *width
= actual_width
;
616 unichar_combining_class (gunichar c
)
618 #if GLIB_CHECK_VERSION (2,14,0)
619 if (glib_check_version (2,14,0))
620 return g_unichar_combining_class (c
);
629 WORD_TYPE_ALPHABETIC
,
630 WORD_TYPE_IDEOGRAPHIC
,
631 WORD_TYPE_INSEPARABLE
,
636 struct WordBreakOpportunity
{
637 GUnicodeBreakType btype
;
647 GArray
*break_ops
; // TextWrappingWrap only
656 GlyphInfo
*prev
; // previous glyph; 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
, GlyphInfo
*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 GlyphInfo
*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
!= NULL
) && APPLY_KERNING (c
))
762 advance
+= word
->font
->Kerning (prev
, glyph
);
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 GlyphInfo
*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
!= NULL
) && APPLY_KERNING (c
))
837 advance
+= word
->font
->Kerning (prev
, glyph
);
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 // compare this character's word-type against the current word-type
885 if ((type
= word_type (ctype
, btype
)) == wtype
)
888 if (type
== WORD_TYPE_UNKNOWN
)
891 // word-types not identical... check if they are compatible
893 case WORD_TYPE_ALPHABETIC
:
894 return type
!= WORD_TYPE_NUMERIC
;
896 case WORD_TYPE_IDEOGRAPHIC
:
897 // this fixes drt #411 but breaks drt #208. I can't win.
898 return type
!= WORD_TYPE_ALPHABETIC
;
910 * @inend = end of word
911 * @max_width: max allowable width for a line
913 * Calculates the advance of the current word, breaking if needed.
915 * Returns: %true if the caller should create a new line for the
916 * remainder of the word or %false otherwise.
919 layout_word_wrap (LayoutWord
*word
, const char *in
, const char *inend
, double max_width
)
921 GUnicodeBreakType btype
= G_UNICODE_BREAK_UNKNOWN
;
922 bool line_start
= word
->line_advance
== 0.0;
923 GlyphInfo
*prev
= word
->prev
;
924 WordBreakOpportunity op
;
925 const char *inptr
= in
;
940 g_array_set_size (word
->break_ops
, 0);
941 word
->type
= WORD_TYPE_UNKNOWN
;
945 d(printf ("layout_word_wrap():\n"));
946 d(debug
= g_string_new (""));
948 while (inptr
< inend
) {
950 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
951 // ignore invalid chars
955 if (UnicharIsLineBreak (c
)) {
960 // check the previous break-type
961 if (btype
== G_UNICODE_BREAK_CLOSE_PUNCTUATION
) {
962 // if anything other than an infix separator come after a close-punctuation, then the 'word' is done
963 btype
= g_unichar_break_type (c
);
964 if (btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
) {
968 } else if (btype
== G_UNICODE_BREAK_INFIX_SEPARATOR
) {
969 btype
= g_unichar_break_type (c
);
970 if (word
->type
== WORD_TYPE_NUMERIC
) {
971 // only accept numbers after the infix
972 if (btype
!= G_UNICODE_BREAK_NUMERIC
) {
976 } else if (word
->type
== WORD_TYPE_UNKNOWN
) {
977 // only accept alphanumerics after the infix
978 if (btype
!= G_UNICODE_BREAK_ALPHABETIC
&& btype
!= G_UNICODE_BREAK_NUMERIC
) {
985 } else if (btype
== G_UNICODE_BREAK_WORD_JOINER
) {
986 btype
= g_unichar_break_type (c
);
989 btype
= g_unichar_break_type (c
);
992 if (BreakSpace (c
, btype
)) {
997 ctype
= g_unichar_type (c
);
999 if (word
->type
== WORD_TYPE_UNKNOWN
) {
1000 // record our word-type
1001 word
->type
= word_type (ctype
, btype
);
1002 } else if (btype
== G_UNICODE_BREAK_OPEN_PUNCTUATION
) {
1003 // this is a good place to break
1006 } else if (word_type_changed (word
->type
, c
, ctype
, btype
)) {
1007 // changing word-types, don't continue
1012 d(g_string_append_unichar (debug
, c
));
1015 // a Combining Class of 0 means start of a new glyph
1016 if (glyphs
> 0 && unichar_combining_class (c
) != 0) {
1017 // this char gets combined with the previous glyph
1025 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1026 if (c
< 128 && isprint ((int) c
))
1027 printf ("\tunichar = %c; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", (char) c
,
1028 unicode_break_types
[btype
], new_glyph
? "true" : "false", unichar_combining_class (c
),
1029 unicode_char_types
[ctype
]);
1031 printf ("\tunichar = 0x%.4X; btype = %s, new glyph = %s; cc = %d; ctype = %s\n", c
,
1032 unicode_break_types
[btype
], new_glyph
? "true" : "false", unichar_combining_class (c
),
1033 unicode_char_types
[ctype
]);
1037 if ((glyph
= word
->font
->GetGlyphInfo (c
))) {
1038 // calculate total glyph advance
1039 advance
= glyph
->metrics
.horiAdvance
;
1040 if ((prev
!= NULL
) && APPLY_KERNING (c
))
1041 advance
+= word
->font
->Kerning (prev
, glyph
);
1042 else if (glyph
->metrics
.horiBearingX
< 0)
1043 advance
-= glyph
->metrics
.horiBearingX
;
1045 word
->line_advance
+= advance
;
1046 word
->advance
+= advance
;
1053 op
.index
= glyph
? glyph
->index
: 0;
1054 op
.advance
= word
->advance
;
1055 op
.count
= word
->count
;
1060 g_array_remove_index (word
->break_ops
, word
->break_ops
->len
- 1);
1061 op
.advance
+= advance
;
1066 g_array_append_val (word
->break_ops
, op
);
1068 if (!isinf (max_width
) && word
->line_advance
> max_width
) {
1069 d(printf ("\tjust exceeded max width (%fpx): %s\n", max_width
, debug
->str
));
1076 d(g_string_free (debug
, true));
1077 word
->length
= (inptr
- in
);
1082 d(printf ("\tcollecting any/all decomposed chars and figuring out the break-type for the next glyph\n"));
1084 // pretend btype is SPACE here in case inptr is at the end of the run
1086 btype
= G_UNICODE_BREAK_SPACE
;
1088 // keep going until we reach a new distinct glyph. we also
1089 // need to know the btype of the char after the char that
1090 // exceeded the width limit.
1091 while (inptr
< inend
) {
1093 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1) {
1094 // ignore invalid chars
1098 if (UnicharIsLineBreak (c
)) {
1099 btype
= G_UNICODE_BREAK_SPACE
;
1104 btype
= g_unichar_break_type (c
);
1105 if (BreakSpace (c
, btype
) || unichar_combining_class (c
) == 0) {
1110 d(g_string_append_unichar (debug
, c
));
1113 if ((glyph
= word
->font
->GetGlyphInfo (c
))) {
1114 // calculate total glyph advance
1115 advance
= glyph
->metrics
.horiAdvance
;
1116 if ((prev
!= NULL
) && APPLY_KERNING (c
))
1117 advance
+= word
->font
->Kerning (prev
, glyph
);
1118 else if (glyph
->metrics
.horiBearingX
< 0)
1119 advance
-= glyph
->metrics
.horiBearingX
;
1121 word
->line_advance
+= advance
;
1122 word
->advance
+= advance
;
1128 g_array_remove_index (word
->break_ops
, word
->break_ops
->len
- 1);
1129 op
.advance
+= advance
;
1132 g_array_append_val (word
->break_ops
, op
);
1135 d(printf ("\tok, at this point we have: %s\n", debug
->str
));
1136 d(printf ("\tnext break-type is %s\n", unicode_break_types
[btype
]));
1137 d(g_string_free (debug
, true));
1139 // at this point, we're going to break the word so we can reset kerning
1142 // we can't break any smaller than a single glyph
1143 if (line_start
&& glyphs
== 1) {
1144 d(printf ("\tsince this is the first glyph on the line, can't break any smaller than that...\n"));
1145 word
->length
= (inptr
- in
);
1152 // search backwards for the best break point
1153 d(printf ("\tscanning over %d break opportunities...\n", word
->break_ops
->len
));
1154 for (guint i
= word
->break_ops
->len
; i
> 0; i
--) {
1155 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 1);
1158 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1159 if (op
.c
< 128 && isprint ((int) op
.c
))
1160 printf ("\tunichar = %c; btype = %s; i = %d\n", (char) op
.c
, unicode_break_types
[op
.btype
], i
);
1162 printf ("\tunichar = 0x%.4X; btype = %s; i = %d\n", op
.c
, unicode_break_types
[op
.btype
], i
);
1167 case G_UNICODE_BREAK_BEFORE_AND_AFTER
:
1168 if (i
> 1 && i
== word
->break_ops
->len
) {
1169 // break after the previous glyph
1170 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1171 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1172 word
->length
= (op
.inptr
- in
);
1173 word
->advance
= op
.advance
;
1174 word
->count
= op
.count
;
1177 } else if (i
< word
->break_ops
->len
) {
1178 // break after this glyph
1179 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1180 word
->length
= (op
.inptr
- in
);
1181 word
->advance
= op
.advance
;
1182 word
->count
= op
.count
;
1186 case G_UNICODE_BREAK_NON_BREAKING_GLUE
:
1187 case G_UNICODE_BREAK_WORD_JOINER
:
1188 // cannot break before or after this character (unless forced)
1189 if (force
&& i
< word
->break_ops
->len
) {
1190 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1191 word
->length
= (op
.inptr
- in
);
1192 word
->advance
= op
.advance
;
1193 word
->count
= op
.count
;
1199 // skip past previous glyph
1200 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1204 case G_UNICODE_BREAK_INSEPARABLE
:
1205 // only restriction is no breaking between inseparables unless we have to
1206 if (line_start
&& i
< word
->break_ops
->len
) {
1207 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1208 word
->length
= (op
.inptr
- in
);
1209 word
->advance
= op
.advance
;
1210 word
->count
= op
.count
;
1215 case G_UNICODE_BREAK_BEFORE
:
1217 // break after the previous glyph
1218 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1219 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1220 word
->length
= (op
.inptr
- in
);
1221 word
->advance
= op
.advance
;
1222 word
->count
= op
.count
;
1227 case G_UNICODE_BREAK_CLOSE_PUNCTUATION
:
1228 if (i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)) {
1229 // we can safely break after this character
1230 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1231 word
->length
= (op
.inptr
- in
);
1232 word
->advance
= op
.advance
;
1233 word
->count
= op
.count
;
1238 if (i
> 1 && !force
) {
1239 // we can never break before a closing punctuation, so skip past prev char
1240 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1244 case G_UNICODE_BREAK_INFIX_SEPARATOR
:
1245 if (i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_NUMERIC
)) {
1246 // we can safely break after this character
1247 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1248 word
->length
= (op
.inptr
- in
);
1249 word
->advance
= op
.advance
;
1250 word
->count
= op
.count
;
1255 if (i
> 1 && !force
) {
1256 // we can never break before an infix, skip past prev char
1257 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 2);
1258 if (op
.btype
== G_UNICODE_BREAK_INFIX_SEPARATOR
||
1259 op
.btype
== G_UNICODE_BREAK_CLOSE_PUNCTUATION
) {
1260 // unless previous char is one of these special types...
1261 op
= g_array_index (word
->break_ops
, WordBreakOpportunity
, i
- 1);
1267 case G_UNICODE_BREAK_ALPHABETIC
:
1268 // only break if we have no choice...
1269 if ((line_start
|| fixed
|| force
) && i
< word
->break_ops
->len
) {
1270 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1271 word
->length
= (op
.inptr
- in
);
1272 word
->advance
= op
.advance
;
1273 word
->count
= op
.count
;
1278 case G_UNICODE_BREAK_IDEOGRAPHIC
:
1279 if (i
< word
->break_ops
->len
&& btype
!= G_UNICODE_BREAK_NON_STARTER
) {
1280 // we can safely break after this character
1281 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1282 word
->length
= (op
.inptr
- in
);
1283 word
->advance
= op
.advance
;
1284 word
->count
= op
.count
;
1289 case G_UNICODE_BREAK_NUMERIC
:
1290 // only break if we have no choice...
1291 if (line_start
&& i
< word
->break_ops
->len
&& (force
|| btype
!= G_UNICODE_BREAK_INFIX_SEPARATOR
)) {
1292 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1293 word
->length
= (op
.inptr
- in
);
1294 word
->advance
= op
.advance
;
1295 word
->count
= op
.count
;
1300 case G_UNICODE_BREAK_OPEN_PUNCTUATION
:
1301 case G_UNICODE_BREAK_COMBINING_MARK
:
1302 case G_UNICODE_BREAK_CONTINGENT
:
1303 case G_UNICODE_BREAK_AMBIGUOUS
:
1304 case G_UNICODE_BREAK_QUOTATION
:
1305 case G_UNICODE_BREAK_PREFIX
:
1306 // do not break after characters with these break-types (unless forced)
1307 if (force
&& i
< word
->break_ops
->len
) {
1308 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1309 word
->length
= (op
.inptr
- in
);
1310 word
->advance
= op
.advance
;
1311 word
->count
= op
.count
;
1317 d(printf ("Unhandled Unicode break-type: %s\n", unicode_break_types
[op
.btype
]));
1318 // fall thru to the "default" behavior
1320 #if GLIB_CHECK_VERSION (2,10,0)
1321 case G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE
:
1322 case G_UNICODE_BREAK_HANGUL_LV_SYLLABLE
:
1323 case G_UNICODE_BREAK_HANGUL_L_JAMO
:
1324 case G_UNICODE_BREAK_HANGUL_V_JAMO
:
1325 case G_UNICODE_BREAK_HANGUL_T_JAMO
:
1327 case G_UNICODE_BREAK_NON_STARTER
:
1328 case G_UNICODE_BREAK_EXCLAMATION
:
1329 case G_UNICODE_BREAK_MANDATORY
:
1330 case G_UNICODE_BREAK_NEXT_LINE
:
1331 case G_UNICODE_BREAK_UNKNOWN
:
1332 case G_UNICODE_BREAK_POSTFIX
:
1333 case G_UNICODE_BREAK_HYPHEN
:
1334 case G_UNICODE_BREAK_AFTER
:
1335 if (i
< word
->break_ops
->len
) {
1336 // we can safely break after this character
1337 word
->prev
= word
->font
->GetGlyphInfo (op
.c
);
1338 word
->length
= (op
.inptr
- in
);
1339 word
->advance
= op
.advance
;
1340 word
->count
= op
.count
;
1351 if (line_start
&& !force
) {
1352 d(printf ("\tcouldn't find a good place to break but we must force a break, retrying...\n"));
1357 d(printf ("\tcouldn't find a good place to break, defaulting to breaking before the word start\n"));
1359 word
->advance
= 0.0;
1367 static const char *wrap_modes
[3] = {
1374 print_lines (GPtrArray
*lines
)
1376 TextLayoutLine
*line
;
1381 for (guint i
= 0; i
< lines
->len
; i
++) {
1382 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1384 printf ("Line (top=%f, height=%f, advance=%f, offset=%d, count=%d):\n", y
, line
->height
, line
->advance
, line
->offset
, line
->count
);
1385 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
1386 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
1388 text
= line
->layout
->GetText () + run
->start
;
1390 printf ("\tRun (advance=%f, start=%d, length=%d): \"", run
->advance
, run
->start
, run
->length
);
1391 for (const char *s
= text
; s
< text
+ run
->length
; s
++) {
1394 fputs ("\\r", stdout
);
1397 fputs ("\\n", stdout
);
1400 fputs ("\\t", stdout
);
1403 fputs ("\\\"", stdout
);
1418 static LayoutWordCallback layout_word_behavior
[] = {
1425 validate_attrs (List
*attributes
)
1427 TextLayoutAttributes
*attrs
;
1429 // if no attributes or first attribute doesn't start at 0, we can't layout any text
1430 if (!(attrs
= (TextLayoutAttributes
*) attributes
->First ()) || attrs
->start
!= 0)
1433 while (attrs
!= NULL
) {
1434 if (!attrs
->Font ()) {
1435 // we can't layout any text if any of the attributes
1436 // weren't able to load their font
1440 attrs
= (TextLayoutAttributes
*) attrs
->next
;
1447 TextLayout::Layout ()
1449 TextLayoutAttributes
*attrs
, *nattrs
;
1450 LayoutWordCallback layout_word
;
1451 const char *inptr
, *inend
;
1452 size_t n_bytes
, n_chars
;
1453 TextLayoutLine
*line
;
1462 if (!isnan (actual_width
))
1465 actual_height
= 0.0;
1471 if (!text
|| !validate_attrs (attributes
))
1474 d(printf ("TextLayout::Layout(): wrap mode = %s, wrapping to %f pixels\n", wrap_modes
[wrapping
], max_width
));
1476 if (wrapping
== TextWrappingWrap
)
1477 word
.break_ops
= g_array_new (false, false, sizeof (WordBreakOpportunity
));
1479 word
.break_ops
= NULL
;
1481 layout_word
= layout_word_behavior
[wrapping
];
1483 attrs
= (TextLayoutAttributes
*) attributes
->First ();
1484 line
= new TextLayoutLine (this, 0, 0);
1485 if (OverrideLineHeight ())
1486 line
->height
= line_height
;
1488 g_ptr_array_add (lines
, line
);
1492 nattrs
= (TextLayoutAttributes
*) attrs
->next
;
1493 inend
= text
+ (nattrs
? nattrs
->start
: length
);
1494 run
= new TextLayoutRun (line
, attrs
, inptr
- text
);
1495 g_ptr_array_add (line
->runs
, run
);
1497 word
.font
= font
= attrs
->Font ();
1499 //if (!OverrideLineHeight ()) {
1500 // line->descend = MIN (line->descend, font->Descender ());
1501 // line->height = MAX (line->height, font->Height ());
1504 if (*inptr
== '\0') {
1505 if (!OverrideLineHeight ()) {
1506 line
->descend
= MIN (line
->descend
, font
->Descender ());
1507 line
->height
= MAX (line
->height
, font
->Height ());
1510 actual_height
+= line
->height
;
1514 // layout until attrs change
1515 while (inptr
< inend
) {
1520 // layout until eoln or until we reach max_width
1521 while (inptr
< inend
) {
1522 // check for line-breaks
1523 if (IsLineBreak (inptr
, inend
- inptr
, &n_bytes
, &n_chars
)) {
1524 if (line
->length
== 0 && !OverrideLineHeight ()) {
1525 line
->descend
= font
->Descender ();
1526 line
->height
= font
->Height ();
1529 line
->length
+= n_bytes
;
1530 run
->length
+= n_bytes
;
1531 line
->count
+= n_chars
;
1532 run
->count
+= n_chars
;
1539 layout_word_init (&word
, line
->advance
, prev
);
1541 // lay out the next word
1542 if (layout_word (&word
, inptr
, inend
, max_width
)) {
1543 // force a line wrap...
1548 if (word
.length
> 0) {
1549 // append the word to the run/line
1550 if (!OverrideLineHeight ()) {
1551 line
->descend
= MIN (line
->descend
, font
->Descender ());
1552 line
->height
= MAX (line
->height
, font
->Height ());
1555 line
->advance
+= word
.advance
;
1556 run
->advance
+= word
.advance
;
1557 line
->width
= line
->advance
;
1558 line
->length
+= word
.length
;
1559 run
->length
+= word
.length
;
1560 line
->count
+= word
.count
;
1561 run
->count
+= word
.count
;
1563 offset
+= word
.count
;
1564 inptr
+= word
.length
;
1571 // now append any trailing lwsp
1572 layout_word_init (&word
, line
->advance
, prev
);
1574 layout_lwsp (&word
, inptr
, inend
);
1576 if (word
.length
> 0) {
1577 if (!OverrideLineHeight ()) {
1578 line
->descend
= MIN (line
->descend
, font
->Descender ());
1579 line
->height
= MAX (line
->height
, font
->Height ());
1582 line
->advance
+= word
.advance
;
1583 run
->advance
+= word
.advance
;
1584 line
->length
+= word
.length
;
1585 run
->length
+= word
.length
;
1586 line
->count
+= word
.count
;
1587 run
->count
+= word
.count
;
1589 offset
+= word
.count
;
1590 inptr
+= word
.length
;
1595 if (linebreak
|| wrapped
|| *inptr
== '\0') {
1596 // update actual width extents
1597 if (*inptr
== '\0') {
1598 // ActualWidth extents only include trailing lwsp on the last line
1599 actual_width
= MAX (actual_width
, line
->advance
);
1601 // not the last line, so don't include trailing lwsp
1602 actual_width
= MAX (actual_width
, line
->width
);
1605 // update actual height extents
1606 actual_height
+= line
->height
;
1608 if (linebreak
|| wrapped
) {
1609 // more text to layout... which means we'll need a new line
1610 line
= new TextLayoutLine (this, inptr
- text
, offset
);
1612 if (!OverrideLineHeight ()) {
1613 if (*inptr
== '\0') {
1614 line
->descend
= font
->Descender ();
1615 line
->height
= font
->Height ();
1618 line
->height
= line_height
;
1621 if (linebreak
&& *inptr
== '\0')
1622 actual_height
+= line
->height
;
1624 g_ptr_array_add (lines
, line
);
1628 if (inptr
< inend
) {
1629 // more text to layout with the current attrs...
1630 run
= new TextLayoutRun (line
, attrs
, inptr
- text
);
1631 g_ptr_array_add (line
->runs
, run
);
1637 } while (*inptr
!= '\0');
1639 if (word
.break_ops
!= NULL
)
1640 g_array_free (word
.break_ops
, true);
1645 if (debug_flags
& RUNTIME_DEBUG_LAYOUT
) {
1646 print_lines (lines
);
1647 printf ("actualWidth = %f, actualHeight = %f\n\n", actual_width
, actual_height
);
1652 static inline TextLayoutGlyphCluster
*
1653 GenerateGlyphCluster (TextFont
*font
, GlyphInfo
**pglyph
, const char *text
, int start
, int length
)
1655 TextLayoutGlyphCluster
*cluster
= new TextLayoutGlyphCluster (start
, length
);
1656 const char *inend
= text
+ start
+ length
;
1657 const char *inptr
= text
+ start
;
1658 GlyphInfo
*prev
= *pglyph
;
1664 // set y0 to the baseline
1665 y0
= font
->Ascender ();
1669 // count how many path data items we'll need to allocate
1670 while (inptr
< inend
) {
1671 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
1674 if (UnicharIsLineBreak (c
))
1677 // treat tab as a single space
1681 if (!(glyph
= font
->GetGlyphInfo (c
)))
1685 size
+= glyph
->path
->cairo
.num_data
+ 1;
1689 // generate the cached path for the cluster
1690 cluster
->path
= moon_path_new (size
);
1691 inptr
= text
+ start
;
1693 while (inptr
< inend
) {
1694 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
1697 if (UnicharIsLineBreak (c
))
1700 // treat tab as a single space
1704 if (!(glyph
= font
->GetGlyphInfo (c
)))
1707 if ((prev
!= NULL
) && APPLY_KERNING (c
))
1708 x0
+= font
->Kerning (prev
, glyph
);
1709 else if (glyph
->metrics
.horiBearingX
< 0)
1710 x0
+= glyph
->metrics
.horiBearingX
;
1712 font
->AppendPath (cluster
->path
, glyph
, x0
, y0
);
1713 x0
+= glyph
->metrics
.horiAdvance
;
1716 if (!g_unichar_isspace (c
))
1720 moon_close_path (cluster
->path
);
1723 cluster
->uadvance
= x1
;
1724 cluster
->advance
= x0
;
1732 TextLayoutRun::GenerateCache ()
1734 int selection_length
= line
->layout
->GetSelectionLength ();
1735 int selection_start
= line
->layout
->GetSelectionStart ();
1736 const char *text
= line
->layout
->GetText ();
1737 const char *inend
= text
+ start
+ length
;
1738 const char *inptr
= text
+ start
;
1739 TextFont
*font
= attrs
->Font ();
1740 TextLayoutGlyphCluster
*cluster
;
1741 const char *selection_end
;
1742 GlyphInfo
*prev
= NULL
;
1745 // cache the glyph cluster leading up to the selection
1746 if (selection_length
== 0 || start
< selection_start
) {
1747 if (selection_length
> 0)
1748 len
= MIN (selection_start
- start
, length
);
1752 cluster
= GenerateGlyphCluster (font
, &prev
, text
, start
, len
);
1753 g_ptr_array_add (clusters
, cluster
);
1757 // cache the selected glyph cluster
1758 selection_end
= text
+ selection_start
+ selection_length
;
1759 if (inptr
< inend
&& inptr
< selection_end
) {
1760 len
= MIN (inend
, selection_end
) - inptr
;
1761 cluster
= GenerateGlyphCluster (font
, &prev
, text
, inptr
- text
, len
);
1762 g_ptr_array_add (clusters
, cluster
);
1763 cluster
->selected
= true;
1767 // cache the glyph cluster following the selection
1768 if (inptr
< inend
) {
1769 cluster
= GenerateGlyphCluster (font
, &prev
, text
, inptr
- text
, inend
- inptr
);
1770 g_ptr_array_add (clusters
, cluster
);
1776 TextLayoutGlyphCluster::Render (cairo_t
*cr
, const Point
&origin
, TextLayoutAttributes
*attrs
, const char *text
, double x
, double y
, bool uline_full
)
1778 TextFont
*font
= attrs
->Font ();
1779 const char *inend
, *prev
;
1786 if (length
== 0 || advance
== 0.0)
1789 // y is the baseline, set the origin to the top-left
1790 cairo_translate (cr
, x
, y
- font
->Ascender ());
1792 // set y0 to the baseline relative to the translation matrix
1793 y0
= font
->Ascender ();
1795 if (selected
&& (brush
= attrs
->Background (true))) {
1796 area
= Rect (origin
.x
, origin
.y
, advance
, font
->Height ());
1798 // extend the selection background by the width of a SPACE if it includes CRLF
1799 inend
= text
+ start
+ length
;
1800 if ((prev
= g_utf8_find_prev_char (text
+ start
, inend
)))
1801 c
= utf8_getc (&prev
, inend
- prev
);
1805 if (UnicharIsLineBreak (c
)) {
1806 if ((glyph
= font
->GetGlyphInfo (' ')))
1807 area
.width
+= glyph
->metrics
.horiAdvance
;
1810 // render the selection background
1811 brush
->SetupBrush (cr
, area
);
1812 cairo_new_path (cr
);
1813 cairo_rectangle (cr
, area
.x
, area
.y
, area
.width
, area
.height
);
1817 // setup the foreground brush
1818 if (!(brush
= attrs
->Foreground (selected
)))
1821 area
= Rect (origin
.x
, origin
.y
, advance
, font
->Height ());
1822 brush
->SetupBrush (cr
, area
);
1823 cairo_new_path (cr
);
1825 if (path
&& path
->cairo
.data
)
1826 cairo_append_path (cr
, &path
->cairo
);
1830 if (attrs
->IsUnderlined ()) {
1831 double thickness
= font
->UnderlineThickness ();
1832 double pos
= y0
+ font
->UnderlinePosition ();
1834 cairo_set_line_width (cr
, thickness
);
1836 cairo_new_path (cr
);
1837 Rect underline
= Rect (0.0, pos
- thickness
* 0.5, uline_full
? advance
: uadvance
, thickness
);
1838 underline
.Draw (cr
);
1845 TextLayoutRun::Render (cairo_t
*cr
, const Point
&origin
, double x
, double y
, bool is_last_run
)
1847 const char *text
= line
->layout
->GetText ();
1848 TextLayoutGlyphCluster
*cluster
;
1851 if (clusters
->len
== 0)
1854 for (guint i
= 0; i
< clusters
->len
; i
++) {
1855 cluster
= (TextLayoutGlyphCluster
*) clusters
->pdata
[i
];
1858 cluster
->Render (cr
, origin
, attrs
, text
, x0
, y
, is_last_run
&& ((i
+ 1) < clusters
->len
));
1861 x0
+= cluster
->advance
;
1866 TextLayoutLine::Render (cairo_t
*cr
, const Point
&origin
, double left
, double top
)
1871 // set y0 to the line's baseline (descend is a negative value)
1872 y0
= top
+ height
+ descend
;
1875 for (guint i
= 0; i
< runs
->len
; i
++) {
1876 run
= (TextLayoutRun
*) runs
->pdata
[i
];
1877 run
->Render (cr
, origin
, x0
, y0
, (i
+ 1) < runs
->len
);
1883 GetWidthConstraint (double avail_width
, double max_width
, double actual_width
)
1885 if (isinf (avail_width
)) {
1886 // find an upper width constraint
1887 if (isinf (max_width
))
1888 return actual_width
;
1897 TextLayout::HorizontalAlignment (double line_width
)
1902 switch (alignment
) {
1903 case TextAlignmentCenter
:
1904 width
= GetWidthConstraint (avail_width
, max_width
, actual_width
);
1905 if (line_width
< width
)
1906 deltax
= (width
- line_width
) / 2.0;
1910 case TextAlignmentRight
:
1911 width
= GetWidthConstraint (avail_width
, max_width
, actual_width
);
1912 if (line_width
< width
)
1913 deltax
= width
- line_width
;
1926 TextLayout::Render (cairo_t
*cr
, const Point
&origin
, const Point
&offset
)
1928 TextLayoutLine
*line
;
1935 for (guint i
= 0; i
< lines
->len
; i
++) {
1936 line
= (TextLayoutLine
*) lines
->pdata
[i
];
1938 x
= offset
.x
+ HorizontalAlignment (line
->advance
);
1939 line
->Render (cr
, origin
, x
, y
);
1944 #ifdef USE_BINARY_SEARCH
1947 * @lo: the low bound
1948 * @hi: the high bound
1950 * Finds the midpoint between positive integer values, @lo and @hi.
1952 * Notes: Typically expressed as '(@lo + @hi) / 2', this is incorrect
1953 * when @lo and @hi are sufficiently large enough that combining them
1954 * would overflow their integer type. To work around this, we use the
1955 * formula, '@lo + ((@hi - @lo) / 2)', thus preventing this problem
1958 * Returns the midpoint between @lo and @hi (rounded down).
1960 #define MID(lo, hi) (lo + ((hi - lo) >> 1))
1963 TextLayout::GetLineFromY (const Point
&offset
, double y
, int *index
)
1965 register guint lo
, hi
;
1966 TextLayoutLine
*line
;
1970 if (lines
->len
== 0)
1973 lo
= 0, hi
= lines
->len
;
1979 line
= (TextLayoutLine
*) lines
->pdata
[m
];
1981 if (m
> 0 && y0
< line
->top
) {
1982 // y is on some line above us
1984 } else if (y0
> line
->top
+ line
->height
) {
1985 // y is on some line below us
1989 // y is on this line
2001 #else /* linear search */
2003 TextLayout::GetLineFromY (const Point
&offset
, double y
, int *index
)
2005 TextLayoutLine
*line
= NULL
;
2008 //printf ("TextLayout::GetLineFromY (%.2g)\n", y);
2012 for (guint i
= 0; i
< lines
->len
; i
++) {
2013 line
= (TextLayoutLine
*) lines
->pdata
[i
];
2015 // set y1 the top of the next line
2016 y1
= y0
+ line
->height
;
2019 // we found the line that the point is located on
2034 TextLayout::GetLineFromIndex (int index
)
2036 if (index
>= (int) lines
->len
|| index
< 0)
2039 return (TextLayoutLine
*) lines
->pdata
[index
];
2043 TextLayoutLine::GetCursorFromX (const Point
&offset
, double x
)
2045 const char *text
, *inend
, *ch
, *inptr
;
2046 TextLayoutRun
*run
= NULL
;
2047 GlyphInfo
*prev
, *glyph
;
2055 // adjust x0 for horizontal alignment
2056 x0
= offset
.x
+ layout
->HorizontalAlignment (advance
);
2058 text
= layout
->GetText ();
2059 inptr
= text
+ start
;
2060 cursor
= this->offset
;
2063 for (i
= 0; i
< runs
->len
; i
++) {
2064 run
= (TextLayoutRun
*) runs
->pdata
[i
];
2066 if (x
< x0
+ run
->advance
) {
2067 // x is in somewhere inside this run
2071 // x is beyond this run
2072 cursor
+= run
->count
;
2073 inptr
+= run
->length
;
2079 inptr
= text
+ run
->start
;
2080 inend
= inptr
+ run
->length
;
2081 font
= run
->attrs
->Font ();
2083 while (inptr
< inend
) {
2085 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
2088 if (UnicharIsLineBreak (c
)) {
2095 // we treat tabs as a single space
2099 if (!(glyph
= font
->GetGlyphInfo (c
)))
2102 if ((prev
!= NULL
) && APPLY_KERNING (c
))
2103 x0
+= font
->Kerning (prev
, glyph
);
2104 else if (glyph
->metrics
.horiBearingX
< 0)
2105 x0
+= glyph
->metrics
.horiBearingX
;
2107 // calculate midpoint of the character
2108 m
= glyph
->metrics
.horiAdvance
/ 2.0;
2110 // if x is <= the midpoint, then the cursor is
2111 // considered to be at the start of this character.
2118 x0
+= glyph
->metrics
.horiAdvance
;
2122 // x is beyond the end of the last run
2123 run
= (TextLayoutRun
*) runs
->pdata
[i
- 1];
2124 inend
= text
+ run
->start
+ run
->length
;
2125 inptr
= text
+ run
->start
;
2127 if ((ch
= g_utf8_find_prev_char (inptr
, inend
)))
2128 c
= utf8_getc (&ch
, inend
- ch
);
2136 if (inend
> inptr
&& inend
[-1] == '\r') {
2140 } else if (UnicharIsLineBreak (c
)) {
2150 TextLayout::GetCursorFromXY (const Point
&offset
, double x
, double y
)
2152 TextLayoutLine
*line
;
2154 //printf ("TextLayout::GetCursorFromXY (%.2g, %.2g)\n", x, y);
2159 if (!(line
= GetLineFromY (offset
, y
)))
2162 return line
->GetCursorFromX (offset
, x
);
2166 TextLayout::GetCursor (const Point
&offset
, int index
)
2168 const char *inptr
, *inend
, *pchar
;
2169 double height
, x0
, y0
, y1
;
2170 GlyphInfo
*prev
, *glyph
;
2171 TextLayoutLine
*line
;
2177 //printf ("TextLayout::GetCursor (%d)\n", index);
2184 for (guint i
= 0; i
< lines
->len
; i
++) {
2185 line
= (TextLayoutLine
*) lines
->pdata
[i
];
2186 inend
= text
+ line
->start
+ line
->length
;
2188 // adjust x0 for horizontal alignment
2189 x0
= offset
.x
+ HorizontalAlignment (line
->advance
);
2191 // set y1 to the baseline (descend is a negative value)
2192 y1
= y0
+ line
->height
+ line
->descend
;
2193 height
= line
->height
;
2195 //printf ("\tline: left=%.2f, top=%.2f, baseline=%.2f, start index=%d\n", x0, y0, y1, line->offset);
2197 if (index
>= cursor
+ line
->count
) {
2198 // maybe the cursor is on the next line...
2199 if ((i
+ 1) == lines
->len
) {
2200 // we are on the last line... get the previous unichar
2201 inptr
= text
+ line
->start
;
2202 inend
= inptr
+ line
->length
;
2204 if ((pchar
= g_utf8_find_prev_char (text
+ line
->start
, inend
)))
2205 c
= utf8_getc (&pchar
, inend
- pchar
);
2209 if (UnicharIsLineBreak (c
)) {
2210 // cursor is on the next line by itself
2211 x0
= offset
.x
+ HorizontalAlignment (0.0);
2214 // cursor at the end of the last line
2215 x0
+= line
->advance
;
2221 cursor
+= line
->count
;
2226 // cursor is on this line...
2227 for (guint j
= 0; j
< line
->runs
->len
; j
++) {
2228 run
= (TextLayoutRun
*) line
->runs
->pdata
[j
];
2229 inend
= text
+ run
->start
+ run
->length
;
2231 if (index
>= cursor
+ run
->count
) {
2232 // maybe the cursor is in the next run...
2233 cursor
+= run
->count
;
2238 // cursor is in this run...
2239 font
= run
->attrs
->Font ();
2240 inptr
= text
+ run
->start
;
2243 while (cursor
< index
) {
2244 if ((c
= utf8_getc (&inptr
, inend
- inptr
)) == (gunichar
) -1)
2249 // we treat tabs as a single space
2253 if (!(glyph
= font
->GetGlyphInfo (c
)))
2256 if ((prev
!= NULL
) && APPLY_KERNING (c
))
2257 x0
+= font
->Kerning (prev
, glyph
);
2258 else if (glyph
->metrics
.horiBearingX
< 0)
2259 x0
+= glyph
->metrics
.horiBearingX
;
2261 x0
+= glyph
->metrics
.horiAdvance
;
2271 return Rect (x0
, y0
, 1.0, height
);