Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/html/css-apply.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/html/css-apply.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1835 @@ +// Copyright (C) 2004-2024 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" +#include "html-imp.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +static const char *border_width_kw[] = { + "medium", + "thick", + "thin", +}; + +static const char *border_style_kw[] = { + "dashed", + "dotted", + "double", + "groove", + "hidden", + "inset", + "none", + "outset", + "ridge", + "solid", +}; + +static const char *color_kw[] = { + "aqua", + "black", + "blue", + "fuchsia", + "gray", + "green", + "lime", + "maroon", + "navy", + "olive", + "orange", + "purple", + "red", + "silver", + "teal", + "transparent", + "white", + "yellow", +}; + +static const char *list_style_type_kw[] = { + "armenian", + "circle", + "decimal", + "decimal-leading-zero", + "disc", + "georgian", + "lower-alpha", + "lower-greek", + "lower-latin", + "lower-roman", + "none", + "square", + "upper-alpha", + "upper-greek", + "upper-latin", + "upper-roman", +}; + +static const char *list_style_position_kw[] = { + "inside", + "outside", +}; + +static const char *font_style_kw[] = { + "italic", + "oblique", +}; + +static const char *font_variant_kw[] = { + "small-caps", +}; + +static const char *font_weight_kw[] = { + "bold", + "bolder", + "lighter", +}; + +static const char *font_size_kw[] = { + "large", + "larger", + "medium", + "small", + "smaller", + "x-large", + "x-small", + "xx-large", + "xx-small", +}; + +/* Properties to ignore when scanning through font-family. We set font-family + * to the full font shorthand value list because Adobe generates DS strings + * where the font-family comes before the font-size (and not at the end as it's + * supposed to). This lets us scan the font shorthand list without trying to + * look up fonts named "bold", etc. + */ +static const char *font_family_ignore[] = { + ",", + "/", + "bold", + "bolder", + "italic", + "large", + "larger", + "lighter", + "medium", + "oblique", + "small", + "small-caps", + "smaller", + "x-large", + "x-small", + "xx-large", + "xx-small", +}; + +static int +keyword_in_list(const char *name, const char **list, int n) +{ + int l = 0; + int r = n - 1; + while (l <= r) + { + int m = (l + r) >> 1; + int c = strcmp(name, list[m]); + if (c < 0) + r = m - 1; + else if (c > 0) + l = m + 1; + else + return 1; + } + return 0; +} + +static int +is_bold_from_font_weight(const char *weight) +{ + return !strcmp(weight, "bold") || !strcmp(weight, "bolder") || atoi(weight) > 400; +} + +static int +is_italic_from_font_style(const char *style) +{ + return !strcmp(style, "italic") || !strcmp(style, "oblique"); +} + +/* + * Compute specificity + */ + +static int +count_condition_ids(fz_css_condition *cond) +{ + int n = 0; + while (cond) + { + if (cond->type == '#') + n ++; + cond = cond->next; + } + return n; +} + +static int +count_selector_ids(fz_css_selector *sel) +{ + int n = count_condition_ids(sel->cond); + if (sel->left && sel->right) + { + n += count_selector_ids(sel->left); + n += count_selector_ids(sel->right); + } + return n; +} + +static int +count_condition_atts(fz_css_condition *cond) +{ + int n = 0; + while (cond) + { + if (cond->type != '#' && cond->type != ':') + n ++; + cond = cond->next; + } + return n; +} + +static int +count_selector_atts(fz_css_selector *sel) +{ + int n = count_condition_atts(sel->cond); + if (sel->left && sel->right) + { + n += count_selector_atts(sel->left); + n += count_selector_atts(sel->right); + } + return n; +} + +static int +count_condition_names(fz_css_condition *cond) +{ + int n = 0; + while (cond) + { + if (cond->type == ':') + n ++; + cond = cond->next; + } + return n; +} + +static int +count_selector_names(fz_css_selector *sel) +{ + int n = count_condition_names(sel->cond); + if (sel->left && sel->right) + { + n += count_selector_names(sel->left); + n += count_selector_names(sel->right); + } + else if (sel->name) + { + n ++; + } + return n; +} + +#define INLINE_SPECIFICITY 10000 + +static int +selector_specificity(fz_css_selector *sel, int important) +{ + int b = count_selector_ids(sel); + int c = count_selector_atts(sel); + int d = count_selector_names(sel); + return important * 1000 + b * 100 + c * 10 + d; +} + +/* + * Selector matching + */ + +static int +match_att_exists_condition(fz_xml *node, const char *key) +{ + const char *s = fz_xml_att(node, key); + return s != NULL; +} + +static int +match_att_is_condition(fz_xml *node, const char *key, const char *val) +{ + const char *att = fz_xml_att(node, key); + return att && !strcmp(val, att); +} + +static int +match_att_has_condition(fz_xml *node, const char *att, const char *needle) +{ + const char *haystack = fz_xml_att(node, att); + const char *ss; + size_t n; + if (haystack) { + ss = strstr(haystack, needle); + if (ss) + { + n = strlen(needle); + + /* Look for exact matches or matching words. */ + if ((ss[n] == ' ' || ss[n] == 0) && (ss == haystack || ss[-1] == ' ')) + return 1; + } + } + return 0; +} + +static int +match_condition(fz_css_condition *cond, fz_xml *node) +{ + if (!cond) + return 1; + + switch (cond->type) { + default: return 0; + case ':': return 0; /* don't support pseudo-classes */ + case '#': if (!match_att_is_condition(node, "id", cond->val)) return 0; break; + case '.': if (!match_att_has_condition(node, "class", cond->val)) return 0; break; + case '[': if (!match_att_exists_condition(node, cond->key)) return 0; break; + case '=': if (!match_att_is_condition(node, cond->key, cond->val)) return 0; break; + case '~': if (!match_att_has_condition(node, cond->key, cond->val)) return 0; break; + case '|': if (!match_att_is_condition(node, cond->key, cond->val)) return 0; break; + } + + return match_condition(cond->next, node); +} + +static int +match_selector(fz_css_selector *sel, fz_xml *node) +{ + if (!node) + return 0; + + if (sel->combine) + { + /* descendant */ + if (sel->combine == ' ') + { + fz_xml *parent = fz_xml_up(node); + if (!parent || !match_selector(sel->right, node)) + return 0; + + while (parent) + { + if (match_selector(sel->left, parent)) + return 1; + parent = fz_xml_up(parent); + } + return 0; + } + + /* child */ + if (sel->combine == '>') + { + fz_xml *parent = fz_xml_up(node); + if (!parent) + return 0; + if (!match_selector(sel->left, parent)) + return 0; + if (!match_selector(sel->right, node)) + return 0; + } + + /* adjacent */ + if (sel->combine == '+') + { + fz_xml *prev = fz_xml_prev(node); + while (prev && !fz_xml_tag(prev)) + prev = fz_xml_prev(prev); + if (!prev) + return 0; + if (!fz_xml_tag(prev)) + return 0; + if (!match_selector(sel->left, prev)) + return 0; + if (!match_selector(sel->right, node)) + return 0; + } + } + + if (sel->name) + { + if (!fz_xml_is_tag(node, sel->name)) + return 0; + } + + if (sel->cond) + { + if (!match_condition(sel->cond, node)) + return 0; + } + + return 1; +} + +/* + * Annotating nodes with properties and expanding shorthand forms. + */ + +static int +count_values(fz_css_value *value) +{ + int n = 0; + while (value) + { + n++; + value = value->next; + } + return n; +} + +static void add_property(fz_css_match *match, int name, fz_css_value *value, int spec); + +static void +add_shorthand_trbl(fz_css_match *match, fz_css_value *value, int spec, + int name_t, int name_r, int name_b, int name_l) +{ + int n = count_values(value); + + if (n == 1) + { + add_property(match, name_t, value, spec); + add_property(match, name_r, value, spec); + add_property(match, name_b, value, spec); + add_property(match, name_l, value, spec); + } + + if (n == 2) + { + fz_css_value *a = value; + fz_css_value *b = value->next; + + add_property(match, name_t, a, spec); + add_property(match, name_r, b, spec); + add_property(match, name_b, a, spec); + add_property(match, name_l, b, spec); + } + + if (n == 3) + { + fz_css_value *a = value; + fz_css_value *b = value->next; + fz_css_value *c = value->next->next; + + add_property(match, name_t, a, spec); + add_property(match, name_r, b, spec); + add_property(match, name_b, c, spec); + add_property(match, name_l, b, spec); + } + + if (n == 4) + { + fz_css_value *a = value; + fz_css_value *b = value->next; + fz_css_value *c = value->next->next; + fz_css_value *d = value->next->next->next; + + add_property(match, name_t, a, spec); + add_property(match, name_r, b, spec); + add_property(match, name_b, c, spec); + add_property(match, name_l, d, spec); + } +} + +static void +add_shorthand_margin(fz_css_match *match, fz_css_value *value, int spec) +{ + add_shorthand_trbl(match, value, spec, + PRO_MARGIN_TOP, + PRO_MARGIN_RIGHT, + PRO_MARGIN_BOTTOM, + PRO_MARGIN_LEFT); +} + +static void +add_shorthand_padding(fz_css_match *match, fz_css_value *value, int spec) +{ + add_shorthand_trbl(match, value, spec, + PRO_PADDING_TOP, + PRO_PADDING_RIGHT, + PRO_PADDING_BOTTOM, + PRO_PADDING_LEFT); +} + +static void +add_shorthand_border_width(fz_css_match *match, fz_css_value *value, int spec) +{ + add_shorthand_trbl(match, value, spec, + PRO_BORDER_TOP_WIDTH, + PRO_BORDER_RIGHT_WIDTH, + PRO_BORDER_BOTTOM_WIDTH, + PRO_BORDER_LEFT_WIDTH); +} + +static void +add_shorthand_border_color(fz_css_match *match, fz_css_value *value, int spec) +{ + add_shorthand_trbl(match, value, spec, + PRO_BORDER_TOP_COLOR, + PRO_BORDER_RIGHT_COLOR, + PRO_BORDER_BOTTOM_COLOR, + PRO_BORDER_LEFT_COLOR); +} + +static void +add_shorthand_border_style(fz_css_match *match, fz_css_value *value, int spec) +{ + add_shorthand_trbl(match, value, spec, + PRO_BORDER_TOP_STYLE, + PRO_BORDER_RIGHT_STYLE, + PRO_BORDER_BOTTOM_STYLE, + PRO_BORDER_LEFT_STYLE); +} + +static void +add_shorthand_border(fz_css_match *match, fz_css_value *value, int spec, int T, int R, int B, int L) +{ + while (value) + { + if (value->type == CSS_HASH) + { + if (T) add_property(match, PRO_BORDER_TOP_COLOR, value, spec); + if (R) add_property(match, PRO_BORDER_RIGHT_COLOR, value, spec); + if (B) add_property(match, PRO_BORDER_BOTTOM_COLOR, value, spec); + if (L) add_property(match, PRO_BORDER_LEFT_COLOR, value, spec); + } + else if (value->type == CSS_KEYWORD) + { + if (keyword_in_list(value->data, border_width_kw, nelem(border_width_kw))) + { + if (T) add_property(match, PRO_BORDER_TOP_WIDTH, value, spec); + if (R) add_property(match, PRO_BORDER_RIGHT_WIDTH, value, spec); + if (B) add_property(match, PRO_BORDER_BOTTOM_WIDTH, value, spec); + if (L) add_property(match, PRO_BORDER_LEFT_WIDTH, value, spec); + } + else if (keyword_in_list(value->data, border_style_kw, nelem(border_style_kw))) + { + if (T) add_property(match, PRO_BORDER_TOP_STYLE, value, spec); + if (R) add_property(match, PRO_BORDER_RIGHT_STYLE, value, spec); + if (B) add_property(match, PRO_BORDER_BOTTOM_STYLE, value, spec); + if (L) add_property(match, PRO_BORDER_LEFT_STYLE, value, spec); + } + else if (keyword_in_list(value->data, color_kw, nelem(color_kw))) + { + if (T) add_property(match, PRO_BORDER_TOP_COLOR, value, spec); + if (R) add_property(match, PRO_BORDER_RIGHT_COLOR, value, spec); + if (B) add_property(match, PRO_BORDER_BOTTOM_COLOR, value, spec); + if (L) add_property(match, PRO_BORDER_LEFT_COLOR, value, spec); + } + } + else + { + if (T) add_property(match, PRO_BORDER_TOP_WIDTH, value, spec); + if (R) add_property(match, PRO_BORDER_RIGHT_WIDTH, value, spec); + if (B) add_property(match, PRO_BORDER_BOTTOM_WIDTH, value, spec); + if (L) add_property(match, PRO_BORDER_LEFT_WIDTH, value, spec); + } + value = value->next; + } +} + +static void +add_shorthand_list_style(fz_css_match *match, fz_css_value *value, int spec) +{ + while (value) + { + if (value->type == CSS_KEYWORD) + { + if (keyword_in_list(value->data, list_style_type_kw, nelem(list_style_type_kw))) + { + add_property(match, PRO_LIST_STYLE_TYPE, value, spec); + } + else if (keyword_in_list(value->data, list_style_position_kw, nelem(list_style_position_kw))) + { + add_property(match, PRO_LIST_STYLE_POSITION, value, spec); + } + } + value = value->next; + } +} + +static fz_css_value static_value_normal = { CSS_KEYWORD, "normal", NULL, NULL }; + +static fz_css_value * +add_shorthand_font_size(fz_css_match *match, fz_css_value *value, int spec) +{ + /* font-size */ + add_property(match, PRO_FONT_SIZE, value, spec); + + /* / line-height */ + if (value->next && value->next->next && !strcmp(value->next->data, "/")) + { + value = value->next->next; + add_property(match, PRO_LINE_HEIGHT, value, spec); + } + + return value; +} + +static void +add_shorthand_font(fz_css_match *match, fz_css_value *value, int spec) +{ + fz_css_value *font_style = NULL; + fz_css_value *font_variant = NULL; + fz_css_value *font_weight = NULL; + + /* add the start as font-family for most robust scanning of matching font names */ + add_property(match, PRO_FONT_FAMILY, value, spec); + + /* then look for known style/variant/weight keywords and font-size/line-height */ + for (; value; value = value->next) + { + /* style/variant/weight/size */ + if (value->type == CSS_KEYWORD) + { + if (keyword_in_list(value->data, font_style_kw, nelem(font_style_kw))) + font_style = value; + else if (keyword_in_list(value->data, font_variant_kw, nelem(font_variant_kw))) + font_variant = value; + else if (keyword_in_list(value->data, font_weight_kw, nelem(font_weight_kw))) + font_weight = value; + else if (keyword_in_list(value->data, font_size_kw, nelem(font_size_kw))) + value = add_shorthand_font_size(match, value, spec); + } + else if (value->type == CSS_NUMBER) + font_weight = value; + else if (value->type == CSS_LENGTH || value->type == CSS_PERCENT) + value = add_shorthand_font_size(match, value, spec); + } + + /* set all properties to their initial values if not specified! */ + if (font_style) + add_property(match, PRO_FONT_STYLE, font_style, spec); + else + add_property(match, PRO_FONT_STYLE, &static_value_normal, spec); + + if (font_variant) + add_property(match, PRO_FONT_VARIANT, font_variant, spec); + else + add_property(match, PRO_FONT_VARIANT, &static_value_normal, spec); + + if (font_weight) + add_property(match, PRO_FONT_WEIGHT, font_weight, spec); + else + add_property(match, PRO_FONT_WEIGHT, &static_value_normal, spec); +} + +static void +add_property(fz_css_match *match, int name, fz_css_value *value, int spec) +{ + /* shorthand expansions: */ + switch (name) + { + case PRO_MARGIN: + add_shorthand_margin(match, value, spec); + return; + case PRO_PADDING: + add_shorthand_padding(match, value, spec); + return; + case PRO_BORDER_WIDTH: + add_shorthand_border_width(match, value, spec); + return; + case PRO_BORDER_COLOR: + add_shorthand_border_color(match, value, spec); + return; + case PRO_BORDER_STYLE: + add_shorthand_border_style(match, value, spec); + return; + case PRO_BORDER: + add_shorthand_border(match, value, spec, 1, 1, 1, 1); + return; + case PRO_BORDER_TOP: + add_shorthand_border(match, value, spec, 1, 0, 0, 0); + return; + case PRO_BORDER_RIGHT: + add_shorthand_border(match, value, spec, 0, 1, 0, 0); + return; + case PRO_BORDER_BOTTOM: + add_shorthand_border(match, value, spec, 0, 0, 1, 0); + return; + case PRO_BORDER_LEFT: + add_shorthand_border(match, value, spec, 0, 0, 0, 1); + return; + case PRO_LIST_STYLE: + add_shorthand_list_style(match, value, spec); + return; + case PRO_FONT: + add_shorthand_font(match, value, spec); + return; + /* TODO: background */ + } + + if (name < NUM_PROPERTIES && match->spec[name] <= spec) + { + match->value[name] = value; + match->spec[name] = spec; + } +} + +void +fz_match_css(fz_context *ctx, fz_css_match *match, fz_css_match *up, fz_css *css, fz_xml *node) +{ + fz_css_rule *rule; + fz_css_selector *sel; + fz_css_property *prop; + const char *s; + int i; + + match->up = up; + for (i = 0; i < NUM_PROPERTIES; ++i) + { + match->spec[i] = -1; + match->value[i] = NULL; + } + + for (rule = css->rule; rule; rule = rule->next) + { + sel = rule->selector; + while (sel) + { + if (match_selector(sel, node)) + { + for (prop = rule->declaration; prop; prop = prop->next) + add_property(match, prop->name, prop->value, selector_specificity(sel, prop->important)); + break; + } + sel = sel->next; + } + } + + if (fz_use_document_css(ctx)) + { + s = fz_xml_att(node, "style"); + if (s) + { + fz_try(ctx) + { + prop = fz_parse_css_properties(ctx, css->pool, s); + while (prop) + { + add_property(match, prop->name, prop->value, INLINE_SPECIFICITY); + prop = prop->next; + } + /* We can "leak" the property here, since it is freed along with the pool allocator. */ + } + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + fz_warn(ctx, "ignoring style attribute"); + } + } + } +} + +void +fz_match_css_at_page(fz_context *ctx, fz_css_match *match, fz_css *css) +{ + fz_css_rule *rule; + fz_css_selector *sel; + fz_css_property *prop; + int i; + + match->up = NULL; + for (i = 0; i < NUM_PROPERTIES; ++i) + { + match->spec[i] = -1; + match->value[i] = NULL; + } + + for (rule = css->rule; rule; rule = rule->next) + { + sel = rule->selector; + while (sel) + { + if (sel->name && !strcmp(sel->name, "@page")) + { + for (prop = rule->declaration; prop; prop = prop->next) + add_property(match, prop->name, prop->value, selector_specificity(sel, prop->important)); + break; + } + sel = sel->next; + } + } +} + +void +fz_add_css_font_face(fz_context *ctx, fz_html_font_set *set, fz_archive *zip, const char *base_uri, fz_css_property *declaration) +{ + fz_html_font_face *custom; + fz_css_property *prop; + fz_font *font = NULL; + fz_buffer *buf = NULL; + fz_stream *stm = NULL; + int is_bold, is_italic, is_small_caps; + char path[2048]; + + const char *family = "serif"; + const char *weight = "normal"; + const char *style = "normal"; + const char *variant = "normal"; + const char *src = NULL; + + for (prop = declaration; prop; prop = prop->next) + { + if (prop->name == PRO_FONT_FAMILY) family = prop->value->data; + if (prop->name == PRO_FONT_WEIGHT) weight = prop->value->data; + if (prop->name == PRO_FONT_STYLE) style = prop->value->data; + if (prop->name == PRO_FONT_VARIANT) variant = prop->value->data; + if (prop->name == PRO_SRC) src = prop->value->data; + } + + if (!src) + return; + + is_bold = is_bold_from_font_weight(weight); + is_italic = is_italic_from_font_style(style); + is_small_caps = !strcmp(variant, "small-caps"); + + fz_strlcpy(path, base_uri, sizeof path); + fz_strlcat(path, "/", sizeof path); + fz_strlcat(path, src, sizeof path); + fz_urldecode(path); + + for (custom = set->custom; custom; custom = custom->next) + if (!strcmp(custom->src, path) && !strcmp(custom->family, family) && + custom->is_bold == is_bold && + custom->is_italic == is_italic && + custom->is_small_caps == is_small_caps) + return; /* already loaded */ + + fz_var(buf); + fz_var(font); + fz_var(stm); + + fz_try(ctx) + { + if (fz_has_archive_entry(ctx, zip, path)) + buf = fz_read_archive_entry(ctx, zip, path); + else + { + stm = fz_try_open_file(ctx, src); + if (stm == NULL) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot locate font '%s' specified by css", src); + buf = fz_read_all(ctx, stm, 0); + } + font = fz_new_font_from_buffer(ctx, NULL, buf, 0, 0); + fz_add_html_font_face(ctx, set, family, is_bold, is_italic, is_small_caps, path, font); + } + fz_always(ctx) + { + fz_drop_buffer(ctx, buf); + fz_drop_stream(ctx, stm); + fz_drop_font(ctx, font); + } + fz_catch(ctx) + { + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + fz_warn(ctx, "cannot load font-face: %s", src); + } +} + +void +fz_add_css_font_faces(fz_context *ctx, fz_html_font_set *set, fz_archive *zip, const char *base_uri, fz_css *css) +{ + fz_css_rule *rule; + fz_css_selector *sel; + + for (rule = css->rule; rule; rule = rule->next) + { + if (!rule->loaded) + { + rule->loaded = 1; + sel = rule->selector; + while (sel) + { + if (sel->name && !strcmp(sel->name, "@font-face")) + { + fz_add_css_font_face(ctx, set, zip, base_uri, rule->declaration); + break; + } + sel = sel->next; + } + } + } +} + +static int +is_inheritable_property(int name) +{ + return + name == PRO_COLOR || + name == PRO_DIRECTION || + name == PRO_FONT_FAMILY || + name == PRO_FONT_STYLE || + name == PRO_FONT_VARIANT || + name == PRO_FONT_WEIGHT || + name == PRO_LEADING || + name == PRO_LETTER_SPACING || + name == PRO_LINE_HEIGHT || + name == PRO_LIST_STYLE_IMAGE || + name == PRO_LIST_STYLE_POSITION || + name == PRO_LIST_STYLE_TYPE || + name == PRO_ORPHANS || + name == PRO_OVERFLOW_WRAP || + name == PRO_QUOTES || + name == PRO_TEXT_ALIGN || + name == PRO_TEXT_INDENT || + name == PRO_TEXT_TRANSFORM || + name == PRO_VISIBILITY || + name == PRO_WHITE_SPACE || + name == PRO_WIDOWS || + name == PRO_WORD_SPACING || + // Strictly speaking, text-decoration is not an inherited property, + // but since when drawing an underlined element, all children are also underlined, + // we may as well make it inherited. + name == PRO_TEXT_DECORATION; +} + +static fz_css_value * +value_from_inheritable_property(fz_css_match *match, int name) +{ + fz_css_value *value = match->value[name]; + if (match->up) + { + if (value && !strcmp(value->data, "inherit")) + return value_from_inheritable_property(match->up, name); + if (!value) + return value_from_inheritable_property(match->up, name); + } + return value; +} + +static fz_css_value * +value_from_property(fz_css_match *match, int name) +{ + fz_css_value *value = match->value[name]; + if (match->up) + { + if (value && !strcmp(value->data, "inherit")) + if (name != PRO_FONT_SIZE) /* never inherit 'font-size' textually */ + return value_from_property(match->up, name); + if (!value && is_inheritable_property(name)) + return value_from_inheritable_property(match->up, name); + } + return value; +} + +static const char * +string_from_property(fz_css_match *match, int name, const char *initial) +{ + fz_css_value *value = value_from_property(match, name); + if (!value) + return initial; + return value->data; +} + +static fz_css_number +make_number(float v, int u) +{ + fz_css_number n; + n.value = v; + n.unit = u; + return n; +} + +static fz_css_number +make_undefined_number(void) +{ + fz_css_number n; + n.value = 0; + n.unit = N_UNDEFINED; + return n; +} + +/* Fast but inaccurate strtof. */ +static float +fz_css_strtof(char *s, char **endptr) +{ + float sign = 1; + float v = 0; + float n = 0; + float d = 1; + + if (*s == '-') + { + sign = -1; + ++s; + } + + while (*s >= '0' && *s <= '9') + { + v = v * 10 + (*s - '0'); + ++s; + } + + if (*s == '.') + { + ++s; + while (*s >= '0' && *s <= '9') + { + n = n * 10 + (*s - '0'); + d = d * 10; + ++s; + } + v += n / d; + } + + if (endptr) + *endptr = s; + + return sign * v; +} + +static fz_css_number +number_from_value(fz_css_value *value, float initial, int initial_unit) +{ + char *p; + + if (!value) + return make_number(initial, initial_unit); + + if (value->type == CSS_PERCENT) + return make_number(fz_css_strtof(value->data, NULL), N_PERCENT); + + if (value->type == CSS_NUMBER) + return make_number(fz_css_strtof(value->data, NULL), N_NUMBER); + + if (value->type == CSS_LENGTH) + { + float x = fz_css_strtof(value->data, &p); + + if (p[0] == 'e' && p[1] == 'm' && p[2] == 0) + return make_number(x, N_SCALE); + if (p[0] == 'e' && p[1] == 'x' && p[2] == 0) + return make_number(x / 2, N_SCALE); + + if (p[0] == 'i' && p[1] == 'n' && p[2] == 0) + return make_number(x * 72, N_LENGTH); + if (p[0] == 'c' && p[1] == 'm' && p[2] == 0) + return make_number(x * 7200 / 254, N_LENGTH); + if (p[0] == 'm' && p[1] == 'm' && p[2] == 0) + return make_number(x * 720 / 254, N_LENGTH); + if (p[0] == 'p' && p[1] == 'c' && p[2] == 0) + return make_number(x * 12, N_LENGTH); + + if (p[0] == 'p' && p[1] == 't' && p[2] == 0) + return make_number(x, N_LENGTH); + if (p[0] == 'p' && p[1] == 'x' && p[2] == 0) + return make_number(x, N_LENGTH); + + /* FIXME: 'rem' should be 'em' of root element. This is a bad approximation. */ + if (p[0] == 'r' && p[1] == 'e' && p[2] == 'm' && p[3] == 0) + return make_number(x * 16, N_LENGTH); + + /* FIXME: 'ch' should be width of '0' character. This is an approximation. */ + if (p[0] == 'c' && p[1] == 'h' && p[2] == 0) + return make_number(x / 2, N_LENGTH); + + return make_number(x, N_LENGTH); + } + + if (value->type == CSS_KEYWORD) + { + if (!strcmp(value->data, "auto")) + return make_number(0, N_AUTO); + } + + return make_number(initial, initial_unit); +} + +static fz_css_number +number_from_property(fz_css_match *match, int property, float initial, int initial_unit) +{ + return number_from_value(value_from_property(match, property), initial, initial_unit); +} + +static fz_css_number +border_width_from_property(fz_css_match *match, int property) +{ + fz_css_value *value = value_from_property(match, property); + if (value) + { + if (!strcmp(value->data, "thin")) + return make_number(1, N_LENGTH); + if (!strcmp(value->data, "medium")) + return make_number(2, N_LENGTH); + if (!strcmp(value->data, "thick")) + return make_number(4, N_LENGTH); + return number_from_value(value, 0, N_LENGTH); + } + return make_number(2, N_LENGTH); /* initial: 'medium' */ +} + +static int +border_style_from_property(fz_css_match *match, int property) +{ + fz_css_value *value = value_from_property(match, property); + if (value) + { + if (!strcmp(value->data, "none")) return BS_NONE; + else if (!strcmp(value->data, "hidden")) return BS_NONE; + else if (!strcmp(value->data, "solid")) return BS_SOLID; + } + return BS_NONE; +} + +int fz_css_number_defined(fz_css_number number) +{ + return number.unit != N_UNDEFINED; +} + +float +fz_from_css_number(fz_css_number number, float em, float percent_value, float auto_value) +{ + switch (number.unit) { + default: + case N_NUMBER: return number.value; + case N_LENGTH: return number.value; + case N_SCALE: return number.value * em; + case N_PERCENT: return number.value * 0.01f * percent_value; + case N_AUTO: return auto_value; + } +} + +float +fz_from_css_number_scale(fz_css_number number, float scale) +{ + switch (number.unit) { + default: + case N_NUMBER: return number.value * scale; + case N_LENGTH: return number.value; + case N_SCALE: return number.value * scale; + case N_PERCENT: return number.value * 0.01f * scale; + case N_AUTO: return scale; + } +} + +static fz_css_color +make_color(int r, int g, int b, int a) +{ + fz_css_color c; + c.r = r < 0 ? 0 : r > 255 ? 255 : r; + c.g = g < 0 ? 0 : g > 255 ? 255 : g; + c.b = b < 0 ? 0 : b > 255 ? 255 : b; + c.a = a < 0 ? 0 : a > 255 ? 255 : a; + return c; +} + +static int tohex(int c) +{ + if (c - '0' < 10) + return c - '0'; + return (c | 32) - 'a' + 10; +} + +static fz_css_color +color_from_value(fz_css_value *value, fz_css_color initial) +{ + if (!value) + return initial; + + if (value->type == CSS_HASH) + { + int r, g, b, a; + size_t n; +hex_color: + n = strlen(value->data); + if (n == 3) + { + r = tohex(value->data[0]) * 16 + tohex(value->data[0]); + g = tohex(value->data[1]) * 16 + tohex(value->data[1]); + b = tohex(value->data[2]) * 16 + tohex(value->data[2]); + a = 255; + } + else if (n == 4) + { + r = tohex(value->data[0]) * 16 + tohex(value->data[0]); + g = tohex(value->data[1]) * 16 + tohex(value->data[1]); + b = tohex(value->data[2]) * 16 + tohex(value->data[2]); + a = tohex(value->data[3]) * 16 + tohex(value->data[3]); + } + else if (n == 6) + { + r = tohex(value->data[0]) * 16 + tohex(value->data[1]); + g = tohex(value->data[2]) * 16 + tohex(value->data[3]); + b = tohex(value->data[4]) * 16 + tohex(value->data[5]); + a = 255; + } + else if (n == 8) + { + r = tohex(value->data[0]) * 16 + tohex(value->data[1]); + g = tohex(value->data[2]) * 16 + tohex(value->data[3]); + b = tohex(value->data[4]) * 16 + tohex(value->data[5]); + a = tohex(value->data[6]) * 16 + tohex(value->data[7]); + } + else + { + r = g = b = 0; + a = 255; + } + return make_color(r, g, b, a); + } + + if (value->type == '(' && !strcmp(value->data, "rgb")) + { + fz_css_value *vr, *vg, *vb; + int r, g, b; + vr = value->args; + vg = vr && vr->next ? vr->next->next : NULL; /* skip the ',' nodes */ + vb = vg && vg->next ? vg->next->next : NULL; /* skip the ',' nodes */ + r = fz_from_css_number(number_from_value(vr, 0, N_NUMBER), 255, 255, 0); + g = fz_from_css_number(number_from_value(vg, 0, N_NUMBER), 255, 255, 0); + b = fz_from_css_number(number_from_value(vb, 0, N_NUMBER), 255, 255, 0); + return make_color(r, g, b, 255); + } + + if (value->type == '(' && !strcmp(value->data, "rgba")) + { + fz_css_value *vr, *vg, *vb, *va; + int r, g, b, a; + vr = value->args; + vg = vr && vr->next ? vr->next->next : NULL; /* skip the ',' nodes */ + vb = vg && vg->next ? vg->next->next : NULL; /* skip the ',' nodes */ + va = vb && vb->next ? vb->next->next : NULL; /* skip the ',' nodes */ + r = fz_from_css_number(number_from_value(vr, 0, N_NUMBER), 255, 255, 0); + g = fz_from_css_number(number_from_value(vg, 0, N_NUMBER), 255, 255, 0); + b = fz_from_css_number(number_from_value(vb, 0, N_NUMBER), 255, 255, 0); + a = fz_from_css_number(number_from_value(va, 0, N_NUMBER), 255, 255, 255); + return make_color(r, g, b, a); + } + + if (value->type == CSS_KEYWORD) + { + if (!strcmp(value->data, "transparent")) + return make_color(0, 0, 0, 0); + if (!strcmp(value->data, "maroon")) + return make_color(0x80, 0x00, 0x00, 255); + if (!strcmp(value->data, "red")) + return make_color(0xFF, 0x00, 0x00, 255); + if (!strcmp(value->data, "orange")) + return make_color(0xFF, 0xA5, 0x00, 255); + if (!strcmp(value->data, "yellow")) + return make_color(0xFF, 0xFF, 0x00, 255); + if (!strcmp(value->data, "olive")) + return make_color(0x80, 0x80, 0x00, 255); + if (!strcmp(value->data, "purple")) + return make_color(0x80, 0x00, 0x80, 255); + if (!strcmp(value->data, "fuchsia")) + return make_color(0xFF, 0x00, 0xFF, 255); + if (!strcmp(value->data, "white")) + return make_color(0xFF, 0xFF, 0xFF, 255); + if (!strcmp(value->data, "lime")) + return make_color(0x00, 0xFF, 0x00, 255); + if (!strcmp(value->data, "green")) + return make_color(0x00, 0x80, 0x00, 255); + if (!strcmp(value->data, "navy")) + return make_color(0x00, 0x00, 0x80, 255); + if (!strcmp(value->data, "blue")) + return make_color(0x00, 0x00, 0xFF, 255); + if (!strcmp(value->data, "aqua")) + return make_color(0x00, 0xFF, 0xFF, 255); + if (!strcmp(value->data, "teal")) + return make_color(0x00, 0x80, 0x80, 255); + if (!strcmp(value->data, "black")) + return make_color(0x00, 0x00, 0x00, 255); + if (!strcmp(value->data, "silver")) + return make_color(0xC0, 0xC0, 0xC0, 255); + if (!strcmp(value->data, "gray")) + return make_color(0x80, 0x80, 0x80, 255); + goto hex_color; /* last ditch attempt: maybe it's a #XXXXXX color without the # */ + } + return initial; +} + +static fz_css_color +color_from_property(fz_css_match *match, int property, fz_css_color initial) +{ + return color_from_value(value_from_property(match, property), initial); +} + +static fz_css_color +color_from_properties(fz_css_match *match, int property, int property2, fz_css_color initial) +{ + fz_css_value *value = value_from_property(match, property); + + if (value == NULL) + value = value_from_property(match, property2); + + return color_from_value(value, initial); +} + +int +fz_get_css_match_display(fz_css_match *match) +{ + fz_css_value *value = value_from_property(match, PRO_DISPLAY); + if (value) + { + if (!strcmp(value->data, "none")) + return DIS_NONE; + if (!strcmp(value->data, "inline")) + return DIS_INLINE; + if (!strcmp(value->data, "block")) + return DIS_BLOCK; + if (!strcmp(value->data, "list-item")) + return DIS_LIST_ITEM; + if (!strcmp(value->data, "inline-block")) + return DIS_INLINE_BLOCK; + if (!strcmp(value->data, "table")) + return DIS_TABLE; + if (!strcmp(value->data, "table-row")) + return DIS_TABLE_ROW; + if (!strcmp(value->data, "table-cell")) + return DIS_TABLE_CELL; + if (!strcmp(value->data, "table-row-group")) + return DIS_TABLE_GROUP; + if (!strcmp(value->data, "table-header-group")) + return DIS_TABLE_GROUP; + if (!strcmp(value->data, "table-footer-group")) + return DIS_TABLE_GROUP; + if (!strcmp(value->data, "table-column-group")) + return DIS_NONE; + if (!strcmp(value->data, "table-column")) + return DIS_NONE; + } + return DIS_INLINE; +} + +static int +white_space_from_property(fz_css_match *match) +{ + fz_css_value *value = value_from_property(match, PRO_WHITE_SPACE); + if (value) + { + if (!strcmp(value->data, "normal")) return WS_NORMAL; + else if (!strcmp(value->data, "pre")) return WS_PRE; + else if (!strcmp(value->data, "nowrap")) return WS_NOWRAP; + else if (!strcmp(value->data, "pre-wrap")) return WS_PRE_WRAP; + else if (!strcmp(value->data, "pre-line")) return WS_PRE_LINE; + } + return WS_NORMAL; +} + +static int +text_decoration_from_property(fz_css_match *match) +{ + fz_css_value *value = value_from_property(match, PRO_TEXT_DECORATION); + if (value) + { + if (!strcmp(value->data, "underline")) return TD_UNDERLINE; + if (!strcmp(value->data, "line-through")) return TD_LINE_THROUGH; + } + return TD_NONE; +} + +static int +visibility_from_property(fz_css_match *match) +{ + fz_css_value *value = value_from_property(match, PRO_VISIBILITY); + if (value) + { + if (!strcmp(value->data, "visible")) return V_VISIBLE; + else if (!strcmp(value->data, "hidden")) return V_HIDDEN; + else if (!strcmp(value->data, "collapse")) return V_COLLAPSE; + } + return V_VISIBLE; +} + +static int +page_break_from_property(fz_css_match *match, int prop) +{ + fz_css_value *value = value_from_property(match, prop); + if (value) + { + if (!strcmp(value->data, "auto")) return PB_AUTO; + else if (!strcmp(value->data, "always")) return PB_ALWAYS; + else if (!strcmp(value->data, "avoid")) return PB_AVOID; + else if (!strcmp(value->data, "left")) return PB_LEFT; + else if (!strcmp(value->data, "right")) return PB_RIGHT; + } + return PB_AUTO; +} + +void +fz_default_css_style(fz_context *ctx, fz_css_style *style) +{ + memset(style, 0, sizeof *style); + style->visibility = V_VISIBLE; + style->text_align = TA_LEFT; + style->vertical_align = VA_BASELINE; + style->white_space = WS_NORMAL; + style->list_style_type = LST_DISC; + style->font_size = make_number(1, N_SCALE); + style->width = make_number(0, N_AUTO); + style->height = make_number(0, N_AUTO); + style->leading = make_undefined_number(); +} + +void +fz_apply_css_style(fz_context *ctx, fz_html_font_set *set, fz_css_style *style, fz_css_match *match) +{ + fz_css_value *value; + + fz_css_color black = { 0, 0, 0, 255 }; + fz_css_color transparent = { 0, 0, 0, 0 }; + + fz_default_css_style(ctx, style); + + style->visibility = visibility_from_property(match); + style->white_space = white_space_from_property(match); + style->text_decoration = text_decoration_from_property(match); + style->page_break_before = page_break_from_property(match, PRO_PAGE_BREAK_BEFORE); + style->page_break_after = page_break_from_property(match, PRO_PAGE_BREAK_AFTER); + + value = value_from_property(match, PRO_TEXT_ALIGN); + if (value) + { + if (!strcmp(value->data, "left")) style->text_align = TA_LEFT; + else if (!strcmp(value->data, "right")) style->text_align = TA_RIGHT; + else if (!strcmp(value->data, "center")) style->text_align = TA_CENTER; + else if (!strcmp(value->data, "justify")) style->text_align = TA_JUSTIFY; + } + + value = value_from_property(match, PRO_VERTICAL_ALIGN); + if (value) + { + if (!strcmp(value->data, "baseline")) style->vertical_align = VA_BASELINE; + else if (!strcmp(value->data, "sub")) style->vertical_align = VA_SUB; + else if (!strcmp(value->data, "super")) style->vertical_align = VA_SUPER; + else if (!strcmp(value->data, "top")) style->vertical_align = VA_TOP; + else if (!strcmp(value->data, "bottom")) style->vertical_align = VA_BOTTOM; + else if (!strcmp(value->data, "text-top")) style->vertical_align = VA_TEXT_TOP; + else if (!strcmp(value->data, "text-bottom")) style->vertical_align = VA_TEXT_BOTTOM; + } + + value = value_from_property(match, PRO_FONT_SIZE); + if (value) + { + /* absolute-size */ + if (!strcmp(value->data, "xx-large")) style->font_size = make_number(1.73f, N_SCALE); + else if (!strcmp(value->data, "x-large")) style->font_size = make_number(1.44f, N_SCALE); + else if (!strcmp(value->data, "large")) style->font_size = make_number(1.2f, N_SCALE); + else if (!strcmp(value->data, "medium")) style->font_size = make_number(1.0f, N_SCALE); + else if (!strcmp(value->data, "small")) style->font_size = make_number(0.83f, N_SCALE); + else if (!strcmp(value->data, "x-small")) style->font_size = make_number(0.69f, N_SCALE); + else if (!strcmp(value->data, "xx-small")) style->font_size = make_number(0.69f, N_SCALE); + /* relative-size */ + else if (!strcmp(value->data, "larger")) style->font_size = make_number(1.2f, N_SCALE); + else if (!strcmp(value->data, "smaller")) style->font_size = make_number(1/1.2f, N_SCALE); + /* percentage */ + else if (value->type == CSS_PERCENT) style->font_size = number_from_value(value, 12, N_LENGTH); + /* length */ + else if (value->type == CSS_LENGTH) style->font_size = number_from_value(value, 12, N_LENGTH); + /* default to 1em */ + else style->font_size = make_number(1, N_SCALE); + } + else + { + style->font_size = make_number(1, N_SCALE); + } + + value = value_from_property(match, PRO_LIST_STYLE_TYPE); + if (value) + { + if (!strcmp(value->data, "none")) style->list_style_type = LST_NONE; + else if (!strcmp(value->data, "disc")) style->list_style_type = LST_DISC; + else if (!strcmp(value->data, "circle")) style->list_style_type = LST_CIRCLE; + else if (!strcmp(value->data, "square")) style->list_style_type = LST_SQUARE; + else if (!strcmp(value->data, "decimal")) style->list_style_type = LST_DECIMAL; + else if (!strcmp(value->data, "decimal-leading-zero")) style->list_style_type = LST_DECIMAL_ZERO; + else if (!strcmp(value->data, "lower-roman")) style->list_style_type = LST_LC_ROMAN; + else if (!strcmp(value->data, "upper-roman")) style->list_style_type = LST_UC_ROMAN; + else if (!strcmp(value->data, "lower-greek")) style->list_style_type = LST_LC_GREEK; + else if (!strcmp(value->data, "upper-greek")) style->list_style_type = LST_UC_GREEK; + else if (!strcmp(value->data, "lower-latin")) style->list_style_type = LST_LC_LATIN; + else if (!strcmp(value->data, "upper-latin")) style->list_style_type = LST_UC_LATIN; + else if (!strcmp(value->data, "lower-alpha")) style->list_style_type = LST_LC_ALPHA; + else if (!strcmp(value->data, "upper-alpha")) style->list_style_type = LST_UC_ALPHA; + else if (!strcmp(value->data, "armenian")) style->list_style_type = LST_ARMENIAN; + else if (!strcmp(value->data, "georgian")) style->list_style_type = LST_GEORGIAN; + } + + value = value_from_property(match, PRO_OVERFLOW_WRAP); + if (value) + { + if (!strcmp(value->data, "break-word")) style->overflow_wrap = OVERFLOW_WRAP_BREAK_WORD; + else style->overflow_wrap = OVERFLOW_WRAP_NORMAL; + } + + style->line_height = number_from_property(match, PRO_LINE_HEIGHT, 1.2f, N_SCALE); + style->leading = number_from_property(match, PRO_LEADING, 0, N_UNDEFINED); + + style->text_indent = number_from_property(match, PRO_TEXT_INDENT, 0, N_LENGTH); + style->text_stroke_width = number_from_property(match, PRO_TEXT_STROKE_WIDTH, 0, N_LENGTH); + + style->width = number_from_property(match, PRO_WIDTH, 0, N_AUTO); + style->height = number_from_property(match, PRO_HEIGHT, 0, N_AUTO); + + style->margin[0] = number_from_property(match, PRO_MARGIN_TOP, 0, N_LENGTH); + style->margin[1] = number_from_property(match, PRO_MARGIN_RIGHT, 0, N_LENGTH); + style->margin[2] = number_from_property(match, PRO_MARGIN_BOTTOM, 0, N_LENGTH); + style->margin[3] = number_from_property(match, PRO_MARGIN_LEFT, 0, N_LENGTH); + + style->padding[0] = number_from_property(match, PRO_PADDING_TOP, 0, N_LENGTH); + style->padding[1] = number_from_property(match, PRO_PADDING_RIGHT, 0, N_LENGTH); + style->padding[2] = number_from_property(match, PRO_PADDING_BOTTOM, 0, N_LENGTH); + style->padding[3] = number_from_property(match, PRO_PADDING_LEFT, 0, N_LENGTH); + + style->color = color_from_property(match, PRO_COLOR, black); + style->text_fill_color = color_from_properties(match, PRO_TEXT_FILL_COLOR, PRO_COLOR, black); + style->text_stroke_color = color_from_property(match, PRO_TEXT_STROKE_COLOR, transparent); + style->background_color = color_from_property(match, PRO_BACKGROUND_COLOR, transparent); + + style->border_spacing = number_from_property(match, PRO_BORDER_SPACING, 0, N_LENGTH); + + style->border_style_0 = border_style_from_property(match, PRO_BORDER_TOP_STYLE); + style->border_style_1 = border_style_from_property(match, PRO_BORDER_RIGHT_STYLE); + style->border_style_2 = border_style_from_property(match, PRO_BORDER_BOTTOM_STYLE); + style->border_style_3 = border_style_from_property(match, PRO_BORDER_LEFT_STYLE); + + style->border_color[0] = color_from_property(match, PRO_BORDER_TOP_COLOR, style->color); + style->border_color[1] = color_from_property(match, PRO_BORDER_RIGHT_COLOR, style->color); + style->border_color[2] = color_from_property(match, PRO_BORDER_BOTTOM_COLOR, style->color); + style->border_color[3] = color_from_property(match, PRO_BORDER_LEFT_COLOR, style->color); + + style->border_width[0] = border_width_from_property(match, PRO_BORDER_TOP_WIDTH); + style->border_width[1] = border_width_from_property(match, PRO_BORDER_RIGHT_WIDTH); + style->border_width[2] = border_width_from_property(match, PRO_BORDER_BOTTOM_WIDTH); + style->border_width[3] = border_width_from_property(match, PRO_BORDER_LEFT_WIDTH); + + { + const char *font_weight = string_from_property(match, PRO_FONT_WEIGHT, "normal"); + const char *font_style = string_from_property(match, PRO_FONT_STYLE, "normal"); + const char *font_variant = string_from_property(match, PRO_FONT_VARIANT, "normal"); + int is_bold = is_bold_from_font_weight(font_weight); + int is_italic = is_italic_from_font_style(font_style); + style->small_caps = !strcmp(font_variant, "small-caps"); + value = value_from_property(match, PRO_FONT_FAMILY); + for (; value; value = value->next) + { + /* ignore numbers and keywords used in font short-hand syntax */ + if (value->type == CSS_STRING || (value->type == CSS_KEYWORD && !keyword_in_list(value->data, font_family_ignore, nelem(font_family_ignore)))) + { + style->font = fz_load_html_font(ctx, set, value->data, is_bold, is_italic, style->small_caps); + if (style->font) + break; + } + } + if (!style->font) + style->font = fz_load_html_font(ctx, set, "serif", is_bold, is_italic, style->small_caps); + } +} + +#ifdef DEBUG_CSS_SPLAY +static void +do_verify_splay(const fz_css_style_splay *x) +{ + printf("%x<", x); + if (x->lt) + { + assert(memcmp(&x->lt->style, &x->style, sizeof(x->style)) < 0); + assert(x->lt->up == x); + do_verify_splay(x->lt); + } + printf(","); + if (x->gt) + { + assert(memcmp(&x->gt->style, &x->style, sizeof(x->style)) > 0); + assert(x->gt->up == x); + do_verify_splay(x->gt); + } + printf(">\n"); +} + +static void +verify_splay(const fz_css_style_splay *x) +{ + if (x == NULL) + return; + assert(x->up == NULL); + do_verify_splay(x); + printf("-----\n"); +} +#endif + +const fz_css_style * +fz_css_enlist(fz_context *ctx, const fz_css_style *style, fz_css_style_splay **tree, fz_pool *pool) +{ + fz_css_style_splay **current = tree; + fz_css_style_splay *x; + fz_css_style_splay *y = NULL; + + /* Search for a match in the tree, if there is one, or for + * the insertion point, if there is not. */ + while (*current != NULL) + { + int cmp = memcmp(style, &(*current)->style, sizeof(*style)); + if (cmp == 0) + { + /* We have a match - break out and do move to root. */ + break; + } + y = (*current); + if (cmp < 0) + current = &y->lt; + else + current = &y->gt; + } + /* Create one if needed */ + if (*current == NULL) + { + x = *current = fz_pool_alloc(ctx, pool, sizeof(*y)); + x->style = *style; + x->up = y; + x->lt = NULL; + x->gt = NULL; + } + else + x = *current; + /* Now move to root */ + /* + The splaying steps used: + + Case 1: |a) z x b) z x + | y D => A y A y y D + | x C B z B x => z C + | A B C D C D A B + + Case 2: |a) z x b) z x + | y D => y z A y => z y + | A x A B C D x D A B C D + | B C B C + + Case 3: |a) y x b) y x + | x C => A y A x => y C + | A B B C B C A B + */ +#ifdef DEBUG_CSS_SPLAY + printf("BEFORE\n"); + verify_splay(*tree); +#endif + while ((y = x->up) != NULL ) /* While we're not at the root */ + { + fz_css_style_splay *z = y->up; + y->up = x; + if (z == NULL) + { + if (y->lt == x) /* Case 3a */ + { + y->lt = x->gt; + if (y->lt) + y->lt->up = y; + x->gt = y; + } + else /* Case 3b */ + { + y->gt = x->lt; + if (y->gt) + y->gt->up = y; + x->lt = y; + } + x->up = NULL; + break; + } + x->up = z->up; + if (z->up) + { + if (z->up->lt == z) + z->up->lt = x; + else + z->up->gt = x; + } + if (z->lt == y) + { + if (y->lt == x) /* Case 1a */ + { + z->lt = y->gt; + if (z->lt) + z->lt->up = z; + y->lt = x->gt; + if (y->lt) + y->lt->up = y; + y->gt = z; + z->up = y; + x->gt = y; + } + else /* Case 2a */ + { + y->gt = x->lt; + if (y->gt) + y->gt->up = y; + z->lt = x->gt; + if (z->lt) + z->lt->up = z; + x->lt = y; + x->gt = z; + z->up = x; + } + } + else + { + if (y->gt == x) /* Case 1b */ + { + z->gt = y->lt; + if (z->gt) + z->gt->up = z; + y->gt = x->lt; + if (y->gt) + y->gt->up = y; + y->lt = z; + z->up = y; + x->lt = y; + } + else /* Case 2b */ + { + z->gt = x->lt; + if (z->gt) + z->gt->up = z; + y->lt = x->gt; + if (y->lt) + y->lt->up = y; + x->gt = y; + x->lt = z; + z->up = x; + } + } + } + + *tree = x; +#ifdef DEBUG_CSS_SPLAY + printf("AFTER\n"); + verify_splay(x); +#endif + + return &x->style; +} + +/* + * Pretty printing + */ + +static void print_value(fz_css_value *val) +{ + printf("%s", val->data); + if (val->args) + { + printf("("); + print_value(val->args); + printf(")"); + } + if (val->next) + { + printf(" "); + print_value(val->next); + } +} + +static void print_property(fz_css_property *prop) +{ + printf("\t%s: ", fz_css_property_name(prop->name)); + print_value(prop->value); + if (prop->important) + printf(" !important"); + printf(";\n"); +} + +static void print_condition(fz_css_condition *cond) +{ + if (cond->type == '=') + printf("[%s=%s]", cond->key, cond->val); + else if (cond->type == '[') + printf("[%s]", cond->key); + else + printf("%c%s", cond->type, cond->val); + if (cond->next) + print_condition(cond->next); +} + +static void print_selector(fz_css_selector *sel) +{ + if (sel->combine) + { + print_selector(sel->left); + if (sel->combine == ' ') + printf(" "); + else + printf(" %c ", sel->combine); + print_selector(sel->right); + } + else if (sel->name) + printf("%s", sel->name); + else + printf("*"); + if (sel->cond) + { + print_condition(sel->cond); + } +} + +static void print_rule(fz_css_rule *rule) +{ + fz_css_selector *sel; + fz_css_property *prop; + + for (sel = rule->selector; sel; sel = sel->next) + { + print_selector(sel); + printf(" /* %d */", selector_specificity(sel, 0)); + if (sel->next) + printf(", "); + } + + printf("\n{\n"); + for (prop = rule->declaration; prop; prop = prop->next) + { + print_property(prop); + } + printf("}\n"); +} + +void +fz_debug_css(fz_context *ctx, fz_css *css) +{ + fz_css_rule *rule = css->rule; + while (rule) + { + print_rule(rule); + rule = rule->next; + } +}
