7 #include <pango/pango.h>
8 #include "swfdec_edittext.h"
9 #include "swfdec_font.h"
10 #include "swfdec_debug.h"
12 struct _SwfdecParagraph
{
13 char * text
; /* the text to display */
14 PangoAttrList
* attrs
; /* attributes for the text */
15 PangoAlignment align
; /* alignment of this paragraph */
17 /* used when parsing */
18 guint start
; /* start index of text */
19 guint end
; /* end index of text */
23 SwfdecEditText
* text
; /* element we're parsing for */
24 GArray
* out
; /* all the paragraphs we've created so far, will be the out value */
25 GString
* str
; /* the text we've parsed so far */
26 GList
* attributes
; /* PangoAttribute list we're currently in */
27 GList
* attributes_completed
; /* completed attributes, oldest last */
28 GList
* paragraphs
; /* SwfdecParagraph list we're currrently in */
32 swfdec_paragraph_init (SwfdecEditText
*text
, SwfdecParagraph
*paragraph
)
34 paragraph
->align
= text
->align
;
35 paragraph
->attrs
= pango_attr_list_new ();
36 paragraph
->text
= NULL
;
40 swfdec_paragraph_finish (SwfdecParagraph
*paragraph
)
42 g_free (paragraph
->text
);
43 pango_attr_list_unref (paragraph
->attrs
);
46 static SwfdecParagraph
*
47 paragraph_start (ParserData
*data
)
49 SwfdecParagraph
*paragraph
;
51 g_array_set_size (data
->out
, data
->out
->len
+ 1);
52 paragraph
= &g_array_index (data
->out
, SwfdecParagraph
, data
->out
->len
- 1);
53 swfdec_paragraph_init (data
->text
, paragraph
);
54 paragraph
->start
= data
->str
->len
;
56 data
->paragraphs
= g_list_prepend (data
->paragraphs
, GUINT_TO_POINTER (data
->out
->len
- 1));
61 paragraph_end (ParserData
*data
, GError
**error
)
63 SwfdecParagraph
*paragraph
= &g_array_index (data
->out
, SwfdecParagraph
, GPOINTER_TO_UINT (data
->paragraphs
->data
));
64 paragraph
->end
= data
->str
->len
;
65 data
->paragraphs
= g_list_remove (data
->paragraphs
, data
->paragraphs
->data
);
69 attribute_start (ParserData
*data
, PangoAttribute
*attribute
)
71 attribute
->start_index
= data
->str
->len
;
72 data
->attributes
= g_list_prepend (data
->attributes
, attribute
);
76 attribute_end (ParserData
*data
, GError
**error
)
78 PangoAttribute
*attribute
;
80 attribute
= data
->attributes
->data
;
82 attribute
->end_index
= data
->str
->len
;
83 data
->attributes
= g_list_remove (data
->attributes
, data
->attributes
->data
);
84 data
->attributes_completed
= g_list_prepend (data
->attributes_completed
, attribute
);
88 font_start (ParserData
*data
, const gchar
**names
, const gchar
**values
,
94 data
->attributes
= g_list_prepend (data
->attributes
, NULL
);
96 for (i
= 0; names
[i
]; i
++) {
97 if (g_ascii_strcasecmp (names
[i
], "face") == 0) {
98 attr
= pango_attr_family_new (values
[i
]);
99 attribute_start (data
, attr
);
100 } else if (g_ascii_strcasecmp (names
[i
], "size") == 0) {
101 const char *parse
= values
[i
];
104 if (parse
[0] == '+' || parse
[0] == '-') {
105 SWFDEC_ERROR ("FIXME: implement relative font sizes!");
108 size
= strtoul (parse
, &end
, 10);
110 g_set_error (error
, G_MARKUP_ERROR
, G_MARKUP_ERROR_INVALID_CONTENT
,
111 "value \"%s\" is not valid for \"size\" attribute", values
[i
]);
114 size
*= 20 * PANGO_SCALE
;
115 attr
= pango_attr_size_new_absolute (size
);
116 attribute_start (data
, attr
);
117 } else if (g_ascii_strcasecmp (names
[i
], "color") == 0) {
119 if (!pango_color_parse (&color
, values
[i
])) {
120 g_set_error (error
, G_MARKUP_ERROR
, G_MARKUP_ERROR_INVALID_CONTENT
,
121 "value \"%s\" is not valid for \"color\" attribute", values
[i
]);
124 attr
= pango_attr_foreground_new (color
.red
, color
.green
, color
.blue
);
125 attribute_start (data
, attr
);
127 g_set_error (error
, G_MARKUP_ERROR
, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE
,
128 "unknown attribute %s", names
[i
]);
135 font_end (ParserData
*data
, GError
**error
)
137 while (data
->attributes
->data
)
138 attribute_end (data
, error
);
139 data
->attributes
= g_list_remove (data
->attributes
, data
->attributes
->data
);
143 p_start (ParserData
*data
, const gchar
**names
, const gchar
**values
,
147 SwfdecParagraph
*paragraph
= paragraph_start (data
);
149 for (i
= 0; names
[i
]; i
++) {
150 if (g_ascii_strcasecmp (names
[i
], "align") == 0) {
151 if (g_ascii_strcasecmp (values
[i
], "left") == 0) {
152 paragraph
->align
= PANGO_ALIGN_LEFT
;
153 } else if (g_ascii_strcasecmp (values
[i
], "right") == 0) {
154 paragraph
->align
= PANGO_ALIGN_RIGHT
;
155 } else if (g_ascii_strcasecmp (values
[i
], "center") == 0) {
156 paragraph
->align
= PANGO_ALIGN_CENTER
;
158 g_set_error (error
, G_MARKUP_ERROR
, G_MARKUP_ERROR_INVALID_CONTENT
,
159 "value \"%s\" is not valid for \"align\" attribute", values
[i
]);
163 g_set_error (error
, G_MARKUP_ERROR
, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE
,
164 "unknown attribute %s", names
[i
]);
172 void (* start
) (ParserData
* data
,
173 const gchar
** attribute_names
,
174 const gchar
** attribute_values
,
176 void (* end
) (ParserData
* data
,
180 /* NB: must be sorted alphabetically */
181 ParserElement elements
[] = {
182 { "font", font_start
, font_end
},
183 { "p", p_start
, paragraph_end
}
187 element_compare (gconstpointer a
, gconstpointer b
)
189 return g_ascii_strcasecmp (((const ParserElement
*) a
)->name
, ((const ParserElement
*) b
)->name
);
192 static ParserElement
*
193 swfdec_html_parser_find_element (const char *name
)
195 ParserElement find
= { name
, NULL
, NULL
};
197 return bsearch (&find
, elements
, G_N_ELEMENTS (elements
),
198 sizeof (ParserElement
), element_compare
);
202 swfdec_html_parser_start_element (GMarkupParseContext
*context
,
203 const gchar
*element_name
, const gchar
**attribute_names
,
204 const gchar
**attribute_values
, gpointer user_data
, GError
**error
)
206 ParserElement
*element
= swfdec_html_parser_find_element (element_name
);
208 if (element
== NULL
) {
209 g_set_error (error
, G_MARKUP_ERROR
, G_MARKUP_ERROR_UNKNOWN_ELEMENT
,
210 "unknown element %s", element_name
);
213 element
->start (user_data
, attribute_names
, attribute_values
, error
);
217 swfdec_html_parser_end_element (GMarkupParseContext
*context
,
218 const gchar
*element_name
, gpointer user_data
, GError
**error
)
220 ParserElement
*element
= swfdec_html_parser_find_element (element_name
);
222 if (element
== NULL
) {
223 g_set_error (error
, G_MARKUP_ERROR
, G_MARKUP_ERROR_UNKNOWN_ELEMENT
,
224 "unknown element %s", element_name
);
227 element
->end (user_data
, error
);
231 swfdec_html_parser_text (GMarkupParseContext
*context
, const gchar
*text
,
232 gsize text_len
, gpointer user_data
, GError
**error
)
234 ParserData
*data
= user_data
;
235 g_string_append_len (data
->str
, text
, text_len
);
239 swfdec_html_parser_error (GMarkupParseContext
*context
, GError
*error
, gpointer user_data
)
241 SWFDEC_ERROR ("error parsing html content: %s", error
->message
);
244 static const GMarkupParser parser
= {
245 swfdec_html_parser_start_element
,
246 swfdec_html_parser_end_element
,
247 swfdec_html_parser_text
,
249 swfdec_html_parser_error
,
252 static void G_GNUC_UNUSED
253 dump (ParserData
*data
)
258 g_print ("paragraphs:\n");
259 for (i
= 0; i
< data
->out
->len
; i
++) {
260 SwfdecParagraph
*paragraph
= &g_array_index (data
->out
, SwfdecParagraph
, i
);
261 g_print ("%3u -%3u %*s\n", paragraph
->start
, paragraph
->end
,
262 paragraph
->end
- paragraph
->start
, data
->str
->str
+ paragraph
->start
);
264 g_print ("attributes:\n");
265 for (walk
= g_list_last (data
->attributes_completed
); walk
; walk
= walk
->prev
) {
266 PangoAttribute
*attr
= walk
->data
;
267 g_print ("%3u -%3u %u\n", attr
->start_index
, attr
->end_index
, attr
->klass
->type
);
272 swfdec_paragraph_html_parse (SwfdecEditText
*text
, const char *str
)
274 ParserData data
= { NULL
, };
275 GMarkupParseContext
*context
;
276 GError
*error
= NULL
;
277 SwfdecParagraph
*retval
;
279 g_return_val_if_fail (SWFDEC_IS_EDIT_TEXT (text
), NULL
);
280 g_return_val_if_fail (str
!= NULL
, NULL
);
283 data
.out
= g_array_new (TRUE
, TRUE
, sizeof (SwfdecParagraph
));
284 data
.str
= g_string_new ("");
285 context
= g_markup_parse_context_new (&parser
, 0, &data
, NULL
);
286 if (g_markup_parse_context_parse (context
, str
, strlen (str
), &error
) &&
287 g_markup_parse_context_end_parse (context
, &error
)) {
292 data
.attributes_completed
= g_list_reverse (data
.attributes_completed
);
293 for (i
= 0; i
< data
.out
->len
; i
++) {
294 SwfdecParagraph
*para
= &g_array_index (data
.out
, SwfdecParagraph
, i
);
295 para
->text
= g_strndup (data
.str
->str
+ para
->start
,
296 para
->end
- para
->start
);
297 for (walk
= data
.attributes_completed
; walk
; walk
= walk
->next
) {
298 PangoAttribute
*tmp
= walk
->data
;
299 if (tmp
->start_index
>= para
->end
||
300 tmp
->end_index
<= para
->start
)
302 tmp
= pango_attribute_copy (tmp
);
303 if (tmp
->start_index
> para
->start
)
304 tmp
->start_index
-= para
->start
;
306 tmp
->start_index
= 0;
307 tmp
->end_index
= MIN (tmp
->end_index
, para
->end
) - para
->start
;
308 if (para
->attrs
== NULL
)
309 para
->attrs
= pango_attr_list_new ();
310 pango_attr_list_change (para
->attrs
, tmp
);
313 g_assert (data
.attributes
== NULL
);
314 retval
= (SwfdecParagraph
*) g_array_free (data
.out
, FALSE
);
317 g_array_free (data
.out
, TRUE
);
319 for (walk
= data
.attributes
; walk
; walk
= walk
->next
) {
321 pango_attribute_destroy (walk
->data
);
323 g_list_free (data
.attributes
);
325 g_list_foreach (data
.attributes_completed
, (GFunc
) pango_attribute_destroy
, NULL
);
326 g_list_free (data
.paragraphs
);
327 g_string_free (data
.str
, TRUE
);
328 g_markup_parse_context_free (context
);
333 swfdec_paragraph_text_parse (SwfdecEditText
*text
, const char *str
)
335 SwfdecParagraph
*ret
;
337 g_return_val_if_fail (SWFDEC_IS_EDIT_TEXT (text
), NULL
);
338 g_return_val_if_fail (str
!= NULL
, NULL
);
340 ret
= g_new0 (SwfdecParagraph
, 2);
341 swfdec_paragraph_init (text
, ret
);
342 ret
->text
= g_strdup (str
);
347 swfdec_paragraph_free (SwfdecParagraph
*paragraphs
)
351 for (i
= 0; paragraphs
[i
].text
!= NULL
; i
++) {
352 swfdec_paragraph_finish (¶graphs
[i
]);
358 swfdec_edit_text_render (SwfdecEditText
*text
, cairo_t
*cr
, const SwfdecParagraph
*paragraph
,
359 const SwfdecColorTransform
*trans
, const SwfdecRect
*inval
)
362 PangoFontDescription
*desc
;
367 g_return_if_fail (SWFDEC_IS_EDIT_TEXT (text
));
368 g_return_if_fail (cr
!= NULL
);
369 g_return_if_fail (paragraph
!= NULL
);
370 g_return_if_fail (trans
!= NULL
);
371 g_return_if_fail (inval
!= NULL
);
373 if (text
->font
== NULL
) {
374 SWFDEC_ERROR ("no font to render with");
377 if (text
->font
->desc
== NULL
) {
378 desc
= pango_font_description_new ();
379 pango_font_description_set_family (desc
, "Sans");
380 SWFDEC_INFO ("font %d has no cairo font description", SWFDEC_CHARACTER (text
->font
)->id
);
382 desc
= pango_font_description_copy (text
->font
->desc
);
384 pango_font_description_set_absolute_size (desc
, text
->height
* PANGO_SCALE
);
385 layout
= pango_cairo_create_layout (cr
);
386 pango_layout_set_font_description (layout
, desc
);
387 pango_font_description_free (desc
);
388 width
= SWFDEC_GRAPHIC (text
)->extents
.x1
- SWFDEC_GRAPHIC (text
)->extents
.x0
- text
->left_margin
- text
->right_margin
;
389 cairo_move_to (cr
, SWFDEC_GRAPHIC (text
)->extents
.x0
+ text
->left_margin
, SWFDEC_GRAPHIC (text
)->extents
.y0
);
390 pango_layout_set_width (layout
, width
* PANGO_SCALE
);
391 color
= swfdec_color_apply_transform (text
->color
, trans
);
392 swfdec_color_set_source (cr
, color
);
394 for (i
= 0; paragraph
[i
].text
!= NULL
; i
++) {
395 pango_layout_set_text (layout
, paragraph
[i
].text
, -1);
396 pango_layout_set_attributes (layout
, paragraph
[i
].attrs
);
397 pango_layout_set_alignment (layout
, paragraph
[i
].align
);
398 pango_cairo_show_layout (cr
, layout
);
400 g_object_unref (layout
);