view mupdf-source/source/html/css-apply.c @ 46:7ee69f120f19 default tip

>>>>> tag v1.26.5+1 for changeset b74429b0f5c4
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 11 Oct 2025 17:17:30 +0200
parents b50eed0cc0ef
children
line wrap: on
line source

// 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;
	}
}