copy/paste caused some issues here
[swfdec.git] / libswfdec / swfdec_html_parser.c
bloba2c0401c38ac62f76a2b8f4d9aedf8f51852f5d9
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
5 #include <stdlib.h>
6 #include <string.h>
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 */
22 typedef struct {
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 */
29 } ParserData;
31 static void
32 swfdec_paragraph_init (SwfdecEditText *text, SwfdecParagraph *paragraph)
34 paragraph->align = text->align;
35 paragraph->attrs = pango_attr_list_new ();
36 paragraph->text = NULL;
39 static void
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));
57 return paragraph;
60 static void
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);
68 static void
69 attribute_start (ParserData *data, PangoAttribute *attribute)
71 attribute->start_index = data->str->len;
72 data->attributes = g_list_prepend (data->attributes, attribute);
75 static void
76 attribute_end (ParserData *data, GError **error)
78 PangoAttribute *attribute;
80 attribute = data->attributes->data;
81 g_assert (attribute);
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);
87 static void
88 font_start (ParserData *data, const gchar **names, const gchar **values,
89 GError **error)
91 PangoAttribute *attr;
92 guint i;
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];
102 char *end;
103 guint size;
104 if (parse[0] == '+' || parse[0] == '-') {
105 SWFDEC_ERROR ("FIXME: implement relative font sizes!");
106 parse++;
108 size = strtoul (parse, &end, 10);
109 if (*end != '\0') {
110 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
111 "value \"%s\" is not valid for \"size\" attribute", values[i]);
112 return;
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) {
118 PangoColor color;
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]);
122 return;
124 attr = pango_attr_foreground_new (color.red, color.green, color.blue);
125 attribute_start (data, attr);
126 } else {
127 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
128 "unknown attribute %s", names[i]);
129 return;
134 static void
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);
142 static void
143 p_start (ParserData *data, const gchar **names, const gchar **values,
144 GError **error)
146 guint i;
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;
157 } else {
158 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
159 "value \"%s\" is not valid for \"align\" attribute", values[i]);
160 return;
162 } else {
163 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
164 "unknown attribute %s", names[i]);
165 return;
170 typedef struct {
171 const char * name;
172 void (* start) (ParserData * data,
173 const gchar ** attribute_names,
174 const gchar ** attribute_values,
175 GError ** error);
176 void (* end) (ParserData * data,
177 GError ** error);
178 } ParserElement;
180 /* NB: must be sorted alphabetically */
181 ParserElement elements[] = {
182 { "font", font_start, font_end },
183 { "p", p_start, paragraph_end }
186 static int
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);
201 static void
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);
211 return;
213 element->start (user_data, attribute_names, attribute_values, error);
216 static void
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);
225 return;
227 element->end (user_data, error);
230 static void
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);
238 static void
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,
248 NULL,
249 swfdec_html_parser_error,
252 static void G_GNUC_UNUSED
253 dump (ParserData *data)
255 guint i;
256 GList *walk;
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);
271 SwfdecParagraph *
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);
282 data.text = text;
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)) {
288 guint i;
289 GList *walk;
291 //dump (&data);
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)
301 continue;
302 tmp = pango_attribute_copy (tmp);
303 if (tmp->start_index > para->start)
304 tmp->start_index -= para->start;
305 else
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);
315 } else {
316 GList *walk;
317 g_array_free (data.out, TRUE);
318 retval = NULL;
319 for (walk = data.attributes; walk; walk = walk->next) {
320 if (walk->data)
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);
329 return retval;
332 SwfdecParagraph *
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);
343 return ret;
346 void
347 swfdec_paragraph_free (SwfdecParagraph *paragraphs)
349 guint i;
351 for (i = 0; paragraphs[i].text != NULL; i++) {
352 swfdec_paragraph_finish (&paragraphs[i]);
354 g_free (paragraphs);
357 void
358 swfdec_edit_text_render (SwfdecEditText *text, cairo_t *cr, const SwfdecParagraph *paragraph,
359 const SwfdecColorTransform *trans, const SwfdecRect *inval)
361 guint i;
362 PangoFontDescription *desc;
363 PangoLayout *layout;
364 guint width;
365 SwfdecColor color;
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");
375 return;
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);
381 } else {
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);