comparison mupdf-source/source/html/css-apply.c @ 3:2c135c81b16c

MERGE: upstream PyMuPDF 1.26.4 with MuPDF 1.26.7
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:44:09 +0200
parents b50eed0cc0ef
children
comparison
equal deleted inserted replaced
0:6015a75abc2d 3:2c135c81b16c
1 // Copyright (C) 2004-2024 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "mupdf/fitz.h"
24 #include "html-imp.h"
25
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <assert.h>
30
31 static const char *border_width_kw[] = {
32 "medium",
33 "thick",
34 "thin",
35 };
36
37 static 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
50 static 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
71 static 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
90 static const char *list_style_position_kw[] = {
91 "inside",
92 "outside",
93 };
94
95 static const char *font_style_kw[] = {
96 "italic",
97 "oblique",
98 };
99
100 static const char *font_variant_kw[] = {
101 "small-caps",
102 };
103
104 static const char *font_weight_kw[] = {
105 "bold",
106 "bolder",
107 "lighter",
108 };
109
110 static const char *font_size_kw[] = {
111 "large",
112 "larger",
113 "medium",
114 "small",
115 "smaller",
116 "x-large",
117 "x-small",
118 "xx-large",
119 "xx-small",
120 };
121
122 /* Properties to ignore when scanning through font-family. We set font-family
123 * to the full font shorthand value list because Adobe generates DS strings
124 * where the font-family comes before the font-size (and not at the end as it's
125 * supposed to). This lets us scan the font shorthand list without trying to
126 * look up fonts named "bold", etc.
127 */
128 static const char *font_family_ignore[] = {
129 ",",
130 "/",
131 "bold",
132 "bolder",
133 "italic",
134 "large",
135 "larger",
136 "lighter",
137 "medium",
138 "oblique",
139 "small",
140 "small-caps",
141 "smaller",
142 "x-large",
143 "x-small",
144 "xx-large",
145 "xx-small",
146 };
147
148 static int
149 keyword_in_list(const char *name, const char **list, int n)
150 {
151 int l = 0;
152 int r = n - 1;
153 while (l <= r)
154 {
155 int m = (l + r) >> 1;
156 int c = strcmp(name, list[m]);
157 if (c < 0)
158 r = m - 1;
159 else if (c > 0)
160 l = m + 1;
161 else
162 return 1;
163 }
164 return 0;
165 }
166
167 static int
168 is_bold_from_font_weight(const char *weight)
169 {
170 return !strcmp(weight, "bold") || !strcmp(weight, "bolder") || atoi(weight) > 400;
171 }
172
173 static int
174 is_italic_from_font_style(const char *style)
175 {
176 return !strcmp(style, "italic") || !strcmp(style, "oblique");
177 }
178
179 /*
180 * Compute specificity
181 */
182
183 static int
184 count_condition_ids(fz_css_condition *cond)
185 {
186 int n = 0;
187 while (cond)
188 {
189 if (cond->type == '#')
190 n ++;
191 cond = cond->next;
192 }
193 return n;
194 }
195
196 static int
197 count_selector_ids(fz_css_selector *sel)
198 {
199 int n = count_condition_ids(sel->cond);
200 if (sel->left && sel->right)
201 {
202 n += count_selector_ids(sel->left);
203 n += count_selector_ids(sel->right);
204 }
205 return n;
206 }
207
208 static int
209 count_condition_atts(fz_css_condition *cond)
210 {
211 int n = 0;
212 while (cond)
213 {
214 if (cond->type != '#' && cond->type != ':')
215 n ++;
216 cond = cond->next;
217 }
218 return n;
219 }
220
221 static int
222 count_selector_atts(fz_css_selector *sel)
223 {
224 int n = count_condition_atts(sel->cond);
225 if (sel->left && sel->right)
226 {
227 n += count_selector_atts(sel->left);
228 n += count_selector_atts(sel->right);
229 }
230 return n;
231 }
232
233 static int
234 count_condition_names(fz_css_condition *cond)
235 {
236 int n = 0;
237 while (cond)
238 {
239 if (cond->type == ':')
240 n ++;
241 cond = cond->next;
242 }
243 return n;
244 }
245
246 static int
247 count_selector_names(fz_css_selector *sel)
248 {
249 int n = count_condition_names(sel->cond);
250 if (sel->left && sel->right)
251 {
252 n += count_selector_names(sel->left);
253 n += count_selector_names(sel->right);
254 }
255 else if (sel->name)
256 {
257 n ++;
258 }
259 return n;
260 }
261
262 #define INLINE_SPECIFICITY 10000
263
264 static int
265 selector_specificity(fz_css_selector *sel, int important)
266 {
267 int b = count_selector_ids(sel);
268 int c = count_selector_atts(sel);
269 int d = count_selector_names(sel);
270 return important * 1000 + b * 100 + c * 10 + d;
271 }
272
273 /*
274 * Selector matching
275 */
276
277 static int
278 match_att_exists_condition(fz_xml *node, const char *key)
279 {
280 const char *s = fz_xml_att(node, key);
281 return s != NULL;
282 }
283
284 static int
285 match_att_is_condition(fz_xml *node, const char *key, const char *val)
286 {
287 const char *att = fz_xml_att(node, key);
288 return att && !strcmp(val, att);
289 }
290
291 static int
292 match_att_has_condition(fz_xml *node, const char *att, const char *needle)
293 {
294 const char *haystack = fz_xml_att(node, att);
295 const char *ss;
296 size_t n;
297 if (haystack) {
298 ss = strstr(haystack, needle);
299 if (ss)
300 {
301 n = strlen(needle);
302
303 /* Look for exact matches or matching words. */
304 if ((ss[n] == ' ' || ss[n] == 0) && (ss == haystack || ss[-1] == ' '))
305 return 1;
306 }
307 }
308 return 0;
309 }
310
311 static int
312 match_condition(fz_css_condition *cond, fz_xml *node)
313 {
314 if (!cond)
315 return 1;
316
317 switch (cond->type) {
318 default: return 0;
319 case ':': return 0; /* don't support pseudo-classes */
320 case '#': if (!match_att_is_condition(node, "id", cond->val)) return 0; break;
321 case '.': if (!match_att_has_condition(node, "class", cond->val)) return 0; break;
322 case '[': if (!match_att_exists_condition(node, cond->key)) return 0; break;
323 case '=': if (!match_att_is_condition(node, cond->key, cond->val)) return 0; break;
324 case '~': if (!match_att_has_condition(node, cond->key, cond->val)) return 0; break;
325 case '|': if (!match_att_is_condition(node, cond->key, cond->val)) return 0; break;
326 }
327
328 return match_condition(cond->next, node);
329 }
330
331 static int
332 match_selector(fz_css_selector *sel, fz_xml *node)
333 {
334 if (!node)
335 return 0;
336
337 if (sel->combine)
338 {
339 /* descendant */
340 if (sel->combine == ' ')
341 {
342 fz_xml *parent = fz_xml_up(node);
343 if (!parent || !match_selector(sel->right, node))
344 return 0;
345
346 while (parent)
347 {
348 if (match_selector(sel->left, parent))
349 return 1;
350 parent = fz_xml_up(parent);
351 }
352 return 0;
353 }
354
355 /* child */
356 if (sel->combine == '>')
357 {
358 fz_xml *parent = fz_xml_up(node);
359 if (!parent)
360 return 0;
361 if (!match_selector(sel->left, parent))
362 return 0;
363 if (!match_selector(sel->right, node))
364 return 0;
365 }
366
367 /* adjacent */
368 if (sel->combine == '+')
369 {
370 fz_xml *prev = fz_xml_prev(node);
371 while (prev && !fz_xml_tag(prev))
372 prev = fz_xml_prev(prev);
373 if (!prev)
374 return 0;
375 if (!fz_xml_tag(prev))
376 return 0;
377 if (!match_selector(sel->left, prev))
378 return 0;
379 if (!match_selector(sel->right, node))
380 return 0;
381 }
382 }
383
384 if (sel->name)
385 {
386 if (!fz_xml_is_tag(node, sel->name))
387 return 0;
388 }
389
390 if (sel->cond)
391 {
392 if (!match_condition(sel->cond, node))
393 return 0;
394 }
395
396 return 1;
397 }
398
399 /*
400 * Annotating nodes with properties and expanding shorthand forms.
401 */
402
403 static int
404 count_values(fz_css_value *value)
405 {
406 int n = 0;
407 while (value)
408 {
409 n++;
410 value = value->next;
411 }
412 return n;
413 }
414
415 static void add_property(fz_css_match *match, int name, fz_css_value *value, int spec);
416
417 static void
418 add_shorthand_trbl(fz_css_match *match, fz_css_value *value, int spec,
419 int name_t, int name_r, int name_b, int name_l)
420 {
421 int n = count_values(value);
422
423 if (n == 1)
424 {
425 add_property(match, name_t, value, spec);
426 add_property(match, name_r, value, spec);
427 add_property(match, name_b, value, spec);
428 add_property(match, name_l, value, spec);
429 }
430
431 if (n == 2)
432 {
433 fz_css_value *a = value;
434 fz_css_value *b = value->next;
435
436 add_property(match, name_t, a, spec);
437 add_property(match, name_r, b, spec);
438 add_property(match, name_b, a, spec);
439 add_property(match, name_l, b, spec);
440 }
441
442 if (n == 3)
443 {
444 fz_css_value *a = value;
445 fz_css_value *b = value->next;
446 fz_css_value *c = value->next->next;
447
448 add_property(match, name_t, a, spec);
449 add_property(match, name_r, b, spec);
450 add_property(match, name_b, c, spec);
451 add_property(match, name_l, b, spec);
452 }
453
454 if (n == 4)
455 {
456 fz_css_value *a = value;
457 fz_css_value *b = value->next;
458 fz_css_value *c = value->next->next;
459 fz_css_value *d = value->next->next->next;
460
461 add_property(match, name_t, a, spec);
462 add_property(match, name_r, b, spec);
463 add_property(match, name_b, c, spec);
464 add_property(match, name_l, d, spec);
465 }
466 }
467
468 static void
469 add_shorthand_margin(fz_css_match *match, fz_css_value *value, int spec)
470 {
471 add_shorthand_trbl(match, value, spec,
472 PRO_MARGIN_TOP,
473 PRO_MARGIN_RIGHT,
474 PRO_MARGIN_BOTTOM,
475 PRO_MARGIN_LEFT);
476 }
477
478 static void
479 add_shorthand_padding(fz_css_match *match, fz_css_value *value, int spec)
480 {
481 add_shorthand_trbl(match, value, spec,
482 PRO_PADDING_TOP,
483 PRO_PADDING_RIGHT,
484 PRO_PADDING_BOTTOM,
485 PRO_PADDING_LEFT);
486 }
487
488 static void
489 add_shorthand_border_width(fz_css_match *match, fz_css_value *value, int spec)
490 {
491 add_shorthand_trbl(match, value, spec,
492 PRO_BORDER_TOP_WIDTH,
493 PRO_BORDER_RIGHT_WIDTH,
494 PRO_BORDER_BOTTOM_WIDTH,
495 PRO_BORDER_LEFT_WIDTH);
496 }
497
498 static void
499 add_shorthand_border_color(fz_css_match *match, fz_css_value *value, int spec)
500 {
501 add_shorthand_trbl(match, value, spec,
502 PRO_BORDER_TOP_COLOR,
503 PRO_BORDER_RIGHT_COLOR,
504 PRO_BORDER_BOTTOM_COLOR,
505 PRO_BORDER_LEFT_COLOR);
506 }
507
508 static void
509 add_shorthand_border_style(fz_css_match *match, fz_css_value *value, int spec)
510 {
511 add_shorthand_trbl(match, value, spec,
512 PRO_BORDER_TOP_STYLE,
513 PRO_BORDER_RIGHT_STYLE,
514 PRO_BORDER_BOTTOM_STYLE,
515 PRO_BORDER_LEFT_STYLE);
516 }
517
518 static void
519 add_shorthand_border(fz_css_match *match, fz_css_value *value, int spec, int T, int R, int B, int L)
520 {
521 while (value)
522 {
523 if (value->type == CSS_HASH)
524 {
525 if (T) add_property(match, PRO_BORDER_TOP_COLOR, value, spec);
526 if (R) add_property(match, PRO_BORDER_RIGHT_COLOR, value, spec);
527 if (B) add_property(match, PRO_BORDER_BOTTOM_COLOR, value, spec);
528 if (L) add_property(match, PRO_BORDER_LEFT_COLOR, value, spec);
529 }
530 else if (value->type == CSS_KEYWORD)
531 {
532 if (keyword_in_list(value->data, border_width_kw, nelem(border_width_kw)))
533 {
534 if (T) add_property(match, PRO_BORDER_TOP_WIDTH, value, spec);
535 if (R) add_property(match, PRO_BORDER_RIGHT_WIDTH, value, spec);
536 if (B) add_property(match, PRO_BORDER_BOTTOM_WIDTH, value, spec);
537 if (L) add_property(match, PRO_BORDER_LEFT_WIDTH, value, spec);
538 }
539 else if (keyword_in_list(value->data, border_style_kw, nelem(border_style_kw)))
540 {
541 if (T) add_property(match, PRO_BORDER_TOP_STYLE, value, spec);
542 if (R) add_property(match, PRO_BORDER_RIGHT_STYLE, value, spec);
543 if (B) add_property(match, PRO_BORDER_BOTTOM_STYLE, value, spec);
544 if (L) add_property(match, PRO_BORDER_LEFT_STYLE, value, spec);
545 }
546 else if (keyword_in_list(value->data, color_kw, nelem(color_kw)))
547 {
548 if (T) add_property(match, PRO_BORDER_TOP_COLOR, value, spec);
549 if (R) add_property(match, PRO_BORDER_RIGHT_COLOR, value, spec);
550 if (B) add_property(match, PRO_BORDER_BOTTOM_COLOR, value, spec);
551 if (L) add_property(match, PRO_BORDER_LEFT_COLOR, value, spec);
552 }
553 }
554 else
555 {
556 if (T) add_property(match, PRO_BORDER_TOP_WIDTH, value, spec);
557 if (R) add_property(match, PRO_BORDER_RIGHT_WIDTH, value, spec);
558 if (B) add_property(match, PRO_BORDER_BOTTOM_WIDTH, value, spec);
559 if (L) add_property(match, PRO_BORDER_LEFT_WIDTH, value, spec);
560 }
561 value = value->next;
562 }
563 }
564
565 static void
566 add_shorthand_list_style(fz_css_match *match, fz_css_value *value, int spec)
567 {
568 while (value)
569 {
570 if (value->type == CSS_KEYWORD)
571 {
572 if (keyword_in_list(value->data, list_style_type_kw, nelem(list_style_type_kw)))
573 {
574 add_property(match, PRO_LIST_STYLE_TYPE, value, spec);
575 }
576 else if (keyword_in_list(value->data, list_style_position_kw, nelem(list_style_position_kw)))
577 {
578 add_property(match, PRO_LIST_STYLE_POSITION, value, spec);
579 }
580 }
581 value = value->next;
582 }
583 }
584
585 static fz_css_value static_value_normal = { CSS_KEYWORD, "normal", NULL, NULL };
586
587 static fz_css_value *
588 add_shorthand_font_size(fz_css_match *match, fz_css_value *value, int spec)
589 {
590 /* font-size */
591 add_property(match, PRO_FONT_SIZE, value, spec);
592
593 /* / line-height */
594 if (value->next && value->next->next && !strcmp(value->next->data, "/"))
595 {
596 value = value->next->next;
597 add_property(match, PRO_LINE_HEIGHT, value, spec);
598 }
599
600 return value;
601 }
602
603 static void
604 add_shorthand_font(fz_css_match *match, fz_css_value *value, int spec)
605 {
606 fz_css_value *font_style = NULL;
607 fz_css_value *font_variant = NULL;
608 fz_css_value *font_weight = NULL;
609
610 /* add the start as font-family for most robust scanning of matching font names */
611 add_property(match, PRO_FONT_FAMILY, value, spec);
612
613 /* then look for known style/variant/weight keywords and font-size/line-height */
614 for (; value; value = value->next)
615 {
616 /* style/variant/weight/size */
617 if (value->type == CSS_KEYWORD)
618 {
619 if (keyword_in_list(value->data, font_style_kw, nelem(font_style_kw)))
620 font_style = value;
621 else if (keyword_in_list(value->data, font_variant_kw, nelem(font_variant_kw)))
622 font_variant = value;
623 else if (keyword_in_list(value->data, font_weight_kw, nelem(font_weight_kw)))
624 font_weight = value;
625 else if (keyword_in_list(value->data, font_size_kw, nelem(font_size_kw)))
626 value = add_shorthand_font_size(match, value, spec);
627 }
628 else if (value->type == CSS_NUMBER)
629 font_weight = value;
630 else if (value->type == CSS_LENGTH || value->type == CSS_PERCENT)
631 value = add_shorthand_font_size(match, value, spec);
632 }
633
634 /* set all properties to their initial values if not specified! */
635 if (font_style)
636 add_property(match, PRO_FONT_STYLE, font_style, spec);
637 else
638 add_property(match, PRO_FONT_STYLE, &static_value_normal, spec);
639
640 if (font_variant)
641 add_property(match, PRO_FONT_VARIANT, font_variant, spec);
642 else
643 add_property(match, PRO_FONT_VARIANT, &static_value_normal, spec);
644
645 if (font_weight)
646 add_property(match, PRO_FONT_WEIGHT, font_weight, spec);
647 else
648 add_property(match, PRO_FONT_WEIGHT, &static_value_normal, spec);
649 }
650
651 static void
652 add_property(fz_css_match *match, int name, fz_css_value *value, int spec)
653 {
654 /* shorthand expansions: */
655 switch (name)
656 {
657 case PRO_MARGIN:
658 add_shorthand_margin(match, value, spec);
659 return;
660 case PRO_PADDING:
661 add_shorthand_padding(match, value, spec);
662 return;
663 case PRO_BORDER_WIDTH:
664 add_shorthand_border_width(match, value, spec);
665 return;
666 case PRO_BORDER_COLOR:
667 add_shorthand_border_color(match, value, spec);
668 return;
669 case PRO_BORDER_STYLE:
670 add_shorthand_border_style(match, value, spec);
671 return;
672 case PRO_BORDER:
673 add_shorthand_border(match, value, spec, 1, 1, 1, 1);
674 return;
675 case PRO_BORDER_TOP:
676 add_shorthand_border(match, value, spec, 1, 0, 0, 0);
677 return;
678 case PRO_BORDER_RIGHT:
679 add_shorthand_border(match, value, spec, 0, 1, 0, 0);
680 return;
681 case PRO_BORDER_BOTTOM:
682 add_shorthand_border(match, value, spec, 0, 0, 1, 0);
683 return;
684 case PRO_BORDER_LEFT:
685 add_shorthand_border(match, value, spec, 0, 0, 0, 1);
686 return;
687 case PRO_LIST_STYLE:
688 add_shorthand_list_style(match, value, spec);
689 return;
690 case PRO_FONT:
691 add_shorthand_font(match, value, spec);
692 return;
693 /* TODO: background */
694 }
695
696 if (name < NUM_PROPERTIES && match->spec[name] <= spec)
697 {
698 match->value[name] = value;
699 match->spec[name] = spec;
700 }
701 }
702
703 void
704 fz_match_css(fz_context *ctx, fz_css_match *match, fz_css_match *up, fz_css *css, fz_xml *node)
705 {
706 fz_css_rule *rule;
707 fz_css_selector *sel;
708 fz_css_property *prop;
709 const char *s;
710 int i;
711
712 match->up = up;
713 for (i = 0; i < NUM_PROPERTIES; ++i)
714 {
715 match->spec[i] = -1;
716 match->value[i] = NULL;
717 }
718
719 for (rule = css->rule; rule; rule = rule->next)
720 {
721 sel = rule->selector;
722 while (sel)
723 {
724 if (match_selector(sel, node))
725 {
726 for (prop = rule->declaration; prop; prop = prop->next)
727 add_property(match, prop->name, prop->value, selector_specificity(sel, prop->important));
728 break;
729 }
730 sel = sel->next;
731 }
732 }
733
734 if (fz_use_document_css(ctx))
735 {
736 s = fz_xml_att(node, "style");
737 if (s)
738 {
739 fz_try(ctx)
740 {
741 prop = fz_parse_css_properties(ctx, css->pool, s);
742 while (prop)
743 {
744 add_property(match, prop->name, prop->value, INLINE_SPECIFICITY);
745 prop = prop->next;
746 }
747 /* We can "leak" the property here, since it is freed along with the pool allocator. */
748 }
749 fz_catch(ctx)
750 {
751 fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
752 fz_report_error(ctx);
753 fz_warn(ctx, "ignoring style attribute");
754 }
755 }
756 }
757 }
758
759 void
760 fz_match_css_at_page(fz_context *ctx, fz_css_match *match, fz_css *css)
761 {
762 fz_css_rule *rule;
763 fz_css_selector *sel;
764 fz_css_property *prop;
765 int i;
766
767 match->up = NULL;
768 for (i = 0; i < NUM_PROPERTIES; ++i)
769 {
770 match->spec[i] = -1;
771 match->value[i] = NULL;
772 }
773
774 for (rule = css->rule; rule; rule = rule->next)
775 {
776 sel = rule->selector;
777 while (sel)
778 {
779 if (sel->name && !strcmp(sel->name, "@page"))
780 {
781 for (prop = rule->declaration; prop; prop = prop->next)
782 add_property(match, prop->name, prop->value, selector_specificity(sel, prop->important));
783 break;
784 }
785 sel = sel->next;
786 }
787 }
788 }
789
790 void
791 fz_add_css_font_face(fz_context *ctx, fz_html_font_set *set, fz_archive *zip, const char *base_uri, fz_css_property *declaration)
792 {
793 fz_html_font_face *custom;
794 fz_css_property *prop;
795 fz_font *font = NULL;
796 fz_buffer *buf = NULL;
797 fz_stream *stm = NULL;
798 int is_bold, is_italic, is_small_caps;
799 char path[2048];
800
801 const char *family = "serif";
802 const char *weight = "normal";
803 const char *style = "normal";
804 const char *variant = "normal";
805 const char *src = NULL;
806
807 for (prop = declaration; prop; prop = prop->next)
808 {
809 if (prop->name == PRO_FONT_FAMILY) family = prop->value->data;
810 if (prop->name == PRO_FONT_WEIGHT) weight = prop->value->data;
811 if (prop->name == PRO_FONT_STYLE) style = prop->value->data;
812 if (prop->name == PRO_FONT_VARIANT) variant = prop->value->data;
813 if (prop->name == PRO_SRC) src = prop->value->data;
814 }
815
816 if (!src)
817 return;
818
819 is_bold = is_bold_from_font_weight(weight);
820 is_italic = is_italic_from_font_style(style);
821 is_small_caps = !strcmp(variant, "small-caps");
822
823 fz_strlcpy(path, base_uri, sizeof path);
824 fz_strlcat(path, "/", sizeof path);
825 fz_strlcat(path, src, sizeof path);
826 fz_urldecode(path);
827
828 for (custom = set->custom; custom; custom = custom->next)
829 if (!strcmp(custom->src, path) && !strcmp(custom->family, family) &&
830 custom->is_bold == is_bold &&
831 custom->is_italic == is_italic &&
832 custom->is_small_caps == is_small_caps)
833 return; /* already loaded */
834
835 fz_var(buf);
836 fz_var(font);
837 fz_var(stm);
838
839 fz_try(ctx)
840 {
841 if (fz_has_archive_entry(ctx, zip, path))
842 buf = fz_read_archive_entry(ctx, zip, path);
843 else
844 {
845 stm = fz_try_open_file(ctx, src);
846 if (stm == NULL)
847 fz_throw(ctx, FZ_ERROR_FORMAT, "cannot locate font '%s' specified by css", src);
848 buf = fz_read_all(ctx, stm, 0);
849 }
850 font = fz_new_font_from_buffer(ctx, NULL, buf, 0, 0);
851 fz_add_html_font_face(ctx, set, family, is_bold, is_italic, is_small_caps, path, font);
852 }
853 fz_always(ctx)
854 {
855 fz_drop_buffer(ctx, buf);
856 fz_drop_stream(ctx, stm);
857 fz_drop_font(ctx, font);
858 }
859 fz_catch(ctx)
860 {
861 fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
862 fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
863 fz_report_error(ctx);
864 fz_warn(ctx, "cannot load font-face: %s", src);
865 }
866 }
867
868 void
869 fz_add_css_font_faces(fz_context *ctx, fz_html_font_set *set, fz_archive *zip, const char *base_uri, fz_css *css)
870 {
871 fz_css_rule *rule;
872 fz_css_selector *sel;
873
874 for (rule = css->rule; rule; rule = rule->next)
875 {
876 if (!rule->loaded)
877 {
878 rule->loaded = 1;
879 sel = rule->selector;
880 while (sel)
881 {
882 if (sel->name && !strcmp(sel->name, "@font-face"))
883 {
884 fz_add_css_font_face(ctx, set, zip, base_uri, rule->declaration);
885 break;
886 }
887 sel = sel->next;
888 }
889 }
890 }
891 }
892
893 static int
894 is_inheritable_property(int name)
895 {
896 return
897 name == PRO_COLOR ||
898 name == PRO_DIRECTION ||
899 name == PRO_FONT_FAMILY ||
900 name == PRO_FONT_STYLE ||
901 name == PRO_FONT_VARIANT ||
902 name == PRO_FONT_WEIGHT ||
903 name == PRO_LEADING ||
904 name == PRO_LETTER_SPACING ||
905 name == PRO_LINE_HEIGHT ||
906 name == PRO_LIST_STYLE_IMAGE ||
907 name == PRO_LIST_STYLE_POSITION ||
908 name == PRO_LIST_STYLE_TYPE ||
909 name == PRO_ORPHANS ||
910 name == PRO_OVERFLOW_WRAP ||
911 name == PRO_QUOTES ||
912 name == PRO_TEXT_ALIGN ||
913 name == PRO_TEXT_INDENT ||
914 name == PRO_TEXT_TRANSFORM ||
915 name == PRO_VISIBILITY ||
916 name == PRO_WHITE_SPACE ||
917 name == PRO_WIDOWS ||
918 name == PRO_WORD_SPACING ||
919 // Strictly speaking, text-decoration is not an inherited property,
920 // but since when drawing an underlined element, all children are also underlined,
921 // we may as well make it inherited.
922 name == PRO_TEXT_DECORATION;
923 }
924
925 static fz_css_value *
926 value_from_inheritable_property(fz_css_match *match, int name)
927 {
928 fz_css_value *value = match->value[name];
929 if (match->up)
930 {
931 if (value && !strcmp(value->data, "inherit"))
932 return value_from_inheritable_property(match->up, name);
933 if (!value)
934 return value_from_inheritable_property(match->up, name);
935 }
936 return value;
937 }
938
939 static fz_css_value *
940 value_from_property(fz_css_match *match, int name)
941 {
942 fz_css_value *value = match->value[name];
943 if (match->up)
944 {
945 if (value && !strcmp(value->data, "inherit"))
946 if (name != PRO_FONT_SIZE) /* never inherit 'font-size' textually */
947 return value_from_property(match->up, name);
948 if (!value && is_inheritable_property(name))
949 return value_from_inheritable_property(match->up, name);
950 }
951 return value;
952 }
953
954 static const char *
955 string_from_property(fz_css_match *match, int name, const char *initial)
956 {
957 fz_css_value *value = value_from_property(match, name);
958 if (!value)
959 return initial;
960 return value->data;
961 }
962
963 static fz_css_number
964 make_number(float v, int u)
965 {
966 fz_css_number n;
967 n.value = v;
968 n.unit = u;
969 return n;
970 }
971
972 static fz_css_number
973 make_undefined_number(void)
974 {
975 fz_css_number n;
976 n.value = 0;
977 n.unit = N_UNDEFINED;
978 return n;
979 }
980
981 /* Fast but inaccurate strtof. */
982 static float
983 fz_css_strtof(char *s, char **endptr)
984 {
985 float sign = 1;
986 float v = 0;
987 float n = 0;
988 float d = 1;
989
990 if (*s == '-')
991 {
992 sign = -1;
993 ++s;
994 }
995
996 while (*s >= '0' && *s <= '9')
997 {
998 v = v * 10 + (*s - '0');
999 ++s;
1000 }
1001
1002 if (*s == '.')
1003 {
1004 ++s;
1005 while (*s >= '0' && *s <= '9')
1006 {
1007 n = n * 10 + (*s - '0');
1008 d = d * 10;
1009 ++s;
1010 }
1011 v += n / d;
1012 }
1013
1014 if (endptr)
1015 *endptr = s;
1016
1017 return sign * v;
1018 }
1019
1020 static fz_css_number
1021 number_from_value(fz_css_value *value, float initial, int initial_unit)
1022 {
1023 char *p;
1024
1025 if (!value)
1026 return make_number(initial, initial_unit);
1027
1028 if (value->type == CSS_PERCENT)
1029 return make_number(fz_css_strtof(value->data, NULL), N_PERCENT);
1030
1031 if (value->type == CSS_NUMBER)
1032 return make_number(fz_css_strtof(value->data, NULL), N_NUMBER);
1033
1034 if (value->type == CSS_LENGTH)
1035 {
1036 float x = fz_css_strtof(value->data, &p);
1037
1038 if (p[0] == 'e' && p[1] == 'm' && p[2] == 0)
1039 return make_number(x, N_SCALE);
1040 if (p[0] == 'e' && p[1] == 'x' && p[2] == 0)
1041 return make_number(x / 2, N_SCALE);
1042
1043 if (p[0] == 'i' && p[1] == 'n' && p[2] == 0)
1044 return make_number(x * 72, N_LENGTH);
1045 if (p[0] == 'c' && p[1] == 'm' && p[2] == 0)
1046 return make_number(x * 7200 / 254, N_LENGTH);
1047 if (p[0] == 'm' && p[1] == 'm' && p[2] == 0)
1048 return make_number(x * 720 / 254, N_LENGTH);
1049 if (p[0] == 'p' && p[1] == 'c' && p[2] == 0)
1050 return make_number(x * 12, N_LENGTH);
1051
1052 if (p[0] == 'p' && p[1] == 't' && p[2] == 0)
1053 return make_number(x, N_LENGTH);
1054 if (p[0] == 'p' && p[1] == 'x' && p[2] == 0)
1055 return make_number(x, N_LENGTH);
1056
1057 /* FIXME: 'rem' should be 'em' of root element. This is a bad approximation. */
1058 if (p[0] == 'r' && p[1] == 'e' && p[2] == 'm' && p[3] == 0)
1059 return make_number(x * 16, N_LENGTH);
1060
1061 /* FIXME: 'ch' should be width of '0' character. This is an approximation. */
1062 if (p[0] == 'c' && p[1] == 'h' && p[2] == 0)
1063 return make_number(x / 2, N_LENGTH);
1064
1065 return make_number(x, N_LENGTH);
1066 }
1067
1068 if (value->type == CSS_KEYWORD)
1069 {
1070 if (!strcmp(value->data, "auto"))
1071 return make_number(0, N_AUTO);
1072 }
1073
1074 return make_number(initial, initial_unit);
1075 }
1076
1077 static fz_css_number
1078 number_from_property(fz_css_match *match, int property, float initial, int initial_unit)
1079 {
1080 return number_from_value(value_from_property(match, property), initial, initial_unit);
1081 }
1082
1083 static fz_css_number
1084 border_width_from_property(fz_css_match *match, int property)
1085 {
1086 fz_css_value *value = value_from_property(match, property);
1087 if (value)
1088 {
1089 if (!strcmp(value->data, "thin"))
1090 return make_number(1, N_LENGTH);
1091 if (!strcmp(value->data, "medium"))
1092 return make_number(2, N_LENGTH);
1093 if (!strcmp(value->data, "thick"))
1094 return make_number(4, N_LENGTH);
1095 return number_from_value(value, 0, N_LENGTH);
1096 }
1097 return make_number(2, N_LENGTH); /* initial: 'medium' */
1098 }
1099
1100 static int
1101 border_style_from_property(fz_css_match *match, int property)
1102 {
1103 fz_css_value *value = value_from_property(match, property);
1104 if (value)
1105 {
1106 if (!strcmp(value->data, "none")) return BS_NONE;
1107 else if (!strcmp(value->data, "hidden")) return BS_NONE;
1108 else if (!strcmp(value->data, "solid")) return BS_SOLID;
1109 }
1110 return BS_NONE;
1111 }
1112
1113 int fz_css_number_defined(fz_css_number number)
1114 {
1115 return number.unit != N_UNDEFINED;
1116 }
1117
1118 float
1119 fz_from_css_number(fz_css_number number, float em, float percent_value, float auto_value)
1120 {
1121 switch (number.unit) {
1122 default:
1123 case N_NUMBER: return number.value;
1124 case N_LENGTH: return number.value;
1125 case N_SCALE: return number.value * em;
1126 case N_PERCENT: return number.value * 0.01f * percent_value;
1127 case N_AUTO: return auto_value;
1128 }
1129 }
1130
1131 float
1132 fz_from_css_number_scale(fz_css_number number, float scale)
1133 {
1134 switch (number.unit) {
1135 default:
1136 case N_NUMBER: return number.value * scale;
1137 case N_LENGTH: return number.value;
1138 case N_SCALE: return number.value * scale;
1139 case N_PERCENT: return number.value * 0.01f * scale;
1140 case N_AUTO: return scale;
1141 }
1142 }
1143
1144 static fz_css_color
1145 make_color(int r, int g, int b, int a)
1146 {
1147 fz_css_color c;
1148 c.r = r < 0 ? 0 : r > 255 ? 255 : r;
1149 c.g = g < 0 ? 0 : g > 255 ? 255 : g;
1150 c.b = b < 0 ? 0 : b > 255 ? 255 : b;
1151 c.a = a < 0 ? 0 : a > 255 ? 255 : a;
1152 return c;
1153 }
1154
1155 static int tohex(int c)
1156 {
1157 if (c - '0' < 10)
1158 return c - '0';
1159 return (c | 32) - 'a' + 10;
1160 }
1161
1162 static fz_css_color
1163 color_from_value(fz_css_value *value, fz_css_color initial)
1164 {
1165 if (!value)
1166 return initial;
1167
1168 if (value->type == CSS_HASH)
1169 {
1170 int r, g, b, a;
1171 size_t n;
1172 hex_color:
1173 n = strlen(value->data);
1174 if (n == 3)
1175 {
1176 r = tohex(value->data[0]) * 16 + tohex(value->data[0]);
1177 g = tohex(value->data[1]) * 16 + tohex(value->data[1]);
1178 b = tohex(value->data[2]) * 16 + tohex(value->data[2]);
1179 a = 255;
1180 }
1181 else if (n == 4)
1182 {
1183 r = tohex(value->data[0]) * 16 + tohex(value->data[0]);
1184 g = tohex(value->data[1]) * 16 + tohex(value->data[1]);
1185 b = tohex(value->data[2]) * 16 + tohex(value->data[2]);
1186 a = tohex(value->data[3]) * 16 + tohex(value->data[3]);
1187 }
1188 else if (n == 6)
1189 {
1190 r = tohex(value->data[0]) * 16 + tohex(value->data[1]);
1191 g = tohex(value->data[2]) * 16 + tohex(value->data[3]);
1192 b = tohex(value->data[4]) * 16 + tohex(value->data[5]);
1193 a = 255;
1194 }
1195 else if (n == 8)
1196 {
1197 r = tohex(value->data[0]) * 16 + tohex(value->data[1]);
1198 g = tohex(value->data[2]) * 16 + tohex(value->data[3]);
1199 b = tohex(value->data[4]) * 16 + tohex(value->data[5]);
1200 a = tohex(value->data[6]) * 16 + tohex(value->data[7]);
1201 }
1202 else
1203 {
1204 r = g = b = 0;
1205 a = 255;
1206 }
1207 return make_color(r, g, b, a);
1208 }
1209
1210 if (value->type == '(' && !strcmp(value->data, "rgb"))
1211 {
1212 fz_css_value *vr, *vg, *vb;
1213 int r, g, b;
1214 vr = value->args;
1215 vg = vr && vr->next ? vr->next->next : NULL; /* skip the ',' nodes */
1216 vb = vg && vg->next ? vg->next->next : NULL; /* skip the ',' nodes */
1217 r = fz_from_css_number(number_from_value(vr, 0, N_NUMBER), 255, 255, 0);
1218 g = fz_from_css_number(number_from_value(vg, 0, N_NUMBER), 255, 255, 0);
1219 b = fz_from_css_number(number_from_value(vb, 0, N_NUMBER), 255, 255, 0);
1220 return make_color(r, g, b, 255);
1221 }
1222
1223 if (value->type == '(' && !strcmp(value->data, "rgba"))
1224 {
1225 fz_css_value *vr, *vg, *vb, *va;
1226 int r, g, b, a;
1227 vr = value->args;
1228 vg = vr && vr->next ? vr->next->next : NULL; /* skip the ',' nodes */
1229 vb = vg && vg->next ? vg->next->next : NULL; /* skip the ',' nodes */
1230 va = vb && vb->next ? vb->next->next : NULL; /* skip the ',' nodes */
1231 r = fz_from_css_number(number_from_value(vr, 0, N_NUMBER), 255, 255, 0);
1232 g = fz_from_css_number(number_from_value(vg, 0, N_NUMBER), 255, 255, 0);
1233 b = fz_from_css_number(number_from_value(vb, 0, N_NUMBER), 255, 255, 0);
1234 a = fz_from_css_number(number_from_value(va, 0, N_NUMBER), 255, 255, 255);
1235 return make_color(r, g, b, a);
1236 }
1237
1238 if (value->type == CSS_KEYWORD)
1239 {
1240 if (!strcmp(value->data, "transparent"))
1241 return make_color(0, 0, 0, 0);
1242 if (!strcmp(value->data, "maroon"))
1243 return make_color(0x80, 0x00, 0x00, 255);
1244 if (!strcmp(value->data, "red"))
1245 return make_color(0xFF, 0x00, 0x00, 255);
1246 if (!strcmp(value->data, "orange"))
1247 return make_color(0xFF, 0xA5, 0x00, 255);
1248 if (!strcmp(value->data, "yellow"))
1249 return make_color(0xFF, 0xFF, 0x00, 255);
1250 if (!strcmp(value->data, "olive"))
1251 return make_color(0x80, 0x80, 0x00, 255);
1252 if (!strcmp(value->data, "purple"))
1253 return make_color(0x80, 0x00, 0x80, 255);
1254 if (!strcmp(value->data, "fuchsia"))
1255 return make_color(0xFF, 0x00, 0xFF, 255);
1256 if (!strcmp(value->data, "white"))
1257 return make_color(0xFF, 0xFF, 0xFF, 255);
1258 if (!strcmp(value->data, "lime"))
1259 return make_color(0x00, 0xFF, 0x00, 255);
1260 if (!strcmp(value->data, "green"))
1261 return make_color(0x00, 0x80, 0x00, 255);
1262 if (!strcmp(value->data, "navy"))
1263 return make_color(0x00, 0x00, 0x80, 255);
1264 if (!strcmp(value->data, "blue"))
1265 return make_color(0x00, 0x00, 0xFF, 255);
1266 if (!strcmp(value->data, "aqua"))
1267 return make_color(0x00, 0xFF, 0xFF, 255);
1268 if (!strcmp(value->data, "teal"))
1269 return make_color(0x00, 0x80, 0x80, 255);
1270 if (!strcmp(value->data, "black"))
1271 return make_color(0x00, 0x00, 0x00, 255);
1272 if (!strcmp(value->data, "silver"))
1273 return make_color(0xC0, 0xC0, 0xC0, 255);
1274 if (!strcmp(value->data, "gray"))
1275 return make_color(0x80, 0x80, 0x80, 255);
1276 goto hex_color; /* last ditch attempt: maybe it's a #XXXXXX color without the # */
1277 }
1278 return initial;
1279 }
1280
1281 static fz_css_color
1282 color_from_property(fz_css_match *match, int property, fz_css_color initial)
1283 {
1284 return color_from_value(value_from_property(match, property), initial);
1285 }
1286
1287 static fz_css_color
1288 color_from_properties(fz_css_match *match, int property, int property2, fz_css_color initial)
1289 {
1290 fz_css_value *value = value_from_property(match, property);
1291
1292 if (value == NULL)
1293 value = value_from_property(match, property2);
1294
1295 return color_from_value(value, initial);
1296 }
1297
1298 int
1299 fz_get_css_match_display(fz_css_match *match)
1300 {
1301 fz_css_value *value = value_from_property(match, PRO_DISPLAY);
1302 if (value)
1303 {
1304 if (!strcmp(value->data, "none"))
1305 return DIS_NONE;
1306 if (!strcmp(value->data, "inline"))
1307 return DIS_INLINE;
1308 if (!strcmp(value->data, "block"))
1309 return DIS_BLOCK;
1310 if (!strcmp(value->data, "list-item"))
1311 return DIS_LIST_ITEM;
1312 if (!strcmp(value->data, "inline-block"))
1313 return DIS_INLINE_BLOCK;
1314 if (!strcmp(value->data, "table"))
1315 return DIS_TABLE;
1316 if (!strcmp(value->data, "table-row"))
1317 return DIS_TABLE_ROW;
1318 if (!strcmp(value->data, "table-cell"))
1319 return DIS_TABLE_CELL;
1320 if (!strcmp(value->data, "table-row-group"))
1321 return DIS_TABLE_GROUP;
1322 if (!strcmp(value->data, "table-header-group"))
1323 return DIS_TABLE_GROUP;
1324 if (!strcmp(value->data, "table-footer-group"))
1325 return DIS_TABLE_GROUP;
1326 if (!strcmp(value->data, "table-column-group"))
1327 return DIS_NONE;
1328 if (!strcmp(value->data, "table-column"))
1329 return DIS_NONE;
1330 }
1331 return DIS_INLINE;
1332 }
1333
1334 static int
1335 white_space_from_property(fz_css_match *match)
1336 {
1337 fz_css_value *value = value_from_property(match, PRO_WHITE_SPACE);
1338 if (value)
1339 {
1340 if (!strcmp(value->data, "normal")) return WS_NORMAL;
1341 else if (!strcmp(value->data, "pre")) return WS_PRE;
1342 else if (!strcmp(value->data, "nowrap")) return WS_NOWRAP;
1343 else if (!strcmp(value->data, "pre-wrap")) return WS_PRE_WRAP;
1344 else if (!strcmp(value->data, "pre-line")) return WS_PRE_LINE;
1345 }
1346 return WS_NORMAL;
1347 }
1348
1349 static int
1350 text_decoration_from_property(fz_css_match *match)
1351 {
1352 fz_css_value *value = value_from_property(match, PRO_TEXT_DECORATION);
1353 if (value)
1354 {
1355 if (!strcmp(value->data, "underline")) return TD_UNDERLINE;
1356 if (!strcmp(value->data, "line-through")) return TD_LINE_THROUGH;
1357 }
1358 return TD_NONE;
1359 }
1360
1361 static int
1362 visibility_from_property(fz_css_match *match)
1363 {
1364 fz_css_value *value = value_from_property(match, PRO_VISIBILITY);
1365 if (value)
1366 {
1367 if (!strcmp(value->data, "visible")) return V_VISIBLE;
1368 else if (!strcmp(value->data, "hidden")) return V_HIDDEN;
1369 else if (!strcmp(value->data, "collapse")) return V_COLLAPSE;
1370 }
1371 return V_VISIBLE;
1372 }
1373
1374 static int
1375 page_break_from_property(fz_css_match *match, int prop)
1376 {
1377 fz_css_value *value = value_from_property(match, prop);
1378 if (value)
1379 {
1380 if (!strcmp(value->data, "auto")) return PB_AUTO;
1381 else if (!strcmp(value->data, "always")) return PB_ALWAYS;
1382 else if (!strcmp(value->data, "avoid")) return PB_AVOID;
1383 else if (!strcmp(value->data, "left")) return PB_LEFT;
1384 else if (!strcmp(value->data, "right")) return PB_RIGHT;
1385 }
1386 return PB_AUTO;
1387 }
1388
1389 void
1390 fz_default_css_style(fz_context *ctx, fz_css_style *style)
1391 {
1392 memset(style, 0, sizeof *style);
1393 style->visibility = V_VISIBLE;
1394 style->text_align = TA_LEFT;
1395 style->vertical_align = VA_BASELINE;
1396 style->white_space = WS_NORMAL;
1397 style->list_style_type = LST_DISC;
1398 style->font_size = make_number(1, N_SCALE);
1399 style->width = make_number(0, N_AUTO);
1400 style->height = make_number(0, N_AUTO);
1401 style->leading = make_undefined_number();
1402 }
1403
1404 void
1405 fz_apply_css_style(fz_context *ctx, fz_html_font_set *set, fz_css_style *style, fz_css_match *match)
1406 {
1407 fz_css_value *value;
1408
1409 fz_css_color black = { 0, 0, 0, 255 };
1410 fz_css_color transparent = { 0, 0, 0, 0 };
1411
1412 fz_default_css_style(ctx, style);
1413
1414 style->visibility = visibility_from_property(match);
1415 style->white_space = white_space_from_property(match);
1416 style->text_decoration = text_decoration_from_property(match);
1417 style->page_break_before = page_break_from_property(match, PRO_PAGE_BREAK_BEFORE);
1418 style->page_break_after = page_break_from_property(match, PRO_PAGE_BREAK_AFTER);
1419
1420 value = value_from_property(match, PRO_TEXT_ALIGN);
1421 if (value)
1422 {
1423 if (!strcmp(value->data, "left")) style->text_align = TA_LEFT;
1424 else if (!strcmp(value->data, "right")) style->text_align = TA_RIGHT;
1425 else if (!strcmp(value->data, "center")) style->text_align = TA_CENTER;
1426 else if (!strcmp(value->data, "justify")) style->text_align = TA_JUSTIFY;
1427 }
1428
1429 value = value_from_property(match, PRO_VERTICAL_ALIGN);
1430 if (value)
1431 {
1432 if (!strcmp(value->data, "baseline")) style->vertical_align = VA_BASELINE;
1433 else if (!strcmp(value->data, "sub")) style->vertical_align = VA_SUB;
1434 else if (!strcmp(value->data, "super")) style->vertical_align = VA_SUPER;
1435 else if (!strcmp(value->data, "top")) style->vertical_align = VA_TOP;
1436 else if (!strcmp(value->data, "bottom")) style->vertical_align = VA_BOTTOM;
1437 else if (!strcmp(value->data, "text-top")) style->vertical_align = VA_TEXT_TOP;
1438 else if (!strcmp(value->data, "text-bottom")) style->vertical_align = VA_TEXT_BOTTOM;
1439 }
1440
1441 value = value_from_property(match, PRO_FONT_SIZE);
1442 if (value)
1443 {
1444 /* absolute-size */
1445 if (!strcmp(value->data, "xx-large")) style->font_size = make_number(1.73f, N_SCALE);
1446 else if (!strcmp(value->data, "x-large")) style->font_size = make_number(1.44f, N_SCALE);
1447 else if (!strcmp(value->data, "large")) style->font_size = make_number(1.2f, N_SCALE);
1448 else if (!strcmp(value->data, "medium")) style->font_size = make_number(1.0f, N_SCALE);
1449 else if (!strcmp(value->data, "small")) style->font_size = make_number(0.83f, N_SCALE);
1450 else if (!strcmp(value->data, "x-small")) style->font_size = make_number(0.69f, N_SCALE);
1451 else if (!strcmp(value->data, "xx-small")) style->font_size = make_number(0.69f, N_SCALE);
1452 /* relative-size */
1453 else if (!strcmp(value->data, "larger")) style->font_size = make_number(1.2f, N_SCALE);
1454 else if (!strcmp(value->data, "smaller")) style->font_size = make_number(1/1.2f, N_SCALE);
1455 /* percentage */
1456 else if (value->type == CSS_PERCENT) style->font_size = number_from_value(value, 12, N_LENGTH);
1457 /* length */
1458 else if (value->type == CSS_LENGTH) style->font_size = number_from_value(value, 12, N_LENGTH);
1459 /* default to 1em */
1460 else style->font_size = make_number(1, N_SCALE);
1461 }
1462 else
1463 {
1464 style->font_size = make_number(1, N_SCALE);
1465 }
1466
1467 value = value_from_property(match, PRO_LIST_STYLE_TYPE);
1468 if (value)
1469 {
1470 if (!strcmp(value->data, "none")) style->list_style_type = LST_NONE;
1471 else if (!strcmp(value->data, "disc")) style->list_style_type = LST_DISC;
1472 else if (!strcmp(value->data, "circle")) style->list_style_type = LST_CIRCLE;
1473 else if (!strcmp(value->data, "square")) style->list_style_type = LST_SQUARE;
1474 else if (!strcmp(value->data, "decimal")) style->list_style_type = LST_DECIMAL;
1475 else if (!strcmp(value->data, "decimal-leading-zero")) style->list_style_type = LST_DECIMAL_ZERO;
1476 else if (!strcmp(value->data, "lower-roman")) style->list_style_type = LST_LC_ROMAN;
1477 else if (!strcmp(value->data, "upper-roman")) style->list_style_type = LST_UC_ROMAN;
1478 else if (!strcmp(value->data, "lower-greek")) style->list_style_type = LST_LC_GREEK;
1479 else if (!strcmp(value->data, "upper-greek")) style->list_style_type = LST_UC_GREEK;
1480 else if (!strcmp(value->data, "lower-latin")) style->list_style_type = LST_LC_LATIN;
1481 else if (!strcmp(value->data, "upper-latin")) style->list_style_type = LST_UC_LATIN;
1482 else if (!strcmp(value->data, "lower-alpha")) style->list_style_type = LST_LC_ALPHA;
1483 else if (!strcmp(value->data, "upper-alpha")) style->list_style_type = LST_UC_ALPHA;
1484 else if (!strcmp(value->data, "armenian")) style->list_style_type = LST_ARMENIAN;
1485 else if (!strcmp(value->data, "georgian")) style->list_style_type = LST_GEORGIAN;
1486 }
1487
1488 value = value_from_property(match, PRO_OVERFLOW_WRAP);
1489 if (value)
1490 {
1491 if (!strcmp(value->data, "break-word")) style->overflow_wrap = OVERFLOW_WRAP_BREAK_WORD;
1492 else style->overflow_wrap = OVERFLOW_WRAP_NORMAL;
1493 }
1494
1495 style->line_height = number_from_property(match, PRO_LINE_HEIGHT, 1.2f, N_SCALE);
1496 style->leading = number_from_property(match, PRO_LEADING, 0, N_UNDEFINED);
1497
1498 style->text_indent = number_from_property(match, PRO_TEXT_INDENT, 0, N_LENGTH);
1499 style->text_stroke_width = number_from_property(match, PRO_TEXT_STROKE_WIDTH, 0, N_LENGTH);
1500
1501 style->width = number_from_property(match, PRO_WIDTH, 0, N_AUTO);
1502 style->height = number_from_property(match, PRO_HEIGHT, 0, N_AUTO);
1503
1504 style->margin[0] = number_from_property(match, PRO_MARGIN_TOP, 0, N_LENGTH);
1505 style->margin[1] = number_from_property(match, PRO_MARGIN_RIGHT, 0, N_LENGTH);
1506 style->margin[2] = number_from_property(match, PRO_MARGIN_BOTTOM, 0, N_LENGTH);
1507 style->margin[3] = number_from_property(match, PRO_MARGIN_LEFT, 0, N_LENGTH);
1508
1509 style->padding[0] = number_from_property(match, PRO_PADDING_TOP, 0, N_LENGTH);
1510 style->padding[1] = number_from_property(match, PRO_PADDING_RIGHT, 0, N_LENGTH);
1511 style->padding[2] = number_from_property(match, PRO_PADDING_BOTTOM, 0, N_LENGTH);
1512 style->padding[3] = number_from_property(match, PRO_PADDING_LEFT, 0, N_LENGTH);
1513
1514 style->color = color_from_property(match, PRO_COLOR, black);
1515 style->text_fill_color = color_from_properties(match, PRO_TEXT_FILL_COLOR, PRO_COLOR, black);
1516 style->text_stroke_color = color_from_property(match, PRO_TEXT_STROKE_COLOR, transparent);
1517 style->background_color = color_from_property(match, PRO_BACKGROUND_COLOR, transparent);
1518
1519 style->border_spacing = number_from_property(match, PRO_BORDER_SPACING, 0, N_LENGTH);
1520
1521 style->border_style_0 = border_style_from_property(match, PRO_BORDER_TOP_STYLE);
1522 style->border_style_1 = border_style_from_property(match, PRO_BORDER_RIGHT_STYLE);
1523 style->border_style_2 = border_style_from_property(match, PRO_BORDER_BOTTOM_STYLE);
1524 style->border_style_3 = border_style_from_property(match, PRO_BORDER_LEFT_STYLE);
1525
1526 style->border_color[0] = color_from_property(match, PRO_BORDER_TOP_COLOR, style->color);
1527 style->border_color[1] = color_from_property(match, PRO_BORDER_RIGHT_COLOR, style->color);
1528 style->border_color[2] = color_from_property(match, PRO_BORDER_BOTTOM_COLOR, style->color);
1529 style->border_color[3] = color_from_property(match, PRO_BORDER_LEFT_COLOR, style->color);
1530
1531 style->border_width[0] = border_width_from_property(match, PRO_BORDER_TOP_WIDTH);
1532 style->border_width[1] = border_width_from_property(match, PRO_BORDER_RIGHT_WIDTH);
1533 style->border_width[2] = border_width_from_property(match, PRO_BORDER_BOTTOM_WIDTH);
1534 style->border_width[3] = border_width_from_property(match, PRO_BORDER_LEFT_WIDTH);
1535
1536 {
1537 const char *font_weight = string_from_property(match, PRO_FONT_WEIGHT, "normal");
1538 const char *font_style = string_from_property(match, PRO_FONT_STYLE, "normal");
1539 const char *font_variant = string_from_property(match, PRO_FONT_VARIANT, "normal");
1540 int is_bold = is_bold_from_font_weight(font_weight);
1541 int is_italic = is_italic_from_font_style(font_style);
1542 style->small_caps = !strcmp(font_variant, "small-caps");
1543 value = value_from_property(match, PRO_FONT_FAMILY);
1544 for (; value; value = value->next)
1545 {
1546 /* ignore numbers and keywords used in font short-hand syntax */
1547 if (value->type == CSS_STRING || (value->type == CSS_KEYWORD && !keyword_in_list(value->data, font_family_ignore, nelem(font_family_ignore))))
1548 {
1549 style->font = fz_load_html_font(ctx, set, value->data, is_bold, is_italic, style->small_caps);
1550 if (style->font)
1551 break;
1552 }
1553 }
1554 if (!style->font)
1555 style->font = fz_load_html_font(ctx, set, "serif", is_bold, is_italic, style->small_caps);
1556 }
1557 }
1558
1559 #ifdef DEBUG_CSS_SPLAY
1560 static void
1561 do_verify_splay(const fz_css_style_splay *x)
1562 {
1563 printf("%x<", x);
1564 if (x->lt)
1565 {
1566 assert(memcmp(&x->lt->style, &x->style, sizeof(x->style)) < 0);
1567 assert(x->lt->up == x);
1568 do_verify_splay(x->lt);
1569 }
1570 printf(",");
1571 if (x->gt)
1572 {
1573 assert(memcmp(&x->gt->style, &x->style, sizeof(x->style)) > 0);
1574 assert(x->gt->up == x);
1575 do_verify_splay(x->gt);
1576 }
1577 printf(">\n");
1578 }
1579
1580 static void
1581 verify_splay(const fz_css_style_splay *x)
1582 {
1583 if (x == NULL)
1584 return;
1585 assert(x->up == NULL);
1586 do_verify_splay(x);
1587 printf("-----\n");
1588 }
1589 #endif
1590
1591 const fz_css_style *
1592 fz_css_enlist(fz_context *ctx, const fz_css_style *style, fz_css_style_splay **tree, fz_pool *pool)
1593 {
1594 fz_css_style_splay **current = tree;
1595 fz_css_style_splay *x;
1596 fz_css_style_splay *y = NULL;
1597
1598 /* Search for a match in the tree, if there is one, or for
1599 * the insertion point, if there is not. */
1600 while (*current != NULL)
1601 {
1602 int cmp = memcmp(style, &(*current)->style, sizeof(*style));
1603 if (cmp == 0)
1604 {
1605 /* We have a match - break out and do move to root. */
1606 break;
1607 }
1608 y = (*current);
1609 if (cmp < 0)
1610 current = &y->lt;
1611 else
1612 current = &y->gt;
1613 }
1614 /* Create one if needed */
1615 if (*current == NULL)
1616 {
1617 x = *current = fz_pool_alloc(ctx, pool, sizeof(*y));
1618 x->style = *style;
1619 x->up = y;
1620 x->lt = NULL;
1621 x->gt = NULL;
1622 }
1623 else
1624 x = *current;
1625 /* Now move to root */
1626 /*
1627 The splaying steps used:
1628
1629 Case 1: |a) z x b) z x
1630 | y D => A y A y y D
1631 | x C B z B x => z C
1632 | A B C D C D A B
1633
1634 Case 2: |a) z x b) z x
1635 | y D => y z A y => z y
1636 | A x A B C D x D A B C D
1637 | B C B C
1638
1639 Case 3: |a) y x b) y x
1640 | x C => A y A x => y C
1641 | A B B C B C A B
1642 */
1643 #ifdef DEBUG_CSS_SPLAY
1644 printf("BEFORE\n");
1645 verify_splay(*tree);
1646 #endif
1647 while ((y = x->up) != NULL ) /* While we're not at the root */
1648 {
1649 fz_css_style_splay *z = y->up;
1650 y->up = x;
1651 if (z == NULL)
1652 {
1653 if (y->lt == x) /* Case 3a */
1654 {
1655 y->lt = x->gt;
1656 if (y->lt)
1657 y->lt->up = y;
1658 x->gt = y;
1659 }
1660 else /* Case 3b */
1661 {
1662 y->gt = x->lt;
1663 if (y->gt)
1664 y->gt->up = y;
1665 x->lt = y;
1666 }
1667 x->up = NULL;
1668 break;
1669 }
1670 x->up = z->up;
1671 if (z->up)
1672 {
1673 if (z->up->lt == z)
1674 z->up->lt = x;
1675 else
1676 z->up->gt = x;
1677 }
1678 if (z->lt == y)
1679 {
1680 if (y->lt == x) /* Case 1a */
1681 {
1682 z->lt = y->gt;
1683 if (z->lt)
1684 z->lt->up = z;
1685 y->lt = x->gt;
1686 if (y->lt)
1687 y->lt->up = y;
1688 y->gt = z;
1689 z->up = y;
1690 x->gt = y;
1691 }
1692 else /* Case 2a */
1693 {
1694 y->gt = x->lt;
1695 if (y->gt)
1696 y->gt->up = y;
1697 z->lt = x->gt;
1698 if (z->lt)
1699 z->lt->up = z;
1700 x->lt = y;
1701 x->gt = z;
1702 z->up = x;
1703 }
1704 }
1705 else
1706 {
1707 if (y->gt == x) /* Case 1b */
1708 {
1709 z->gt = y->lt;
1710 if (z->gt)
1711 z->gt->up = z;
1712 y->gt = x->lt;
1713 if (y->gt)
1714 y->gt->up = y;
1715 y->lt = z;
1716 z->up = y;
1717 x->lt = y;
1718 }
1719 else /* Case 2b */
1720 {
1721 z->gt = x->lt;
1722 if (z->gt)
1723 z->gt->up = z;
1724 y->lt = x->gt;
1725 if (y->lt)
1726 y->lt->up = y;
1727 x->gt = y;
1728 x->lt = z;
1729 z->up = x;
1730 }
1731 }
1732 }
1733
1734 *tree = x;
1735 #ifdef DEBUG_CSS_SPLAY
1736 printf("AFTER\n");
1737 verify_splay(x);
1738 #endif
1739
1740 return &x->style;
1741 }
1742
1743 /*
1744 * Pretty printing
1745 */
1746
1747 static void print_value(fz_css_value *val)
1748 {
1749 printf("%s", val->data);
1750 if (val->args)
1751 {
1752 printf("(");
1753 print_value(val->args);
1754 printf(")");
1755 }
1756 if (val->next)
1757 {
1758 printf(" ");
1759 print_value(val->next);
1760 }
1761 }
1762
1763 static void print_property(fz_css_property *prop)
1764 {
1765 printf("\t%s: ", fz_css_property_name(prop->name));
1766 print_value(prop->value);
1767 if (prop->important)
1768 printf(" !important");
1769 printf(";\n");
1770 }
1771
1772 static void print_condition(fz_css_condition *cond)
1773 {
1774 if (cond->type == '=')
1775 printf("[%s=%s]", cond->key, cond->val);
1776 else if (cond->type == '[')
1777 printf("[%s]", cond->key);
1778 else
1779 printf("%c%s", cond->type, cond->val);
1780 if (cond->next)
1781 print_condition(cond->next);
1782 }
1783
1784 static void print_selector(fz_css_selector *sel)
1785 {
1786 if (sel->combine)
1787 {
1788 print_selector(sel->left);
1789 if (sel->combine == ' ')
1790 printf(" ");
1791 else
1792 printf(" %c ", sel->combine);
1793 print_selector(sel->right);
1794 }
1795 else if (sel->name)
1796 printf("%s", sel->name);
1797 else
1798 printf("*");
1799 if (sel->cond)
1800 {
1801 print_condition(sel->cond);
1802 }
1803 }
1804
1805 static void print_rule(fz_css_rule *rule)
1806 {
1807 fz_css_selector *sel;
1808 fz_css_property *prop;
1809
1810 for (sel = rule->selector; sel; sel = sel->next)
1811 {
1812 print_selector(sel);
1813 printf(" /* %d */", selector_specificity(sel, 0));
1814 if (sel->next)
1815 printf(", ");
1816 }
1817
1818 printf("\n{\n");
1819 for (prop = rule->declaration; prop; prop = prop->next)
1820 {
1821 print_property(prop);
1822 }
1823 printf("}\n");
1824 }
1825
1826 void
1827 fz_debug_css(fz_context *ctx, fz_css *css)
1828 {
1829 fz_css_rule *rule = css->rule;
1830 while (rule)
1831 {
1832 print_rule(rule);
1833 rule = rule->next;
1834 }
1835 }