1#include "mupdf/fitz.h"
2#include "html-imp.h"
3
4#include <string.h>
5#include <stdlib.h>
6#include <stdio.h>
7
8static const char *inherit_list[] = {
9 "color",
10 "direction",
11 "font-family",
12 "font-style",
13 "font-variant",
14 "font-weight",
15 "letter-spacing",
16 "line-height",
17 "list-style-image",
18 "list-style-position",
19 "list-style-type",
20 "orphans",
21 "quotes",
22 "text-align",
23 "text-indent",
24 "text-transform",
25 "visibility",
26 "white-space",
27 "widows",
28 "word-spacing",
29};
30
31static const char *border_width_kw[] = {
32 "medium",
33 "thick",
34 "thin",
35};
36
37static const char *border_style_kw[] = {
38 "dashed",
39 "dotted",
40 "double",
41 "groove",
42 "hidden",
43 "inset",
44 "none",
45 "outset",
46 "ridge",
47 "solid",
48};
49
50static const char *color_kw[] = {
51 "aqua",
52 "black",
53 "blue",
54 "fuchsia",
55 "gray",
56 "green",
57 "lime",
58 "maroon",
59 "navy",
60 "olive",
61 "orange",
62 "purple",
63 "red",
64 "silver",
65 "teal",
66 "transparent",
67 "white",
68 "yellow",
69};
70
71static const char *list_style_type_kw[] = {
72 "armenian",
73 "circle",
74 "decimal",
75 "decimal-leading-zero",
76 "disc",
77 "georgian",
78 "lower-alpha",
79 "lower-greek",
80 "lower-latin",
81 "lower-roman",
82 "none",
83 "square",
84 "upper-alpha",
85 "upper-greek",
86 "upper-latin",
87 "upper-roman",
88};
89
90static const char *list_style_position_kw[] = {
91 "inside",
92 "outside",
93};
94
95static int
96keyword_in_list(const char *name, const char **list, int n)
97{
98 int l = 0;
99 int r = n - 1;
100 while (l <= r)
101 {
102 int m = (l + r) >> 1;
103 int c = strcmp(name, list[m]);
104 if (c < 0)
105 r = m - 1;
106 else if (c > 0)
107 l = m + 1;
108 else
109 return 1;
110 }
111 return 0;
112}
113
114static int
115is_bold_from_font_weight(const char *weight)
116{
117 return !strcmp(weight, "bold") || !strcmp(weight, "bolder") || atoi(weight) > 400;
118}
119
120static int
121is_italic_from_font_style(const char *style)
122{
123 return !strcmp(style, "italic") || !strcmp(style, "oblique");
124}
125
126/*
127 * Compute specificity
128 */
129
130static int
131count_condition_ids(fz_css_condition *cond)
132{
133 int n = 0;
134 while (cond)
135 {
136 if (cond->type == '#')
137 n ++;
138 cond = cond->next;
139 }
140 return n;
141}
142
143static int
144count_selector_ids(fz_css_selector *sel)
145{
146 int n = count_condition_ids(sel->cond);
147 if (sel->left && sel->right)
148 {
149 n += count_selector_ids(sel->left);
150 n += count_selector_ids(sel->right);
151 }
152 return n;
153}
154
155static int
156count_condition_atts(fz_css_condition *cond)
157{
158 int n = 0;
159 while (cond)
160 {
161 if (cond->type != '#' && cond->type != ':')
162 n ++;
163 cond = cond->next;
164 }
165 return n;
166}
167
168static int
169count_selector_atts(fz_css_selector *sel)
170{
171 int n = count_condition_atts(sel->cond);
172 if (sel->left && sel->right)
173 {
174 n += count_selector_atts(sel->left);
175 n += count_selector_atts(sel->right);
176 }
177 return n;
178}
179
180static int
181count_condition_names(fz_css_condition *cond)
182{
183 int n = 0;
184 while (cond)
185 {
186 if (cond->type == ':')
187 n ++;
188 cond = cond->next;
189 }
190 return n;
191}
192
193static int
194count_selector_names(fz_css_selector *sel)
195{
196 int n = count_condition_names(sel->cond);
197 if (sel->left && sel->right)
198 {
199 n += count_selector_names(sel->left);
200 n += count_selector_names(sel->right);
201 }
202 else if (sel->name)
203 {
204 n ++;
205 }
206 return n;
207}
208
209#define INLINE_SPECIFICITY 10000
210
211static int
212selector_specificity(fz_css_selector *sel, int important)
213{
214 int b = count_selector_ids(sel);
215 int c = count_selector_atts(sel);
216 int d = count_selector_names(sel);
217 return important * 1000 + b * 100 + c * 10 + d;
218}
219
220/*
221 * Selector matching
222 */
223
224static int
225match_att_exists_condition(fz_xml *node, const char *key)
226{
227 const char *s = fz_xml_att(node, key);
228 return s != NULL;
229}
230
231static int
232match_att_is_condition(fz_xml *node, const char *key, const char *val)
233{
234 const char *att = fz_xml_att(node, key);
235 return att && !strcmp(val, att);
236}
237
238static int
239match_att_has_condition(fz_xml *node, const char *att, const char *needle)
240{
241 const char *haystack = fz_xml_att(node, att);
242 const char *ss;
243 size_t n;
244 if (haystack) {
245 /* Try matching whole property first. */
246 if (!strcmp(haystack, needle))
247 return 1;
248
249 /* Look for matching words. */
250 n = strlen(needle);
251 ss = strstr(haystack, needle);
252 if (ss && (ss[n] == ' ' || ss[n] == 0) && (ss == haystack || ss[-1] == ' '))
253 return 1;
254 }
255 return 0;
256}
257
258static int
259match_condition(fz_css_condition *cond, fz_xml *node)
260{
261 if (!cond)
262 return 1;
263
264 switch (cond->type) {
265 default: return 0;
266 case ':': return 0; /* don't support pseudo-classes */
267 case '#': if (!match_att_is_condition(node, "id", cond->val)) return 0; break;
268 case '.': if (!match_att_has_condition(node, "class", cond->val)) return 0; break;
269 case '[': if (!match_att_exists_condition(node, cond->key)) return 0; break;
270 case '=': if (!match_att_is_condition(node, cond->key, cond->val)) return 0; break;
271 case '~': if (!match_att_has_condition(node, cond->key, cond->val)) return 0; break;
272 case '|': if (!match_att_is_condition(node, cond->key, cond->val)) return 0; break;
273 }
274
275 return match_condition(cond->next, node);
276}
277
278static int
279match_selector(fz_css_selector *sel, fz_xml *node)
280{
281 if (!node)
282 return 0;
283
284 if (sel->combine)
285 {
286 /* descendant */
287 if (sel->combine == ' ')
288 {
289 fz_xml *parent = fz_xml_up(node);
290 while (parent)
291 {
292 if (match_selector(sel->left, parent))
293 if (match_selector(sel->right, node))
294 return 1;
295 parent = fz_xml_up(parent);
296 }
297 return 0;
298 }
299
300 /* child */
301 if (sel->combine == '>')
302 {
303 fz_xml *parent = fz_xml_up(node);
304 if (!parent)
305 return 0;
306 if (!match_selector(sel->left, parent))
307 return 0;
308 if (!match_selector(sel->right, node))
309 return 0;
310 }
311
312 /* adjacent */
313 if (sel->combine == '+')
314 {
315 fz_xml *prev = fz_xml_prev(node);
316 while (prev && !fz_xml_tag(prev))
317 prev = fz_xml_prev(prev);
318 if (!prev)
319 return 0;
320 if (!fz_xml_tag(prev))
321 return 0;
322 if (!match_selector(sel->left, prev))
323 return 0;
324 if (!match_selector(sel->right, node))
325 return 0;
326 }
327 }
328
329 if (sel->name)
330 {
331 if (!fz_xml_is_tag(node, sel->name))
332 return 0;
333 }
334
335 if (sel->cond)
336 {
337 if (!match_condition(sel->cond, node))
338 return 0;
339 }
340
341 return 1;
342}
343
344/*
345 * Annotating nodes with properties and expanding shorthand forms.
346 */
347
348static int
349count_values(fz_css_value *value)
350{
351 int n = 0;
352 while (value)
353 {
354 n++;
355 value = value->next;
356 }
357 return n;
358}
359
360static void add_property(fz_css_match *match, const char *name, fz_css_value *value, int spec);
361
362static void
363add_shorthand_trbl(fz_css_match *match, fz_css_value *value, int spec,
364 const char *name_t, const char *name_r, const char *name_b, const char *name_l)
365{
366 int n = count_values(value);
367
368 if (n == 1)
369 {
370 add_property(match, name_t, value, spec);
371 add_property(match, name_r, value, spec);
372 add_property(match, name_b, value, spec);
373 add_property(match, name_l, value, spec);
374 }
375
376 if (n == 2)
377 {
378 fz_css_value *a = value;
379 fz_css_value *b = value->next;
380
381 add_property(match, name_t, a, spec);
382 add_property(match, name_r, b, spec);
383 add_property(match, name_b, a, spec);
384 add_property(match, name_l, b, spec);
385 }
386
387 if (n == 3)
388 {
389 fz_css_value *a = value;
390 fz_css_value *b = value->next;
391 fz_css_value *c = value->next->next;
392
393 add_property(match, name_t, a, spec);
394 add_property(match, name_r, b, spec);
395 add_property(match, name_b, c, spec);
396 add_property(match, name_l, b, spec);
397 }
398
399 if (n == 4)
400 {
401 fz_css_value *a = value;
402 fz_css_value *b = value->next;
403 fz_css_value *c = value->next->next;
404 fz_css_value *d = value->next->next->next;
405
406 add_property(match, name_t, a, spec);
407 add_property(match, name_r, b, spec);
408 add_property(match, name_b, c, spec);
409 add_property(match, name_l, d, spec);
410 }
411}
412
413static void
414add_shorthand_margin(fz_css_match *match, fz_css_value *value, int spec)
415{
416 add_shorthand_trbl(match, value, spec,
417 "margin-top", "margin-right", "margin-bottom", "margin-left");
418}
419
420static void
421add_shorthand_padding(fz_css_match *match, fz_css_value *value, int spec)
422{
423 add_shorthand_trbl(match, value, spec,
424 "padding-top", "padding-right", "padding-bottom", "padding-left");
425}
426
427static void
428add_shorthand_border_width(fz_css_match *match, fz_css_value *value, int spec)
429{
430 add_shorthand_trbl(match, value, spec,
431 "border-top-width", "border-right-width", "border-bottom-width", "border-left-width");
432}
433
434static void
435add_shorthand_border_color(fz_css_match *match, fz_css_value *value, int spec)
436{
437 add_shorthand_trbl(match, value, spec,
438 "border-top-color", "border-right-color", "border-bottom-color", "border-left-color");
439}
440
441static void
442add_shorthand_border_style(fz_css_match *match, fz_css_value *value, int spec)
443{
444 add_shorthand_trbl(match, value, spec,
445 "border-top-style", "border-right-style", "border-bottom-style", "border-left-style");
446}
447
448static void
449add_shorthand_border(fz_css_match *match, fz_css_value *value, int spec, int T, int R, int B, int L)
450{
451 while (value)
452 {
453 if (value->type == CSS_HASH)
454 {
455 if (T) add_property(match, "border-top-color", value, spec);
456 if (R) add_property(match, "border-right-color", value, spec);
457 if (B) add_property(match, "border-bottom-color", value, spec);
458 if (L) add_property(match, "border-left-color", value, spec);
459 }
460 else if (value->type == CSS_KEYWORD)
461 {
462 if (keyword_in_list(value->data, border_width_kw, nelem(border_width_kw)))
463 {
464 if (T) add_property(match, "border-top-width", value, spec);
465 if (R) add_property(match, "border-right-width", value, spec);
466 if (B) add_property(match, "border-bottom-width", value, spec);
467 if (L) add_property(match, "border-left-width", value, spec);
468 }
469 else if (keyword_in_list(value->data, border_style_kw, nelem(border_style_kw)))
470 {
471 if (T) add_property(match, "border-top-style", value, spec);
472 if (R) add_property(match, "border-right-style", value, spec);
473 if (B) add_property(match, "border-bottom-style", value, spec);
474 if (L) add_property(match, "border-left-style", value, spec);
475 }
476 else if (keyword_in_list(value->data, color_kw, nelem(color_kw)))
477 {
478 if (T) add_property(match, "border-top-color", value, spec);
479 if (R) add_property(match, "border-right-color", value, spec);
480 if (B) add_property(match, "border-bottom-color", value, spec);
481 if (L) add_property(match, "border-left-color", value, spec);
482 }
483 }
484 else
485 {
486 if (T) add_property(match, "border-top-width", value, spec);
487 if (R) add_property(match, "border-right-width", value, spec);
488 if (B) add_property(match, "border-bottom-width", value, spec);
489 if (L) add_property(match, "border-left-width", value, spec);
490 }
491 value = value->next;
492 }
493}
494
495static void
496add_shorthand_list_style(fz_css_match *match, fz_css_value *value, int spec)
497{
498 while (value)
499 {
500 if (value->type == CSS_KEYWORD)
501 {
502 if (keyword_in_list(value->data, list_style_type_kw, nelem(list_style_type_kw)))
503 {
504 add_property(match, "list-style-type", value, spec);
505 }
506 else if (keyword_in_list(value->data, list_style_position_kw, nelem(list_style_position_kw)))
507 {
508 add_property(match, "list-style-position", value, spec);
509 }
510 }
511 value = value->next;
512 }
513}
514
515static void
516add_property(fz_css_match *match, const char *name, fz_css_value *value, int spec)
517{
518 int i;
519
520 if (!strcmp(name, "margin"))
521 {
522 add_shorthand_margin(match, value, spec);
523 return;
524 }
525 if (!strcmp(name, "padding"))
526 {
527 add_shorthand_padding(match, value, spec);
528 return;
529 }
530 if (!strcmp(name, "border-width"))
531 {
532 add_shorthand_border_width(match, value, spec);
533 return;
534 }
535 if (!strcmp(name, "border-color"))
536 {
537 add_shorthand_border_color(match, value, spec);
538 return;
539 }
540 if (!strcmp(name, "border-style"))
541 {
542 add_shorthand_border_style(match, value, spec);
543 return;
544 }
545 if (!strcmp(name, "border"))
546 {
547 add_shorthand_border(match, value, spec, 1, 1, 1, 1);
548 return;
549 }
550 if (!strcmp(name, "border-top"))
551 {
552 add_shorthand_border(match, value, spec, 1, 0, 0, 0);
553 return;
554 }
555 if (!strcmp(name, "border-right"))
556 {
557 add_shorthand_border(match, value, spec, 0, 1, 0, 0);
558 return;
559 }
560 if (!strcmp(name, "border-bottom"))
561 {
562 add_shorthand_border(match, value, spec, 0, 0, 1, 0);
563 return;
564 }
565 if (!strcmp(name, "border-left"))
566 {
567 add_shorthand_border(match, value, spec, 0, 0, 0, 1);
568 return;
569 }
570 if (!strcmp(name, "list-style"))
571 {
572 add_shorthand_list_style(match, value, spec);
573 return;
574 }
575
576 /* shorthand expansions: */
577 /* TODO: border-color */
578 /* TODO: border-style */
579 /* TODO: font */
580 /* TODO: list-style */
581 /* TODO: background */
582
583 for (i = 0; i < match->count; ++i)
584 {
585 if (!strcmp(match->prop[i].name, name))
586 {
587 if (match->prop[i].spec <= spec)
588 {
589 match->prop[i].value = value;
590 match->prop[i].spec = spec;
591 }
592 return;
593 }
594 }
595
596 if (match->count + 1 >= nelem(match->prop))
597 {
598 // fz_warn(ctx, "too many css properties");
599 return;
600 }
601
602 match->prop[match->count].name = name;
603 match->prop[match->count].value = value;
604 match->prop[match->count].spec = spec;
605 ++match->count;
606}
607
608static void
609sort_properties(fz_css_match *match)
610{
611 int count = match->count;
612 fz_css_match_prop *prop = match->prop;
613 int i, k;
614
615 /* Insertion sort. */
616 for (i = 1; i < count; ++i)
617 {
618 k = i;
619 while (k > 0 && strcmp(prop[k-1].name, prop[k].name) > 0)
620 {
621 fz_css_match_prop save = prop[k-1];
622 prop[k-1] = prop[k];
623 prop[k] = save;
624 --k;
625 }
626 }
627}
628
629void
630fz_match_css(fz_context *ctx, fz_css_match *match, fz_css *css, fz_xml *node)
631{
632 fz_css_rule *rule;
633 fz_css_selector *sel;
634 fz_css_property *prop;
635 const char *s;
636
637 for (rule = css->rule; rule; rule = rule->next)
638 {
639 sel = rule->selector;
640 while (sel)
641 {
642 if (match_selector(sel, node))
643 {
644 for (prop = rule->declaration; prop; prop = prop->next)
645 add_property(match, prop->name, prop->value, selector_specificity(sel, prop->important));
646 break;
647 }
648 sel = sel->next;
649 }
650 }
651
652 if (fz_use_document_css(ctx))
653 {
654 s = fz_xml_att(node, "style");
655 if (s)
656 {
657 fz_try(ctx)
658 {
659 prop = fz_parse_css_properties(ctx, css->pool, s);
660 while (prop)
661 {
662 add_property(match, prop->name, prop->value, INLINE_SPECIFICITY);
663 prop = prop->next;
664 }
665 /* We can "leak" the property here, since it is freed along with the pool allocator. */
666 }
667 fz_catch(ctx)
668 {
669 fz_warn(ctx, "ignoring style attribute");
670 }
671 }
672 }
673
674 sort_properties(match); /* speed up subsequent value_from_raw_property lookups */
675}
676
677void
678fz_match_css_at_page(fz_context *ctx, fz_css_match *match, fz_css *css)
679{
680 fz_css_rule *rule;
681 fz_css_selector *sel;
682 fz_css_property *prop;
683
684 for (rule = css->rule; rule; rule = rule->next)
685 {
686 sel = rule->selector;
687 while (sel)
688 {
689 if (sel->name && !strcmp(sel->name, "@page"))
690 {
691 for (prop = rule->declaration; prop; prop = prop->next)
692 add_property(match, prop->name, prop->value, selector_specificity(sel, prop->important));
693 break;
694 }
695 sel = sel->next;
696 }
697 }
698
699 sort_properties(match); /* speed up subsequent value_from_raw_property lookups */
700}
701
702void
703fz_add_css_font_face(fz_context *ctx, fz_html_font_set *set, fz_archive *zip, const char *base_uri, fz_css_property *declaration)
704{
705 fz_html_font_face *custom;
706 fz_css_property *prop;
707 fz_font *font = NULL;
708 fz_buffer *buf = NULL;
709 int is_bold, is_italic, is_small_caps;
710 char path[2048];
711
712 const char *family = "serif";
713 const char *weight = "normal";
714 const char *style = "normal";
715 const char *variant = "normal";
716 const char *src = NULL;
717
718 for (prop = declaration; prop; prop = prop->next)
719 {
720 if (!strcmp(prop->name, "font-family")) family = prop->value->data;
721 if (!strcmp(prop->name, "font-weight")) weight = prop->value->data;
722 if (!strcmp(prop->name, "font-style")) style = prop->value->data;
723 if (!strcmp(prop->name, "font-variant")) variant = prop->value->data;
724 if (!strcmp(prop->name, "src")) src = prop->value->data;
725 }
726
727 if (!src)
728 return;
729
730 is_bold = is_bold_from_font_weight(weight);
731 is_italic = is_italic_from_font_style(style);
732 is_small_caps = !strcmp(variant, "small-caps");
733
734 fz_strlcpy(path, base_uri, sizeof path);
735 fz_strlcat(path, "/", sizeof path);
736 fz_strlcat(path, src, sizeof path);
737 fz_urldecode(path);
738 fz_cleanname(path);
739
740 for (custom = set->custom; custom; custom = custom->next)
741 if (!strcmp(custom->src, path) && !strcmp(custom->family, family) &&
742 custom->is_bold == is_bold &&
743 custom->is_italic == is_italic &&
744 custom->is_small_caps == is_small_caps)
745 return; /* already loaded */
746
747 fz_var(buf);
748 fz_var(font);
749
750 fz_try(ctx)
751 {
752 if (fz_has_archive_entry(ctx, zip, path))
753 buf = fz_read_archive_entry(ctx, zip, path);
754 else
755 buf = fz_read_file(ctx, src);
756 font = fz_new_font_from_buffer(ctx, NULL, buf, 0, 0);
757 fz_add_html_font_face(ctx, set, family, is_bold, is_italic, is_small_caps, path, font);
758 }
759 fz_always(ctx)
760 {
761 fz_drop_buffer(ctx, buf);
762 fz_drop_font(ctx, font);
763 }
764 fz_catch(ctx)
765 {
766 fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
767 fz_warn(ctx, "cannot load font-face: %s", src);
768 }
769}
770
771void
772fz_add_css_font_faces(fz_context *ctx, fz_html_font_set *set, fz_archive *zip, const char *base_uri, fz_css *css)
773{
774 fz_css_rule *rule;
775 fz_css_selector *sel;
776
777 for (rule = css->rule; rule; rule = rule->next)
778 {
779 sel = rule->selector;
780 while (sel)
781 {
782 if (sel->name && !strcmp(sel->name, "@font-face"))
783 {
784 fz_add_css_font_face(ctx, set, zip, base_uri, rule->declaration);
785 break;
786 }
787 sel = sel->next;
788 }
789 }
790}
791
792static fz_css_value *
793value_from_raw_property(fz_css_match *match, const char *name)
794{
795 fz_css_match_prop *prop = match->prop;
796 int l = 0;
797 int r = match->count - 1;
798 while (l <= r)
799 {
800 int m = (l + r) >> 1;
801 int c = strcmp(name, prop[m].name);
802 if (c < 0)
803 r = m - 1;
804 else if (c > 0)
805 l = m + 1;
806 else
807 return prop[m].value;
808 }
809 return NULL;
810}
811
812static fz_css_value *
813value_from_property(fz_css_match *match, const char *name)
814{
815 fz_css_value *value;
816
817 value = value_from_raw_property(match, name);
818 if (match->up)
819 {
820 if (value && !strcmp(value->data, "inherit"))
821 if (strcmp(name, "font-size") != 0) /* never inherit 'font-size' textually */
822 return value_from_property(match->up, name);
823 if (!value && keyword_in_list(name, inherit_list, nelem(inherit_list)))
824 return value_from_property(match->up, name);
825 }
826 return value;
827}
828
829static const char *
830string_from_property(fz_css_match *match, const char *name, const char *initial)
831{
832 fz_css_value *value;
833 value = value_from_property(match, name);
834 if (!value)
835 return initial;
836 return value->data;
837}
838
839static fz_css_number
840make_number(float v, int u)
841{
842 fz_css_number n;
843 n.value = v;
844 n.unit = u;
845 return n;
846}
847
848/* Fast but inaccurate strtof. */
849static float
850fz_css_strtof(char *s, char **endptr)
851{
852 float sign = 1;
853 float v = 0;
854 float n = 0;
855 float d = 1;
856
857 if (*s == '-')
858 {
859 sign = -1;
860 ++s;
861 }
862
863 while (*s >= '0' && *s <= '9')
864 {
865 v = v * 10 + (*s - '0');
866 ++s;
867 }
868
869 if (*s == '.')
870 {
871 ++s;
872 while (*s >= '0' && *s <= '9')
873 {
874 n = n * 10 + (*s - '0');
875 d = d * 10;
876 ++s;
877 }
878 v += n / d;
879 }
880
881 if (endptr)
882 *endptr = s;
883
884 return sign * v;
885}
886
887static fz_css_number
888number_from_value(fz_css_value *value, float initial, int initial_unit)
889{
890 char *p;
891
892 if (!value)
893 return make_number(initial, initial_unit);
894
895 if (value->type == CSS_PERCENT)
896 return make_number(fz_css_strtof(value->data, NULL), N_PERCENT);
897
898 if (value->type == CSS_NUMBER)
899 return make_number(fz_css_strtof(value->data, NULL), N_NUMBER);
900
901 if (value->type == CSS_LENGTH)
902 {
903 float x = fz_css_strtof(value->data, &p);
904
905 if (p[0] == 'e' && p[1] == 'm' && p[2] == 0)
906 return make_number(x, N_SCALE);
907 if (p[0] == 'e' && p[1] == 'x' && p[2] == 0)
908 return make_number(x / 2, N_SCALE);
909
910 if (p[0] == 'i' && p[1] == 'n' && p[2] == 0)
911 return make_number(x * 72, N_LENGTH);
912 if (p[0] == 'c' && p[1] == 'm' && p[2] == 0)
913 return make_number(x * 7200 / 254, N_LENGTH);
914 if (p[0] == 'm' && p[1] == 'm' && p[2] == 0)
915 return make_number(x * 720 / 254, N_LENGTH);
916 if (p[0] == 'p' && p[1] == 'c' && p[2] == 0)
917 return make_number(x * 12, N_LENGTH);
918
919 if (p[0] == 'p' && p[1] == 't' && p[2] == 0)
920 return make_number(x, N_LENGTH);
921 if (p[0] == 'p' && p[1] == 'x' && p[2] == 0)
922 return make_number(x, N_LENGTH);
923
924 /* FIXME: 'rem' should be 'em' of root element. This is a bad approximation. */
925 if (p[0] == 'r' && p[1] == 'e' && p[2] == 'm' && p[3] == 0)
926 return make_number(x * 16, N_LENGTH);
927
928 /* FIXME: 'ch' should be width of '0' character. This is an approximation. */
929 if (p[0] == 'c' && p[1] == 'h' && p[2] == 0)
930 return make_number(x / 2, N_LENGTH);
931
932 return make_number(x, N_LENGTH);
933 }
934
935 if (value->type == CSS_KEYWORD)
936 {
937 if (!strcmp(value->data, "auto"))
938 return make_number(0, N_AUTO);
939 }
940
941 return make_number(initial, initial_unit);
942}
943
944static fz_css_number
945number_from_property(fz_css_match *match, const char *property, float initial, int initial_unit)
946{
947 return number_from_value(value_from_property(match, property), initial, initial_unit);
948}
949
950static fz_css_number
951border_width_from_property(fz_css_match *match, const char *property)
952{
953 fz_css_value *value = value_from_property(match, property);
954 if (value)
955 {
956 if (!strcmp(value->data, "thin"))
957 return make_number(1, N_LENGTH);
958 if (!strcmp(value->data, "medium"))
959 return make_number(2, N_LENGTH);
960 if (!strcmp(value->data, "thick"))
961 return make_number(4, N_LENGTH);
962 return number_from_value(value, 0, N_LENGTH);
963 }
964 return make_number(2, N_LENGTH); /* initial: 'medium' */
965}
966
967static int
968border_style_from_property(fz_css_match *match, const char *property)
969{
970 fz_css_value *value = value_from_property(match, property);
971 if (value)
972 {
973 if (!strcmp(value->data, "none")) return BS_NONE;
974 else if (!strcmp(value->data, "hidden")) return BS_NONE;
975 else if (!strcmp(value->data, "solid")) return BS_SOLID;
976 }
977 return BS_NONE;
978}
979
980float
981fz_from_css_number(fz_css_number number, float em, float percent_value, float auto_value)
982{
983 switch (number.unit) {
984 default:
985 case N_NUMBER: return number.value;
986 case N_LENGTH: return number.value;
987 case N_SCALE: return number.value * em;
988 case N_PERCENT: return number.value * 0.01f * percent_value;
989 case N_AUTO: return auto_value;
990 }
991}
992
993float
994fz_from_css_number_scale(fz_css_number number, float scale)
995{
996 switch (number.unit) {
997 default:
998 case N_NUMBER: return number.value * scale;
999 case N_LENGTH: return number.value;
1000 case N_SCALE: return number.value * scale;
1001 case N_PERCENT: return number.value * 0.01f * scale;
1002 case N_AUTO: return scale;
1003 }
1004}
1005
1006static fz_css_color
1007make_color(int r, int g, int b, int a)
1008{
1009 fz_css_color c;
1010 c.r = r < 0 ? 0 : r > 255 ? 255 : r;
1011 c.g = g < 0 ? 0 : g > 255 ? 255 : g;
1012 c.b = b < 0 ? 0 : b > 255 ? 255 : b;
1013 c.a = a < 0 ? 0 : a > 255 ? 255 : a;
1014 return c;
1015}
1016
1017static int tohex(int c)
1018{
1019 if (c - '0' < 10)
1020 return c - '0';
1021 return (c | 32) - 'a' + 10;
1022}
1023
1024static fz_css_color
1025color_from_value(fz_css_value *value, fz_css_color initial)
1026{
1027 if (!value)
1028 return initial;
1029
1030 if (value->type == CSS_HASH)
1031 {
1032 int r, g, b;
1033 size_t n;
1034hex_color:
1035 n = strlen(value->data);
1036 if (n == 3)
1037 {
1038 r = tohex(value->data[0]) * 16 + tohex(value->data[0]);
1039 g = tohex(value->data[1]) * 16 + tohex(value->data[1]);
1040 b = tohex(value->data[2]) * 16 + tohex(value->data[2]);
1041 }
1042 else if (n == 6)
1043 {
1044 r = tohex(value->data[0]) * 16 + tohex(value->data[1]);
1045 g = tohex(value->data[2]) * 16 + tohex(value->data[3]);
1046 b = tohex(value->data[4]) * 16 + tohex(value->data[5]);
1047 }
1048 else
1049 {
1050 r = g = b = 0;
1051 }
1052 return make_color(r, g, b, 255);
1053 }
1054
1055 if (value->type == '(' && !strcmp(value->data, "rgb"))
1056 {
1057 fz_css_value *vr, *vg, *vb;
1058 int r, g, b;
1059 vr = value->args;
1060 vg = vr && vr->next ? vr->next->next : NULL; /* skip the ',' nodes */
1061 vb = vg && vg->next ? vg->next->next : NULL; /* skip the ',' nodes */
1062 r = fz_from_css_number(number_from_value(vr, 0, N_NUMBER), 255, 255, 0);
1063 g = fz_from_css_number(number_from_value(vg, 0, N_NUMBER), 255, 255, 0);
1064 b = fz_from_css_number(number_from_value(vb, 0, N_NUMBER), 255, 255, 0);
1065 return make_color(r, g, b, 255);
1066 }
1067
1068 if (value->type == CSS_KEYWORD)
1069 {
1070 if (!strcmp(value->data, "transparent"))
1071 return make_color(0, 0, 0, 0);
1072 if (!strcmp(value->data, "maroon"))
1073 return make_color(0x80, 0x00, 0x00, 255);
1074 if (!strcmp(value->data, "red"))
1075 return make_color(0xFF, 0x00, 0x00, 255);
1076 if (!strcmp(value->data, "orange"))
1077 return make_color(0xFF, 0xA5, 0x00, 255);
1078 if (!strcmp(value->data, "yellow"))
1079 return make_color(0xFF, 0xFF, 0x00, 255);
1080 if (!strcmp(value->data, "olive"))
1081 return make_color(0x80, 0x80, 0x00, 255);
1082 if (!strcmp(value->data, "purple"))
1083 return make_color(0x80, 0x00, 0x80, 255);
1084 if (!strcmp(value->data, "fuchsia"))
1085 return make_color(0xFF, 0x00, 0xFF, 255);
1086 if (!strcmp(value->data, "white"))
1087 return make_color(0xFF, 0xFF, 0xFF, 255);
1088 if (!strcmp(value->data, "lime"))
1089 return make_color(0x00, 0xFF, 0x00, 255);
1090 if (!strcmp(value->data, "green"))
1091 return make_color(0x00, 0x80, 0x00, 255);
1092 if (!strcmp(value->data, "navy"))
1093 return make_color(0x00, 0x00, 0x80, 255);
1094 if (!strcmp(value->data, "blue"))
1095 return make_color(0x00, 0x00, 0xFF, 255);
1096 if (!strcmp(value->data, "aqua"))
1097 return make_color(0x00, 0xFF, 0xFF, 255);
1098 if (!strcmp(value->data, "teal"))
1099 return make_color(0x00, 0x80, 0x80, 255);
1100 if (!strcmp(value->data, "black"))
1101 return make_color(0x00, 0x00, 0x00, 255);
1102 if (!strcmp(value->data, "silver"))
1103 return make_color(0xC0, 0xC0, 0xC0, 255);
1104 if (!strcmp(value->data, "gray"))
1105 return make_color(0x80, 0x80, 0x80, 255);
1106 goto hex_color; /* last ditch attempt: maybe it's a #XXXXXX color without the # */
1107 }
1108 return initial;
1109}
1110
1111static fz_css_color
1112color_from_property(fz_css_match *match, const char *property, fz_css_color initial)
1113{
1114 return color_from_value(value_from_property(match, property), initial);
1115}
1116
1117int
1118fz_get_css_match_display(fz_css_match *match)
1119{
1120 fz_css_value *value = value_from_property(match, "display");
1121 if (value)
1122 {
1123 if (!strcmp(value->data, "none"))
1124 return DIS_NONE;
1125 if (!strcmp(value->data, "inline"))
1126 return DIS_INLINE;
1127 if (!strcmp(value->data, "block"))
1128 return DIS_BLOCK;
1129 if (!strcmp(value->data, "list-item"))
1130 return DIS_LIST_ITEM;
1131 if (!strcmp(value->data, "inline-block"))
1132 return DIS_INLINE_BLOCK;
1133 if (!strcmp(value->data, "table"))
1134 return DIS_TABLE;
1135 if (!strcmp(value->data, "table-row"))
1136 return DIS_TABLE_ROW;
1137 if (!strcmp(value->data, "table-cell"))
1138 return DIS_TABLE_CELL;
1139 }
1140 return DIS_INLINE;
1141}
1142
1143static int
1144white_space_from_property(fz_css_match *match)
1145{
1146 fz_css_value *value = value_from_property(match, "white-space");
1147 if (value)
1148 {
1149 if (!strcmp(value->data, "normal")) return WS_NORMAL;
1150 else if (!strcmp(value->data, "pre")) return WS_PRE;
1151 else if (!strcmp(value->data, "nowrap")) return WS_NOWRAP;
1152 else if (!strcmp(value->data, "pre-wrap")) return WS_PRE_WRAP;
1153 else if (!strcmp(value->data, "pre-line")) return WS_PRE_LINE;
1154 }
1155 return WS_NORMAL;
1156}
1157
1158static int
1159visibility_from_property(fz_css_match *match)
1160{
1161 fz_css_value *value = value_from_property(match, "visibility");
1162 if (value)
1163 {
1164 if (!strcmp(value->data, "visible")) return V_VISIBLE;
1165 else if (!strcmp(value->data, "hidden")) return V_HIDDEN;
1166 else if (!strcmp(value->data, "collapse")) return V_COLLAPSE;
1167 }
1168 return V_VISIBLE;
1169}
1170
1171static int
1172page_break_from_property(fz_css_match *match, char *prop)
1173{
1174 fz_css_value *value = value_from_property(match, prop);
1175 if (value)
1176 {
1177 if (!strcmp(value->data, "auto")) return PB_AUTO;
1178 else if (!strcmp(value->data, "always")) return PB_ALWAYS;
1179 else if (!strcmp(value->data, "avoid")) return PB_AVOID;
1180 else if (!strcmp(value->data, "left")) return PB_LEFT;
1181 else if (!strcmp(value->data, "right")) return PB_RIGHT;
1182 }
1183 return PB_AUTO;
1184}
1185
1186void
1187fz_default_css_style(fz_context *ctx, fz_css_style *style)
1188{
1189 memset(style, 0, sizeof *style);
1190 style->visibility = V_VISIBLE;
1191 style->text_align = TA_LEFT;
1192 style->vertical_align = VA_BASELINE;
1193 style->white_space = WS_NORMAL;
1194 style->list_style_type = LST_DISC;
1195 style->font_size = make_number(1, N_SCALE);
1196 style->width = make_number(0, N_AUTO);
1197 style->height = make_number(0, N_AUTO);
1198}
1199
1200void
1201fz_apply_css_style(fz_context *ctx, fz_html_font_set *set, fz_css_style *style, fz_css_match *match)
1202{
1203 fz_css_value *value;
1204
1205 fz_css_color black = { 0, 0, 0, 255 };
1206 fz_css_color transparent = { 0, 0, 0, 0 };
1207
1208 fz_default_css_style(ctx, style);
1209
1210 style->visibility = visibility_from_property(match);
1211 style->white_space = white_space_from_property(match);
1212 style->page_break_before = page_break_from_property(match, "page-break-before");
1213 style->page_break_after = page_break_from_property(match, "page-break-after");
1214
1215 value = value_from_property(match, "text-align");
1216 if (value)
1217 {
1218 if (!strcmp(value->data, "left")) style->text_align = TA_LEFT;
1219 else if (!strcmp(value->data, "right")) style->text_align = TA_RIGHT;
1220 else if (!strcmp(value->data, "center")) style->text_align = TA_CENTER;
1221 else if (!strcmp(value->data, "justify")) style->text_align = TA_JUSTIFY;
1222 }
1223
1224 value = value_from_property(match, "vertical-align");
1225 if (value)
1226 {
1227 if (!strcmp(value->data, "baseline")) style->vertical_align = VA_BASELINE;
1228 else if (!strcmp(value->data, "sub")) style->vertical_align = VA_SUB;
1229 else if (!strcmp(value->data, "super")) style->vertical_align = VA_SUPER;
1230 else if (!strcmp(value->data, "top")) style->vertical_align = VA_TOP;
1231 else if (!strcmp(value->data, "bottom")) style->vertical_align = VA_BOTTOM;
1232 else if (!strcmp(value->data, "text-top")) style->vertical_align = VA_TEXT_TOP;
1233 else if (!strcmp(value->data, "text-bottom")) style->vertical_align = VA_TEXT_BOTTOM;
1234 }
1235
1236 value = value_from_property(match, "font-size");
1237 if (value)
1238 {
1239 if (!strcmp(value->data, "xx-large")) style->font_size = make_number(1.73f, N_SCALE);
1240 else if (!strcmp(value->data, "x-large")) style->font_size = make_number(1.44f, N_SCALE);
1241 else if (!strcmp(value->data, "large")) style->font_size = make_number(1.2f, N_SCALE);
1242 else if (!strcmp(value->data, "medium")) style->font_size = make_number(1.0f, N_SCALE);
1243 else if (!strcmp(value->data, "small")) style->font_size = make_number(0.83f, N_SCALE);
1244 else if (!strcmp(value->data, "x-small")) style->font_size = make_number(0.69f, N_SCALE);
1245 else if (!strcmp(value->data, "xx-small")) style->font_size = make_number(0.69f, N_SCALE);
1246 else if (!strcmp(value->data, "larger")) style->font_size = make_number(1.2f, N_SCALE);
1247 else if (!strcmp(value->data, "smaller")) style->font_size = make_number(1/1.2f, N_SCALE);
1248 else style->font_size = number_from_value(value, 12, N_LENGTH);
1249 }
1250 else
1251 {
1252 style->font_size = make_number(1, N_SCALE);
1253 }
1254
1255 value = value_from_property(match, "list-style-type");
1256 if (value)
1257 {
1258 if (!strcmp(value->data, "none")) style->list_style_type = LST_NONE;
1259 else if (!strcmp(value->data, "disc")) style->list_style_type = LST_DISC;
1260 else if (!strcmp(value->data, "circle")) style->list_style_type = LST_CIRCLE;
1261 else if (!strcmp(value->data, "square")) style->list_style_type = LST_SQUARE;
1262 else if (!strcmp(value->data, "decimal")) style->list_style_type = LST_DECIMAL;
1263 else if (!strcmp(value->data, "decimal-leading-zero")) style->list_style_type = LST_DECIMAL_ZERO;
1264 else if (!strcmp(value->data, "lower-roman")) style->list_style_type = LST_LC_ROMAN;
1265 else if (!strcmp(value->data, "upper-roman")) style->list_style_type = LST_UC_ROMAN;
1266 else if (!strcmp(value->data, "lower-greek")) style->list_style_type = LST_LC_GREEK;
1267 else if (!strcmp(value->data, "upper-greek")) style->list_style_type = LST_UC_GREEK;
1268 else if (!strcmp(value->data, "lower-latin")) style->list_style_type = LST_LC_LATIN;
1269 else if (!strcmp(value->data, "upper-latin")) style->list_style_type = LST_UC_LATIN;
1270 else if (!strcmp(value->data, "lower-alpha")) style->list_style_type = LST_LC_ALPHA;
1271 else if (!strcmp(value->data, "upper-alpha")) style->list_style_type = LST_UC_ALPHA;
1272 else if (!strcmp(value->data, "armenian")) style->list_style_type = LST_ARMENIAN;
1273 else if (!strcmp(value->data, "georgian")) style->list_style_type = LST_GEORGIAN;
1274 }
1275
1276 style->line_height = number_from_property(match, "line-height", 1.2f, N_SCALE);
1277
1278 style->text_indent = number_from_property(match, "text-indent", 0, N_LENGTH);
1279
1280 style->width = number_from_property(match, "width", 0, N_AUTO);
1281 style->height = number_from_property(match, "height", 0, N_AUTO);
1282
1283 style->margin[0] = number_from_property(match, "margin-top", 0, N_LENGTH);
1284 style->margin[1] = number_from_property(match, "margin-right", 0, N_LENGTH);
1285 style->margin[2] = number_from_property(match, "margin-bottom", 0, N_LENGTH);
1286 style->margin[3] = number_from_property(match, "margin-left", 0, N_LENGTH);
1287
1288 style->padding[0] = number_from_property(match, "padding-top", 0, N_LENGTH);
1289 style->padding[1] = number_from_property(match, "padding-right", 0, N_LENGTH);
1290 style->padding[2] = number_from_property(match, "padding-bottom", 0, N_LENGTH);
1291 style->padding[3] = number_from_property(match, "padding-left", 0, N_LENGTH);
1292
1293 style->color = color_from_property(match, "color", black);
1294 style->background_color = color_from_property(match, "background-color", transparent);
1295
1296 style->border_style_0 = border_style_from_property(match, "border-top-style");
1297 style->border_style_1 = border_style_from_property(match, "border-right-style");
1298 style->border_style_2 = border_style_from_property(match, "border-bottom-style");
1299 style->border_style_3 = border_style_from_property(match, "border-left-style");
1300
1301 style->border_color[0] = color_from_property(match, "border-top-color", style->color);
1302 style->border_color[1] = color_from_property(match, "border-right-color", style->color);
1303 style->border_color[2] = color_from_property(match, "border-bottom-color", style->color);
1304 style->border_color[3] = color_from_property(match, "border-left-color", style->color);
1305
1306 style->border_width[0] = border_width_from_property(match, "border-top-width");
1307 style->border_width[1] = border_width_from_property(match, "border-right-width");
1308 style->border_width[2] = border_width_from_property(match, "border-bottom-width");
1309 style->border_width[3] = border_width_from_property(match, "border-left-width");
1310
1311 {
1312 const char *font_weight = string_from_property(match, "font-weight", "normal");
1313 const char *font_style = string_from_property(match, "font-style", "normal");
1314 const char *font_variant = string_from_property(match, "font-variant", "normal");
1315 int is_bold = is_bold_from_font_weight(font_weight);
1316 int is_italic = is_italic_from_font_style(font_style);
1317 style->small_caps = !strcmp(font_variant, "small-caps");
1318 value = value_from_property(match, "font-family");
1319 while (value)
1320 {
1321 if (strcmp(value->data, ",") != 0)
1322 {
1323 style->font = fz_load_html_font(ctx, set, value->data, is_bold, is_italic, style->small_caps);
1324 if (style->font)
1325 break;
1326 }
1327 value = value->next;
1328 }
1329 if (!style->font)
1330 style->font = fz_load_html_font(ctx, set, "serif", is_bold, is_italic, style->small_caps);
1331 }
1332}
1333
1334/*
1335 * Pretty printing
1336 */
1337
1338static void print_value(fz_css_value *val)
1339{
1340 printf("%s", val->data);
1341 if (val->args)
1342 {
1343 printf("(");
1344 print_value(val->args);
1345 printf(")");
1346 }
1347 if (val->next)
1348 {
1349 printf(" ");
1350 print_value(val->next);
1351 }
1352}
1353
1354static void print_property(fz_css_property *prop)
1355{
1356 printf("\t%s: ", prop->name);
1357 print_value(prop->value);
1358 if (prop->important)
1359 printf(" !important");
1360 printf(";\n");
1361}
1362
1363static void print_condition(fz_css_condition *cond)
1364{
1365 if (cond->type == '=')
1366 printf("[%s=%s]", cond->key, cond->val);
1367 else if (cond->type == '[')
1368 printf("[%s]", cond->key);
1369 else
1370 printf("%c%s", cond->type, cond->val);
1371 if (cond->next)
1372 print_condition(cond->next);
1373}
1374
1375static void print_selector(fz_css_selector *sel)
1376{
1377 if (sel->combine)
1378 {
1379 print_selector(sel->left);
1380 if (sel->combine == ' ')
1381 printf(" ");
1382 else
1383 printf(" %c ", sel->combine);
1384 print_selector(sel->right);
1385 }
1386 else if (sel->name)
1387 printf("%s", sel->name);
1388 else
1389 printf("*");
1390 if (sel->cond)
1391 {
1392 print_condition(sel->cond);
1393 }
1394}
1395
1396static void print_rule(fz_css_rule *rule)
1397{
1398 fz_css_selector *sel;
1399 fz_css_property *prop;
1400
1401 for (sel = rule->selector; sel; sel = sel->next)
1402 {
1403 print_selector(sel);
1404 printf(" /* %d */", selector_specificity(sel, 0));
1405 if (sel->next)
1406 printf(", ");
1407 }
1408
1409 printf("\n{\n");
1410 for (prop = rule->declaration; prop; prop = prop->next)
1411 {
1412 print_property(prop);
1413 }
1414 printf("}\n");
1415}
1416
1417void
1418fz_debug_css(fz_context *ctx, fz_css *css)
1419{
1420 fz_css_rule *rule = css->rule;
1421 while (rule)
1422 {
1423 print_rule(rule);
1424 rule = rule->next;
1425 }
1426}
1427