2 * Copyright (C) 2006-2008 Benjamin Otte <otte@gnome.org>
3 * 2007 Pekka Lampila <pekka.lampila@iki.fi>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301 USA
25 #include <pango/pango.h>
26 #include <pango/pangocairo.h>
29 #include "swfdec_text_layout.h"
30 #include "swfdec_debug.h"
31 #include "swfdec_decoder.h"
33 #define BULLET_MARGIN 36
36 swfdec_text_attribute_apply_bold (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
,
37 PangoFontDescription
*desc
)
39 pango_font_description_set_weight (desc
, attr
->bold
? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
);
42 static PangoAttribute
*
43 swfdec_text_attribute_create_color (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
)
45 return pango_attr_foreground_new (SWFDEC_COLOR_RED (attr
->color
) * 255,
46 SWFDEC_COLOR_GREEN (attr
->color
) * 255, SWFDEC_COLOR_BLUE (attr
->color
) * 255);
50 swfdec_text_attribute_apply_font (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
,
51 PangoFontDescription
*desc
)
53 /* FIXME: resolve _sans, _serif and friends */
54 pango_font_description_set_family (desc
, attr
->font
);
58 swfdec_text_attribute_apply_italic (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
,
59 PangoFontDescription
*desc
)
61 pango_font_description_set_style (desc
, attr
->italic
? PANGO_STYLE_ITALIC
: PANGO_STYLE_NORMAL
);
64 static PangoAttribute
*
65 swfdec_text_attribute_create_letter_spacing (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
)
67 return pango_attr_letter_spacing_new (attr
->letter_spacing
* PANGO_SCALE
);
71 swfdec_text_attribute_apply_size (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
,
72 PangoFontDescription
*desc
)
74 guint size
= attr
->size
* layout
->scale
;
76 pango_font_description_set_absolute_size (desc
, size
* PANGO_SCALE
);
79 static PangoAttribute
*
80 swfdec_text_attribute_create_underline (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
)
82 return pango_attr_underline_new (attr
->underline
? PANGO_UNDERLINE_SINGLE
: PANGO_UNDERLINE_NONE
);
85 /*** THE BIG OPTIONS TABLE ***/
95 FormatApplication application
;
96 PangoAttribute
* (* create_attribute
) (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
);
97 void (* apply_attribute
) (SwfdecTextLayout
*layout
, const SwfdecTextAttributes
*attr
, PangoFontDescription
*desc
);
99 /* unknown or unhandled properties */
100 [SWFDEC_TEXT_ATTRIBUTE_DISPLAY
] = { FORMAT_APPLY_UNKNOWN
, NULL
, NULL
},
101 [SWFDEC_TEXT_ATTRIBUTE_KERNING
] = { FORMAT_APPLY_UNKNOWN
, NULL
, NULL
},
102 /* per-paragraph options */
103 [SWFDEC_TEXT_ATTRIBUTE_BULLET
] = { FORMAT_APPLY_BLOCK
, NULL
, NULL
},
104 [SWFDEC_TEXT_ATTRIBUTE_INDENT
] = { FORMAT_APPLY_BLOCK
, NULL
, NULL
},
105 /* per-line options */
106 [SWFDEC_TEXT_ATTRIBUTE_ALIGN
] = { FORMAT_APPLY_LINE
, NULL
, NULL
},
107 [SWFDEC_TEXT_ATTRIBUTE_BLOCK_INDENT
] = { FORMAT_APPLY_LINE
, NULL
, NULL
},
108 [SWFDEC_TEXT_ATTRIBUTE_LEADING
] = { FORMAT_APPLY_LINE
, NULL
, NULL
},
109 [SWFDEC_TEXT_ATTRIBUTE_LEFT_MARGIN
] = { FORMAT_APPLY_LINE
, NULL
, NULL
},
110 [SWFDEC_TEXT_ATTRIBUTE_RIGHT_MARGIN
] = { FORMAT_APPLY_LINE
, NULL
, NULL
},
111 [SWFDEC_TEXT_ATTRIBUTE_TAB_STOPS
] = { FORMAT_APPLY_LINE
, NULL
, NULL
},
112 /* per-glyph options */
113 [SWFDEC_TEXT_ATTRIBUTE_BOLD
] = { FORMAT_APPLY_GLYPH
, NULL
, swfdec_text_attribute_apply_bold
},
114 [SWFDEC_TEXT_ATTRIBUTE_COLOR
] = { FORMAT_APPLY_GLYPH
, swfdec_text_attribute_create_color
, NULL
},
115 [SWFDEC_TEXT_ATTRIBUTE_FONT
] = { FORMAT_APPLY_GLYPH
, NULL
, swfdec_text_attribute_apply_font
},
116 [SWFDEC_TEXT_ATTRIBUTE_ITALIC
] = { FORMAT_APPLY_GLYPH
, NULL
, swfdec_text_attribute_apply_italic
},
117 [SWFDEC_TEXT_ATTRIBUTE_LETTER_SPACING
] = { FORMAT_APPLY_GLYPH
, swfdec_text_attribute_create_letter_spacing
, NULL
},
118 [SWFDEC_TEXT_ATTRIBUTE_SIZE
] = { FORMAT_APPLY_GLYPH
, NULL
, swfdec_text_attribute_apply_size
},
119 [SWFDEC_TEXT_ATTRIBUTE_TARGET
] = { FORMAT_APPLY_GLYPH
, NULL
, NULL
},
120 [SWFDEC_TEXT_ATTRIBUTE_UNDERLINE
] = { FORMAT_APPLY_GLYPH
, swfdec_text_attribute_create_underline
, NULL
},
121 [SWFDEC_TEXT_ATTRIBUTE_URL
] = { FORMAT_APPLY_GLYPH
, NULL
}
124 #define SWFDEC_TEXT_ATTRIBUTES_MASK_NEW_BLOCK (\
125 (1 << SWFDEC_TEXT_ATTRIBUTE_ALIGN) | \
126 (1 << SWFDEC_TEXT_ATTRIBUTE_BLOCK_INDENT) | \
127 (1 << SWFDEC_TEXT_ATTRIBUTE_LEADING) | \
128 (1 << SWFDEC_TEXT_ATTRIBUTE_LEFT_MARGIN) | \
129 (1 << SWFDEC_TEXT_ATTRIBUTE_RIGHT_MARGIN) | \
130 (1 << SWFDEC_TEXT_ATTRIBUTE_TAB_STOPS))
134 /* A block is one vertical part of the layout that can be represented by exactly
135 * one PangoLayout. */
137 typedef struct _SwfdecTextBlock SwfdecTextBlock
;
139 struct _SwfdecTextBlock
{
140 PangoLayout
* layout
; /* the layout we render */
141 SwfdecRectangle rect
; /* the area occupied by this layout */
142 guint row
; /* index of start row */
143 gsize start
; /* first character drawn */
144 gsize end
; /* first character not drawn anymore */
145 gboolean bullet
; /* TRUE if we need to draw a bullet in front of this layout */
149 swfdec_text_block_free (gpointer blockp
)
151 SwfdecTextBlock
*block
= blockp
;
153 g_object_unref (block
->layout
);
155 g_slice_free (SwfdecTextBlock
, block
);
158 static SwfdecTextBlock
*
159 swfdec_text_block_new (PangoContext
*context
)
161 SwfdecTextBlock
*block
;
163 block
= g_slice_new0 (SwfdecTextBlock
);
164 block
->layout
= pango_layout_new (context
);
165 pango_layout_set_wrap (block
->layout
, PANGO_WRAP_WORD_CHAR
);
170 /*** MAINTAINING THE LAYOUT ***/
173 swfdec_text_layout_apply_paragraph_attributes (SwfdecTextLayout
*layout
,
174 SwfdecTextBlock
*block
, const SwfdecTextAttributes
*attr
)
178 /* SWFDEC_TEXT_ATTRIBUTE_BULLET */
179 block
->bullet
= attr
->bullet
;
181 block
->rect
.x
+= BULLET_MARGIN
;
182 if (block
->rect
.width
<= BULLET_MARGIN
) {
183 SWFDEC_FIXME ("out of horizontal space, what now?");
184 block
->rect
.width
= 0;
186 block
->rect
.width
-= BULLET_MARGIN
;
190 /* SWFDEC_TEXT_ATTRIBUTE_INDENT */
191 indent
= attr
->indent
;
192 indent
= MAX (indent
, - attr
->left_margin
- attr
->block_indent
);
193 pango_layout_set_indent (block
->layout
, indent
* PANGO_SCALE
);
197 swfdec_text_layout_apply_line_attributes (SwfdecTextBlock
*block
,
198 const SwfdecTextAttributes
*attr
)
200 /* SWFDEC_TEXT_ATTRIBUTE_ALIGN */
201 switch (attr
->align
) {
202 case SWFDEC_TEXT_ALIGN_LEFT
:
203 pango_layout_set_alignment (block
->layout
, PANGO_ALIGN_LEFT
);
204 pango_layout_set_justify (block
->layout
, FALSE
);
206 case SWFDEC_TEXT_ALIGN_RIGHT
:
207 pango_layout_set_alignment (block
->layout
, PANGO_ALIGN_RIGHT
);
208 pango_layout_set_justify (block
->layout
, FALSE
);
210 case SWFDEC_TEXT_ALIGN_CENTER
:
211 pango_layout_set_alignment (block
->layout
, PANGO_ALIGN_CENTER
);
212 pango_layout_set_justify (block
->layout
, FALSE
);
214 case SWFDEC_TEXT_ALIGN_JUSTIFY
:
215 pango_layout_set_alignment (block
->layout
, PANGO_ALIGN_LEFT
);
216 pango_layout_set_justify (block
->layout
, TRUE
);
219 g_assert_not_reached ();
222 /* SWFDEC_TEXT_ATTRIBUTE_BLOCK_INDENT */
223 /* SWFDEC_TEXT_ATTRIBUTE_LEFT_MARGIN */
224 /* SWFDEC_TEXT_ATTRIBUTE_RIGHT_MARGIN */
225 block
->rect
.x
+= attr
->left_margin
+ attr
->block_indent
;
226 if (block
->rect
.width
<= attr
->left_margin
+ attr
->block_indent
+ attr
->right_margin
) {
227 SWFDEC_FIXME ("out of horizontal space, what now?");
228 block
->rect
.width
= 0;
230 block
->rect
.width
-= attr
->left_margin
+ attr
->block_indent
+ attr
->right_margin
;
233 /* SWFDEC_TEXT_ATTRIBUTE_LEADING */
234 if (attr
->leading
> 0)
235 pango_layout_set_spacing (block
->layout
, attr
->leading
* PANGO_SCALE
);
237 pango_layout_set_spacing (block
->layout
, (attr
->leading
- 1) * PANGO_SCALE
);
239 /* SWFDEC_TEXT_ATTRIBUTE_TAB_STOPS */
240 if (attr
->n_tab_stops
!= 0) {
241 PangoTabArray
*tabs
= pango_tab_array_new (attr
->n_tab_stops
, TRUE
);
243 for (i
= 0; i
< attr
->n_tab_stops
; i
++) {
244 pango_tab_array_set_tab (tabs
, i
, PANGO_TAB_LEFT
, attr
->tab_stops
[i
]);
246 pango_layout_set_tabs (block
->layout
, tabs
);
247 pango_tab_array_free (tabs
);
252 swfdec_text_layout_apply_attributes_to_description (SwfdecTextLayout
*layout
,
253 const SwfdecTextAttributes
*attr
, PangoFontDescription
*desc
)
257 for (i
= 0; i
< G_N_ELEMENTS (format_table
); i
++) {
258 if (format_table
[i
].apply_attribute
) {
259 format_table
[i
].apply_attribute (layout
, attr
, desc
);
265 swfdec_text_layout_apply_attributes (SwfdecTextLayout
*layout
, PangoAttrList
*list
,
266 const SwfdecTextAttributes
*attr
, guint start
, guint end
)
268 PangoAttribute
*attribute
;
269 PangoFontDescription
*desc
;
272 for (i
= 0; i
< G_N_ELEMENTS (format_table
); i
++) {
273 if (format_table
[i
].create_attribute
) {
274 attribute
= format_table
[i
].create_attribute (layout
, attr
);
275 attribute
->start_index
= start
;
276 attribute
->end_index
= end
;
277 pango_attr_list_change (list
, attribute
);
280 desc
= pango_font_description_new ();
281 swfdec_text_layout_apply_attributes_to_description (layout
, attr
, desc
);
282 attribute
= pango_attr_font_desc_new (desc
);
283 pango_font_description_free (desc
);
284 attribute
->start_index
= start
;
285 attribute
->end_index
= end
;
286 pango_attr_list_change (list
, attribute
);
289 static const SwfdecTextBlock
*
290 swfdec_text_layout_create_paragraph (SwfdecTextLayout
*layout
, PangoContext
*context
,
291 const SwfdecTextBlock
*last
, gsize start
, gsize end
)
293 SwfdecTextBufferIter
*iter
;
294 SwfdecTextBlock
*block
;
297 const SwfdecTextAttributes
*attr
, *attr_next
;
298 gsize new_block
, start_next
;
299 PangoFontDescription
*desc
;
300 gboolean first
= TRUE
;
301 // TODO: kerning, display
303 string
= swfdec_text_buffer_get_text (layout
->text
);
305 iter
= swfdec_text_buffer_get_iter (layout
->text
, start
);
306 attr
= swfdec_text_buffer_iter_get_attributes (layout
->text
, iter
);
309 block
= swfdec_text_block_new (context
);
310 block
->start
= start
;
313 block
->rect
.y
= last
->rect
.y
+ last
->rect
.height
;
314 block
->row
= last
->row
+ pango_layout_get_line_count (last
->layout
);
319 block
->rect
.width
= layout
->width
;
320 desc
= pango_font_description_new ();
321 swfdec_text_layout_apply_attributes_to_description (layout
, attr
, desc
);
322 pango_layout_set_font_description (block
->layout
, desc
);
323 pango_font_description_free (desc
);
324 g_sequence_append (layout
->blocks
, block
);
326 if (layout
->password
) {
327 /* requires sane line breaking so we can't just replace the text with * chars.
328 * A good idea might be using a custom font that replaces all chars upon
329 * rendering, so Pango still thinks it uses proper utf-8 */
330 SWFDEC_FIXME ("implement password handling.");
332 pango_layout_set_text (block
->layout
, string
+ start
, end
- start
);
335 swfdec_text_layout_apply_paragraph_attributes (layout
, block
, attr
);
338 swfdec_text_layout_apply_line_attributes (block
, attr
);
339 if (layout
->word_wrap
)
340 pango_layout_set_width (block
->layout
, block
->rect
.width
* PANGO_SCALE
);
342 iter
= swfdec_text_buffer_get_iter (layout
->text
, start
);
343 attr
= swfdec_text_buffer_iter_get_attributes (layout
->text
, iter
);
344 list
= pango_attr_list_new ();
347 while (start_next
!= end
) {
348 gsize cur_start
= start_next
;
350 iter
= swfdec_text_buffer_iter_next (layout
->text
, iter
);
351 attr_next
= iter
? swfdec_text_buffer_iter_get_attributes (layout
->text
, iter
) : NULL
;
352 start_next
= iter
? swfdec_text_buffer_iter_get_start (layout
->text
, iter
) :
353 swfdec_text_buffer_get_length (layout
->text
);
354 start_next
= MIN (start_next
, end
);
356 swfdec_text_layout_apply_attributes (layout
, list
, attr
, cur_start
- block
->start
, start_next
- block
->start
);
358 if (attr_next
&& new_block
> start_next
&&
359 (swfdec_text_attributes_diff (attr
, attr_next
) & SWFDEC_TEXT_ATTRIBUTES_MASK_NEW_BLOCK
))
360 new_block
= start_next
;
364 pango_layout_set_attributes (block
->layout
, list
);
365 pango_attr_list_unref (list
);
367 if (new_block
< end
) {
370 for (i
= 0; i
< pango_layout_get_line_count (block
->layout
); i
++) {
371 PangoLayoutLine
*line
= pango_layout_get_line_readonly (block
->layout
, i
);
372 if ((gsize
) line
->start_index
+ line
->length
+ start
>= new_block
) {
373 new_block
= line
->start_index
+ line
->length
+ start
;
374 /* I hope deleting lines actually works by removing text */
375 pango_layout_set_text (block
->layout
, string
+ start
, new_block
- start
);
376 g_assert (i
+ 1 == pango_layout_get_line_count (block
->layout
));
380 /* check that we have found a line */
381 g_assert (i
< pango_layout_get_line_count (block
->layout
));
383 block
->end
= new_block
;
386 pango_layout_get_pixel_size (block
->layout
, &block
->rect
.width
, &block
->rect
.height
);
387 /* add leading to last line, too */
388 block
->rect
.height
+= pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
;
391 } while (start
!= end
);
397 swfdec_text_layout_create (SwfdecTextLayout
*layout
)
399 const char *p
, *end
, *string
;
400 PangoContext
*context
;
402 const SwfdecTextBlock
*last
= NULL
;
404 map
= pango_cairo_font_map_get_default ();
405 context
= pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (map
));
407 p
= string
= swfdec_text_buffer_get_text (layout
->text
);
409 end
= strpbrk (p
, "\r\n");
411 end
= string
+ swfdec_text_buffer_get_length (layout
->text
);
414 last
= swfdec_text_layout_create_paragraph (layout
, context
, last
, p
- string
, end
- string
);
421 g_object_unref (context
);
425 swfdec_text_layout_ensure (SwfdecTextLayout
*layout
)
427 if (!g_sequence_iter_is_end (g_sequence_get_begin_iter (layout
->blocks
)))
430 swfdec_text_layout_create (layout
);
434 swfdec_text_layout_invalidate (SwfdecTextLayout
*layout
)
436 if (g_sequence_iter_is_end (g_sequence_get_begin_iter (layout
->blocks
)))
440 g_sequence_remove_range (g_sequence_get_begin_iter (layout
->blocks
),
441 g_sequence_get_end_iter (layout
->blocks
));
442 layout
->layout_width
= 0;
447 /* A layout represents the whole text of a TextFieldMovie, this includes the
448 * invisible parts. It also does not care about transformations.
449 * It's the job of the movie to take care of that. */
450 G_DEFINE_TYPE (SwfdecTextLayout
, swfdec_text_layout
, G_TYPE_OBJECT
)
453 swfdec_text_layout_dispose (GObject
*object
)
455 SwfdecTextLayout
*layout
= SWFDEC_TEXT_LAYOUT (object
);
457 g_signal_handlers_disconnect_matched (layout
->text
,
458 G_SIGNAL_MATCH_DATA
, 0, 0, NULL
, NULL
, layout
);
459 g_object_unref (layout
->text
);
461 g_sequence_free (layout
->blocks
);
462 layout
->blocks
= NULL
;
464 G_OBJECT_CLASS (swfdec_text_layout_parent_class
)->dispose (object
);
468 swfdec_text_layout_class_init (SwfdecTextLayoutClass
*klass
)
470 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
472 object_class
->dispose
= swfdec_text_layout_dispose
;
476 swfdec_text_layout_init (SwfdecTextLayout
*layout
)
478 layout
->width
= G_MAXINT
; /* G_MAXUINT causes overflow */
480 layout
->blocks
= g_sequence_new (swfdec_text_block_free
);
484 swfdec_text_layout_new (SwfdecTextBuffer
*buffer
)
486 SwfdecTextLayout
*layout
;
488 g_return_val_if_fail (SWFDEC_IS_TEXT_BUFFER (buffer
), NULL
);
490 layout
= g_object_new (SWFDEC_TYPE_TEXT_LAYOUT
, NULL
);
491 layout
->text
= g_object_ref (buffer
);
492 g_signal_connect_swapped (buffer
, "text-changed",
493 G_CALLBACK (swfdec_text_layout_invalidate
), layout
);
499 swfdec_text_layout_set_wrap_width (SwfdecTextLayout
*layout
, guint width
)
501 g_return_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
));
503 if (layout
->width
== width
)
506 layout
->width
= width
;
507 swfdec_text_layout_invalidate (layout
);
511 swfdec_text_layout_get_wrap_width (SwfdecTextLayout
*layout
)
513 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), -1);
515 return layout
->width
;
519 swfdec_text_layout_set_word_wrap (SwfdecTextLayout
*layout
, gboolean word_wrap
)
521 g_return_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
));
523 if (layout
->word_wrap
== word_wrap
)
526 layout
->word_wrap
= word_wrap
;
527 swfdec_text_layout_invalidate (layout
);
531 swfdec_text_layout_get_word_wrap (SwfdecTextLayout
*layout
)
533 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), -1);
535 return layout
->word_wrap
;
539 swfdec_text_layout_get_password (SwfdecTextLayout
*layout
)
541 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), FALSE
);
543 return layout
->password
;
547 swfdec_text_layout_set_password (SwfdecTextLayout
*layout
, gboolean password
)
549 g_return_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
));
551 if (layout
->password
== password
)
554 layout
->password
= password
;
555 swfdec_text_layout_invalidate (layout
);
559 swfdec_text_layout_get_scale (SwfdecTextLayout
*layout
)
561 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), 1.0);
563 return layout
->scale
;
567 swfdec_text_layout_set_scale (SwfdecTextLayout
*layout
, double scale
)
569 g_return_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
));
570 g_return_if_fail (scale
> 0);
572 if (layout
->scale
== scale
)
575 layout
->scale
= scale
;
576 swfdec_text_layout_invalidate (layout
);
580 * swfdec_text_layout_get_width:
581 * @layout: the layout
583 * Computes the width of the layout in pixels. Note that the width can still
584 * exceed the width set with swfdec_text_layout_set_width() if some
585 * words are too long. Computing the width takes a long time, so it might be
586 * useful to cache the value.
588 * Returns: The width in pixels
591 swfdec_text_layout_get_width (SwfdecTextLayout
*layout
)
594 SwfdecTextBlock
*block
;
596 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), 0);
598 if (layout
->layout_width
)
599 return layout
->layout_width
;
601 swfdec_text_layout_ensure (layout
);
603 for (iter
= g_sequence_get_begin_iter (layout
->blocks
);
604 !g_sequence_iter_is_end (iter
);
605 iter
= g_sequence_iter_next (iter
)) {
606 block
= g_sequence_get (iter
);
607 layout
->layout_width
= MAX (layout
->layout_width
, (guint
) block
->rect
.x
+ block
->rect
.width
);
610 return layout
->layout_width
;
614 * swfdec_text_layout_get_height:
615 * @layout: the layout
617 * Computes the height of the layout in pixels. This is a fast operation.
619 * Returns: The height of the layout in pixels
622 swfdec_text_layout_get_height (SwfdecTextLayout
*layout
)
625 SwfdecTextBlock
*block
;
627 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), 0);
629 swfdec_text_layout_ensure (layout
);
631 if (swfdec_text_buffer_get_length (layout
->text
) == 0)
633 iter
= g_sequence_iter_prev (g_sequence_get_end_iter (layout
->blocks
));
634 block
= g_sequence_get (iter
);
635 return block
->rect
.y
+ block
->rect
.height
;
639 swfdec_text_layout_get_n_rows (SwfdecTextLayout
*layout
)
642 SwfdecTextBlock
*block
;
644 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), 0);
646 swfdec_text_layout_ensure (layout
);
648 iter
= g_sequence_iter_prev (g_sequence_get_end_iter (layout
->blocks
));
649 block
= g_sequence_get (iter
);
650 return block
->row
+ pango_layout_get_line_count (block
->layout
);
653 static GSequenceIter
*
654 swfdec_text_layout_find_row (SwfdecTextLayout
*layout
, guint row
)
656 GSequenceIter
*begin
, *end
, *mid
;
657 SwfdecTextBlock
*cur
;
659 begin
= g_sequence_get_begin_iter (layout
->blocks
);
660 end
= g_sequence_iter_prev (g_sequence_get_end_iter (layout
->blocks
));
661 while (begin
!= end
) {
662 mid
= g_sequence_range_get_midpoint (begin
, end
);
664 mid
= g_sequence_iter_next (mid
);
665 cur
= g_sequence_get (mid
);
667 end
= g_sequence_iter_prev (mid
);
675 * swfdec_text_layout_get_visible_rows:
676 * @layout: the layout
677 * @row: the first visible row
678 * @height: the height in pixels of visible text
680 * Queries the number of rows that would be rendered, if rendering were to start
681 * with @row and had to fit into @height pixels.
683 * Returns: the number of rows that would be rendered
686 swfdec_text_layout_get_visible_rows (SwfdecTextLayout
*layout
, guint row
, guint height
)
689 SwfdecTextBlock
*block
;
690 PangoRectangle extents
;
693 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), 1);
694 g_return_val_if_fail (row
< swfdec_text_layout_get_n_rows (layout
), 1);
696 swfdec_text_layout_ensure (layout
);
698 iter
= swfdec_text_layout_find_row (layout
, row
);
699 block
= g_sequence_get (iter
);
703 block
= g_sequence_get (iter
);
704 for (;row
< (guint
) pango_layout_get_line_count (block
->layout
); row
++) {
705 PangoLayoutLine
*line
= pango_layout_get_line_readonly (block
->layout
, row
);
707 pango_layout_line_get_pixel_extents (line
, NULL
, &extents
);
708 if (extents
.height
> (int) height
)
710 height
-= extents
.height
;
714 if ((int) height
<= pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
)
716 height
-= pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
;
717 iter
= g_sequence_iter_next (iter
);
718 } while (!g_sequence_iter_is_end (iter
));
721 return MAX (count
, 1);
725 * swfdec_text_layout_get_visible_rows_end:
726 * @layout: the layout
727 * @height: the height in pixels
729 * Computes how many rows will be visible if only the last rows would be
730 * displayed. This is useful for computing maximal scroll offsets.
732 * Returns: The number of rows visible at the bottom.
735 swfdec_text_layout_get_visible_rows_end (SwfdecTextLayout
*layout
, guint height
)
738 SwfdecTextBlock
*block
;
739 PangoRectangle extents
;
740 guint row
, count
= 0;
742 g_return_val_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
), 1);
744 swfdec_text_layout_ensure (layout
);
745 iter
= g_sequence_get_end_iter (layout
->blocks
);
748 iter
= g_sequence_iter_prev (iter
);
749 block
= g_sequence_get (iter
);
750 if ((int) height
<= pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
)
752 height
-= pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
;
753 for (row
= pango_layout_get_line_count (block
->layout
); row
> 0; row
--) {
754 PangoLayoutLine
*line
= pango_layout_get_line_readonly (block
->layout
, row
- 1);
756 pango_layout_line_get_pixel_extents (line
, NULL
, &extents
);
757 if (extents
.height
> (int) height
)
759 height
-= extents
.height
;
762 } while (!g_sequence_iter_is_begin (iter
));
765 return MAX (count
, 1);
769 swfdec_text_layout_get_ascent_descent (SwfdecTextLayout
*layout
, int *ascent
,
772 SwfdecTextBlock
*block
;
774 PangoLayoutLine
*line
;
777 g_return_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
));
778 g_return_if_fail (ascent
!= NULL
);
779 g_return_if_fail (descent
!= NULL
);
781 if (swfdec_text_buffer_get_length (layout
->text
) == 0) {
787 swfdec_text_layout_ensure (layout
);
788 iter
= g_sequence_get_begin_iter (layout
->blocks
);
789 block
= g_sequence_get (iter
);
790 line
= pango_layout_get_line_readonly (block
->layout
, 0);
791 pango_layout_line_get_pixel_extents (line
, NULL
, &rect
);
793 *ascent
= PANGO_ASCENT (rect
) - 1; /* don't ask me... */
794 *descent
= PANGO_DESCENT (rect
);
798 swfdec_text_layout_get_line_offset (SwfdecTextLayout
*layout
,
799 SwfdecTextBlock
*block
, PangoLayoutLine
*line
)
801 PangoAlignment align
;
804 align
= pango_layout_get_alignment (block
->layout
);
805 if (align
== PANGO_ALIGN_LEFT
)
808 /* FIXME: realign lines that are too long */
809 pango_layout_get_pixel_size (block
->layout
, &width
, NULL
);
810 diff
= layout
->width
- width
;
811 if (align
== PANGO_ALIGN_CENTER
)
819 static PangoAttrList
*
820 swfdec_text_layout_modify_attributes (SwfdecTextLayout
*layout
,
821 SwfdecTextBlock
*block
, const SwfdecColorTransform
*ctrans
, SwfdecColor focus
)
823 gsize sel_start
, sel_end
;
824 PangoAttrList
*old
, *new;
825 PangoAttrIterator
*iter
;
827 if (swfdec_color_transform_is_identity (ctrans
)) {
828 /* if we're not focused, there's no need to draw a selection */
831 swfdec_text_buffer_get_selection (layout
->text
, &sel_start
, &sel_end
);
832 /* no selection, we draw a cursor instead */
833 if (sel_start
== sel_end
)
835 /* selection outside of block's range */
836 if (sel_start
>= block
->end
|| sel_end
<= block
->start
)
840 swfdec_text_buffer_get_selection (layout
->text
, &sel_start
, &sel_end
);
842 sel_start
= sel_end
= 0;
845 if (sel_start
<= block
->start
)
848 sel_start
-= block
->start
;
849 if (sel_end
<= block
->start
)
852 sel_end
-= block
->start
;
854 old
= pango_layout_get_attributes (block
->layout
);
855 pango_attr_list_ref (old
);
856 new = pango_attr_list_copy (old
);
857 /* we create an iterator through the old list, so we know it
858 * never gets modified. As the new list is identical to the old one, it
859 * achieves exactly what we want. */
860 iter
= pango_attr_list_get_iterator (old
);
862 guint cur_start
, cur_end
;
863 PangoAttrColor
*color_attr
;
865 pango_attr_iterator_range (iter
, (int *) &cur_start
, (int *) &cur_end
);
866 if (cur_end
== G_MAXINT
)
868 color_attr
= (PangoAttrColor
*) pango_attr_iterator_get (iter
, PANGO_ATTR_FOREGROUND
);
869 /* must hold as we explicitly set color attributes when creating
870 * the list initially */
871 g_assert (color_attr
);
872 color
= SWFDEC_COLOR_COMBINE (color_attr
->color
.red
>> 8,
873 color_attr
->color
.green
>> 8, color_attr
->color
.blue
>> 8, 0xFF);
874 color
= swfdec_color_apply_transform (color
, ctrans
);
875 /* We differentiate three ranges: before selection, in selection and after selection */
876 if (cur_start
< sel_start
) {
877 PangoAttribute
*fg
= pango_attr_foreground_new (SWFDEC_COLOR_RED (color
) * 0x101,
878 SWFDEC_COLOR_GREEN (color
) * 0x101, SWFDEC_COLOR_BLUE (color
) * 0x101);
879 fg
->start_index
= cur_start
;
880 fg
->end_index
= MIN (cur_end
, sel_start
);
881 pango_attr_list_change (new, fg
);
883 if (sel_start
< cur_end
&& sel_end
> cur_start
) {
884 PangoAttribute
*fg
= pango_attr_foreground_new (SWFDEC_COLOR_RED (focus
) * 0x101,
885 SWFDEC_COLOR_GREEN (focus
) * 0x101, SWFDEC_COLOR_BLUE (focus
) * 0x101);
886 PangoAttribute
*bg
= pango_attr_background_new (SWFDEC_COLOR_RED (color
) * 0x101,
887 SWFDEC_COLOR_GREEN (color
) * 0x101, SWFDEC_COLOR_BLUE (color
) * 0x101);
888 fg
->start_index
= bg
->start_index
= MAX (cur_start
, sel_start
);
889 fg
->end_index
= bg
->end_index
= MIN (cur_end
, sel_end
);
890 pango_attr_list_change (new, fg
);
891 pango_attr_list_change (new, bg
);
893 if (cur_end
> sel_end
) {
894 PangoAttribute
*fg
= pango_attr_foreground_new (SWFDEC_COLOR_RED (color
) * 0x101,
895 SWFDEC_COLOR_GREEN (color
) * 0x101, SWFDEC_COLOR_BLUE (color
) * 0x101);
896 fg
->start_index
= MAX (sel_end
, cur_start
);
897 fg
->end_index
= cur_end
;
898 pango_attr_list_change (new, fg
);
900 } while (pango_attr_iterator_next (iter
));
901 pango_layout_set_attributes (block
->layout
, new);
902 pango_attr_list_unref (new);
903 pango_attr_iterator_destroy (iter
);
908 * swfdec_text_layout_render:
909 * @layout: the layout to render
910 * @cr: the Cairo context to render to. The context will be transformed so that
911 * (0, 0) points to where the @row should be rendered.
912 * @ctrans: The color transform to apply.
913 * @row: index of the first row to render.
914 * @height: The height in pixels of the visible area.
915 * @focus: color to invert the selection with, 0 if no focus
917 * Renders the contents of the layout into the given Cairo context.
920 swfdec_text_layout_render (SwfdecTextLayout
*layout
, cairo_t
*cr
,
921 const SwfdecColorTransform
*ctrans
, guint row
, guint height
, SwfdecColor focus
)
924 SwfdecTextBlock
*block
;
925 PangoRectangle extents
;
927 SwfdecColor cursor_color
;
929 int cursor_index
; /* Pango neds proper types... */
930 gboolean first_line
= TRUE
;
932 g_return_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
));
933 g_return_if_fail (cr
!= NULL
);
934 g_return_if_fail (ctrans
!= NULL
);
935 g_return_if_fail (row
< swfdec_text_layout_get_n_rows (layout
));
937 swfdec_text_layout_ensure (layout
);
939 if (!focus
|| swfdec_text_buffer_has_selection (layout
->text
)) {
943 cursor_color
= swfdec_text_buffer_get_attributes (layout
->text
,
944 swfdec_text_buffer_get_length (layout
->text
))->color
;
945 cursor_color
= swfdec_color_apply_transform (cursor_color
, ctrans
);
946 cursor_color
= SWFDEC_COLOR_OPAQUE (cursor_color
);
947 cursor
= swfdec_text_buffer_get_cursor (layout
->text
);
949 iter
= swfdec_text_layout_find_row (layout
, row
);
950 block
= g_sequence_get (iter
);
953 block
= g_sequence_get (iter
);
954 cursor_index
= cursor
- block
->start
;
955 pango_cairo_update_layout (cr
, block
->layout
);
956 cairo_translate (cr
, block
->rect
.x
, 0);
957 if (block
->bullet
&& row
== 0) {
958 SWFDEC_FIXME ("render bullet");
960 attr
= swfdec_text_layout_modify_attributes (layout
, block
, ctrans
, focus
);
961 for (;row
< (guint
) pango_layout_get_line_count (block
->layout
); row
++) {
962 PangoLayoutLine
*line
= pango_layout_get_line_readonly (block
->layout
, row
);
963 int xoffset
= swfdec_text_layout_get_line_offset (layout
, block
, line
);
965 pango_layout_line_get_pixel_extents (line
, NULL
, &extents
);
966 if (extents
.height
> (int) height
&& !first_line
) {
968 pango_layout_set_attributes (block
->layout
, attr
);
969 pango_attr_list_unref (attr
);
974 cairo_translate (cr
, xoffset
, - extents
.y
);
975 pango_cairo_show_layout_line (cr
, line
);
976 if (line
->start_index
+ line
->length
>= cursor_index
&&
977 line
->start_index
<= cursor_index
&&
978 (line
->start_index
+ line
->length
!= cursor_index
||
979 (gsize
) line
->start_index
+ line
->length
== block
->end
- block
->start
)) {
981 /* FIXME: implement (trailing for) RTL */
982 pango_layout_line_index_to_x (line
, cursor_index
, FALSE
, &x_index
);
983 x_index
= PANGO_PIXELS (x_index
);
984 swfdec_color_set_source (cr
, cursor_color
);
985 cairo_set_line_width (cr
, 1.0);
986 cairo_move_to (cr
, x_index
+ 0.5, extents
.y
);
987 cairo_rel_line_to (cr
, 0, extents
.height
);
990 height
-= extents
.height
;
991 cairo_translate (cr
, - xoffset
, extents
.height
+ extents
.y
);
994 pango_layout_set_attributes (block
->layout
, attr
);
995 pango_attr_list_unref (attr
);
997 if ((int) height
<= pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
)
999 height
-= pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
;
1000 cairo_translate (cr
, -block
->rect
.x
, pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
);
1002 iter
= g_sequence_iter_next (iter
);
1003 } while (!g_sequence_iter_is_end (iter
));
1007 * swfdec_text_layout_query_position:
1008 * @layout: the layout to query
1009 * @row: the start of this row indicates the (0,0) position to which @x and @y
1013 * @index_: %NULL or variable that will be set to the index in the layout's
1014 * buffer that corresponds to the position of the pressed grapheme.
1015 * @hit: %NULL or variable that will be set to %TRUE if the coordinate is inside
1016 * the glyhs's extents and %FALSE otherwise
1017 * @trailing: %NULL or variable that will be set to thenumber of characters
1018 * inside the grapheme that have been passed.
1020 * Magic function to query everything of interest about the string at a current
1021 * position inisde the @layout.
1024 swfdec_text_layout_query_position (SwfdecTextLayout
*layout
, guint row
,
1025 int x
, int y
, gsize
*index_
, gboolean
*hit
, int *trailing
)
1027 GSequenceIter
*iter
;
1028 SwfdecTextBlock
*block
;
1029 PangoRectangle extents
;
1031 g_return_if_fail (SWFDEC_IS_TEXT_LAYOUT (layout
));
1032 g_return_if_fail (row
< swfdec_text_layout_get_n_rows (layout
));
1034 swfdec_text_layout_ensure (layout
);
1036 iter
= swfdec_text_layout_find_row (layout
, row
);
1037 block
= g_sequence_get (iter
);
1041 block
= g_sequence_get (iter
);
1044 *index_
= block
->start
;
1052 for (;row
< (guint
) pango_layout_get_line_count (block
->layout
); row
++) {
1053 PangoLayoutLine
*line
= pango_layout_get_line_readonly (block
->layout
, row
);
1055 pango_layout_line_get_pixel_extents (line
, NULL
, &extents
);
1056 if (extents
.height
> y
) {
1059 x
-= swfdec_text_layout_get_line_offset (layout
, block
, line
);
1060 tmp
= pango_layout_line_x_to_index (line
, x
* PANGO_SCALE
, &ind
, trailing
);
1064 *index_
= block
->start
+ ind
;
1067 y
-= extents
.height
;
1069 y
-= pango_layout_get_spacing (block
->layout
) / PANGO_SCALE
;
1071 iter
= g_sequence_iter_next (iter
);
1072 } while (!g_sequence_iter_is_end (iter
));
1075 *index_
= swfdec_text_buffer_get_length (layout
->text
);