#include "mupdf/html.h"
static void add_property(struct style *style, const char *name, struct value *value, int spec);
struct rule *
new_rule(struct selector *selector, struct property *declaration)
{
struct rule *rule;
rule = malloc(sizeof(struct rule));
rule->selector = selector;
rule->declaration = declaration;
rule->next = NULL;
return rule;
}
struct selector *
new_selector(const char *name)
{
struct selector *sel;
sel = malloc(sizeof(struct selector));
sel->name = name ? strdup(name) : NULL;
sel->combine = 0;
sel->cond = NULL;
sel->left = NULL;
sel->right = NULL;
sel->next = NULL;
return sel;
}
struct condition *
new_condition(int type, const char *key, const char *val)
{
struct condition *cond;
cond = malloc(sizeof(struct condition));
cond->type = type;
cond->key = key ? strdup(key) : NULL;
cond->val = val ? strdup(val) : NULL;
cond->next = NULL;
return cond;
}
struct property *
new_property(const char *name, struct value *value, int spec)
{
struct property *prop;
prop = malloc(sizeof(struct property));
prop->name = strdup(name);
prop->value = value;
prop->spec = spec;
prop->next = NULL;
return prop;
}
struct value *
new_value(int type, const char *data)
{
struct value *val;
val = malloc(sizeof(struct value));
val->type = type;
val->data = strdup(data);
val->args = NULL;
val->next = NULL;
return val;
}
/*
* Compute specificity
*/
static int
count_condition_ids(struct condition *cond)
{
int n = 0;
while (cond)
{
if (cond->type == '#')
n ++;
cond = cond->next;
}
return n;
}
static int
count_selector_ids(struct 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(struct condition *cond)
{
int n = 0;
while (cond)
{
if (cond->type != '#' && cond->type != ':')
n ++;
cond = cond->next;
}
return n;
}
static int
count_selector_atts(struct 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(struct condition *cond)
{
int n = 0;
while (cond)
{
if (cond->type == ':')
n ++;
cond = cond->next;
}
return n;
}
static int
count_selector_names(struct 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 1000
int
selector_specificity(struct selector *sel)
{
int b = count_selector_ids(sel);
int c = count_selector_atts(sel);
int d = count_selector_names(sel);
return b * 100 + c * 10 + d;
}
/*
* Pretty printing
*/
void
print_value(struct value *val)
{
printf("%s", val->data);
if (val->args)
{
printf("(");
print_value(val->args);
printf(")");
}
if (val->next)
{
printf(" ");
print_value(val->next);
}
}
void
print_property(struct property *prop)
{
printf("\t%s: ", prop->name);
print_value(prop->value);
printf(" !%d;\n", prop->spec);
}
void
print_condition(struct 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);
}
void
print_selector(struct selector *sel)
{
if (sel->combine)
{
putchar('(');
print_selector(sel->left);
if (sel->combine == ' ')
printf(" ");
else
printf(" %c ", sel->combine);
print_selector(sel->right);
putchar(')');
}
else if (sel->name)
printf("%s", sel->name);
else
printf("*");
if (sel->cond)
{
print_condition(sel->cond);
}
}
void
print_rule(struct rule *rule)
{
struct selector *sel;
struct property *prop;
for (sel = rule->selector; sel; sel = sel->next)
{
print_selector(sel);
printf(" !%d", selector_specificity(sel));
if (sel->next)
printf(", ");
}
printf("\n{\n");
for (prop = rule->declaration; prop; prop = prop->next)
{
print_property(prop);
}
printf("}\n");
}
void
print_rules(struct rule *rule)
{
while (rule)
{
print_rule(rule);
rule = rule->next;
}
}
/*
* Selector matching
*/
int
match_id_condition(fz_xml *node, const char *p)
{
const char *s = fz_xml_att(node, "id");
if (s && !strcmp(s, p))
return 1;
return 0;
}
int
match_class_condition(fz_xml *node, const char *p)
{
const char *s = fz_xml_att(node, "class");
char buf[1024];
if (s) {
strcpy(buf, s);
s = strtok(buf, " ");
while (s) {
if (!strcmp(s, p))
return 1;
s = strtok(NULL, " ");
}
}
return 0;
}
int
match_condition(struct 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_id_condition(node, cond->val)) return 0; break;
case '.': if (!match_class_condition(node, cond->val)) return 0; break;
}
return match_condition(cond->next, node);
}
int
match_selector(struct selector *sel, fz_xml *node)
{
if (!node)
return 0;
if (sel->combine)
{
/* descendant */
if (sel->combine == ' ')
{
fz_xml *parent = fz_xml_up(node);
while (parent)
{
if (match_selector(sel->left, parent))
if (match_selector(sel->right, node))
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 (strcmp(sel->name, fz_xml_tag(node)))
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(struct value *value)
{
int n = 0;
while (value)
{
n++;
value = value->next;
}
return n;
}
static void
add_shorthand_margin(struct style *style, struct value *value, int spec)
{
int n = count_values(value);
if (n == 1)
{
add_property(style, "margin-top", value, spec);
add_property(style, "margin-right", value, spec);
add_property(style, "margin-bottom", value, spec);
add_property(style, "margin-left", value, spec);
}
if (n == 2)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
add_property(style, "margin-top", a, spec);
add_property(style, "margin-right", b, spec);
add_property(style, "margin-bottom", a, spec);
add_property(style, "margin-left", b, spec);
}
if (n == 3)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
struct value *c = new_value(value->next->next->type, value->next->next->data);
add_property(style, "margin-top", a, spec);
add_property(style, "margin-right", b, spec);
add_property(style, "margin-bottom", c, spec);
add_property(style, "margin-left", b, spec);
}
if (n == 4)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
struct value *c = new_value(value->next->next->type, value->next->next->data);
struct value *d = new_value(value->next->next->next->type, value->next->next->next->data);
add_property(style, "margin-top", a, spec);
add_property(style, "margin-right", b, spec);
add_property(style, "margin-bottom", c, spec);
add_property(style, "margin-left", d, spec);
}
}
static void
add_shorthand_padding(struct style *style, struct value *value, int spec)
{
int n = count_values(value);
if (n == 1)
{
add_property(style, "padding-top", value, spec);
add_property(style, "padding-right", value, spec);
add_property(style, "padding-bottom", value, spec);
add_property(style, "padding-left", value, spec);
}
if (n == 2)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
add_property(style, "padding-top", a, spec);
add_property(style, "padding-right", b, spec);
add_property(style, "padding-bottom", a, spec);
add_property(style, "padding-left", b, spec);
}
if (n == 3)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
struct value *c = new_value(value->next->next->type, value->next->next->data);
add_property(style, "padding-top", a, spec);
add_property(style, "padding-right", b, spec);
add_property(style, "padding-bottom", c, spec);
add_property(style, "padding-left", b, spec);
}
if (n == 4)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
struct value *c = new_value(value->next->next->type, value->next->next->data);
struct value *d = new_value(value->next->next->next->type, value->next->next->next->data);
add_property(style, "padding-top", a, spec);
add_property(style, "padding-right", b, spec);
add_property(style, "padding-bottom", c, spec);
add_property(style, "padding-left", d, spec);
}
}
static void
add_shorthand_border_width(struct style *style, struct value *value, int spec)
{
int n = count_values(value);
if (n == 1)
{
add_property(style, "border-top-width", value, spec);
add_property(style, "border-right-width", value, spec);
add_property(style, "border-bottom-width", value, spec);
add_property(style, "border-left-width", value, spec);
}
if (n == 2)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
add_property(style, "border-top-width", a, spec);
add_property(style, "border-right-width", b, spec);
add_property(style, "border-bottom-width", a, spec);
add_property(style, "border-left-width", b, spec);
}
if (n == 3)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
struct value *c = new_value(value->next->next->type, value->next->next->data);
add_property(style, "border-top-width", a, spec);
add_property(style, "border-right-width", b, spec);
add_property(style, "border-bottom-width", c, spec);
add_property(style, "border-left-width", b, spec);
}
if (n == 4)
{
struct value *a = new_value(value->type, value->data);
struct value *b = new_value(value->next->type, value->next->data);
struct value *c = new_value(value->next->next->type, value->next->next->data);
struct value *d = new_value(value->next->next->next->type, value->next->next->next->data);
add_property(style, "border-top-width", a, spec);
add_property(style, "border-right-width", b, spec);
add_property(style, "border-bottom-width", c, spec);
add_property(style, "border-left-width", d, spec);
}
}
static void
add_property(struct style *style, const char *name, struct value *value, int spec)
{
int i;
if (!strcmp(name, "margin"))
{
add_shorthand_margin(style, value, spec);
return;
}
if (!strcmp(name, "padding"))
{
add_shorthand_padding(style, value, spec);
return;
}
if (!strcmp(name, "border-width"))
{
add_shorthand_border_width(style, value, spec);
return;
}
/* TODO: border-color */
/* TODO: border-style */
/* TODO: border */
/* TODO: font */
/* TODO: list-style */
/* TODO: background */
for (i = 0; i < style->count; ++i)
{
if (!strcmp(style->prop[i].name, name))
{
if (style->prop[i].spec <= spec)
{
style->prop[i].value = value;
style->prop[i].spec = spec;
}
return;
}
}
if (style->count + 1 >= nelem(style->prop))
{
// fz_warn(ctx, "too many css properties");
return;
}
style->prop[style->count].name = name;
style->prop[style->count].value = value;
style->prop[style->count].spec = spec;
++style->count;
}
void
apply_styles(fz_context *ctx, struct style *style, struct rule *rule, fz_xml *node)
{
struct selector *sel;
struct property *prop;
const char *s;
while (rule)
{
sel = rule->selector;
while (sel)
{
if (match_selector(sel, node))
{
for (prop = rule->declaration; prop; prop = prop->next)
add_property(style, prop->name, prop->value, selector_specificity(sel));
break;
}
sel = sel->next;
}
rule = rule->next;
}
s = fz_xml_att(node, "style");
if (s)
{
prop = fz_parse_css_properties(ctx, s);
while (prop)
{
add_property(style, prop->name, prop->value, INLINE_SPECIFICITY);
prop = prop->next;
}
// TODO: free props
}
}
static const char *inherit_list[] = {
"color", "direction",
"font-family", "font-size", "font-style", "font-variant", "font-weight",
"letter-spacing", "line-height",
"list-style-image", "list-style-position", "list-style-type",
"orphans", "quotes", "text-align", "text-indent", "text-transform",
"visibility", "white-space", "widows", "word-spacing",
/* this is not supposed to be inherited: */
"vertical-align",
};
static struct value *
get_raw_property(struct style *node, const char *name)
{
int i;
for (i = 0; i < node->count; ++i)
if (!strcmp(node->prop[i].name, name))
return node->prop[i].value;
return NULL;
}
static int
should_inherit_property(const char *name)
{
int l = 0;
int r = nelem(inherit_list) - 1;
while (l <= r)
{
int m = (l + r) >> 1;
int c = strcmp(name, inherit_list[m]);
if (c < 0)
r = m - 1;
else if (c > 0)
l = m + 1;
else
return 1;
}
return 0;
}
static struct value *
get_style_property(struct style *node, const char *name)
{
struct value *value;
value = get_raw_property(node, name);
if (node->up)
{
if (value && !strcmp(value->data, "inherit"))
return get_style_property(node->up, name);
if (!value && should_inherit_property(name))
return get_style_property(node->up, name);
}
return value;
}
static const char *
get_style_property_string(struct style *node, const char *name, const char *initial)
{
struct value *value;
value = get_style_property(node, name);
if (!value)
return initial;
return value->data;
}
static struct number
make_number(float v, int u)
{
struct number n;
n.value = v;
n.unit = u;
return n;
}
static struct number
number_from_value(struct 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(strtof(value->data, NULL), N_PERCENT);
if (value->type == CSS_NUMBER)
return make_number(strtof(value->data, NULL), N_NUMBER);
if (value->type == CSS_LENGTH)
{
float x = strtof(value->data, &p);
if (p[0] == 'e' && p[1] == 'm')
return make_number(x, N_SCALE);
if (p[0] == 'e' && p[1] == 'x')
return make_number(x / 2, N_SCALE);
if (p[0] == 'i' && p[1] == 'n')
return make_number(x * 72, N_NUMBER);
if (p[0] == 'c' && p[1] == 'm')
return make_number(x * 7200 / 254, N_NUMBER);
if (p[0] == 'm' && p[1] == 'm')
return make_number(x * 720 / 254, N_NUMBER);
if (p[0] == 'p' && p[1] == 'c')
return make_number(x * 12, N_NUMBER);
if (p[0] == 'p' && p[1] == 't')
return make_number(x, N_NUMBER);
if (p[0] == 'p' && p[1] == 'x')
return make_number(x, N_NUMBER);
return make_number(x, N_NUMBER);
}
return make_number(initial, initial_unit);
}
static struct number
number_from_property(struct style *node, const char *property, float initial, int initial_unit)
{
return number_from_value(get_style_property(node, property), initial, initial_unit);
}
float
from_number(struct number number, float em, float width)
{
switch (number.unit) {
default:
case N_NUMBER: return number.value;
case N_SCALE: return number.value * em;
case N_PERCENT: return number.value * 0.01 * width;
}
}
float
from_number_scale(struct number number, float scale, float em, float width)
{
switch (number.unit) {
default:
case N_NUMBER: return number.value * scale;
case N_SCALE: return number.value * em;
case N_PERCENT: return number.value * 0.01 * width;
}
}
static struct color
make_color(int r, int g, int b, int a)
{
struct color c;
c.r = r;
c.g = g;
c.b = b;
c.a = a;
return c;
}
static int tohex(int c)
{
if (c - '0' < 10)
return c - '0';
return (c | 32) - 'a' + 10;
}
static struct color
color_from_value(struct value *value)
{
if (!value)
return make_color(0, 0, 0, 0);
if (value->type == CSS_COLOR)
{
int r = tohex(value->data[0]) * 16 + tohex(value->data[1]);
int g = tohex(value->data[2]) * 16 + tohex(value->data[3]);
int b = tohex(value->data[4]) * 16 + tohex(value->data[5]);
return make_color(r, g, b, 255);
}
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);
return make_color(0, 0, 0, 255);
}
return make_color(0, 0, 0, 0);
}
static struct color
color_from_property(struct style *node, const char *property)
{
return color_from_value(get_style_property(node, property));
}
int
get_style_property_display(struct style *node)
{
struct value *value = get_style_property(node, "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;
}
return DIS_INLINE;
}
int
get_style_property_white_space(struct style *node)
{
struct value *value = get_style_property(node, "white-space");
if (value)
{
if (!strcmp(value->data, "normal")) return WS_NORMAL;
if (!strcmp(value->data, "pre")) return WS_PRE;
if (!strcmp(value->data, "nowrap")) return WS_NOWRAP;
if (!strcmp(value->data, "pre-wrap")) return WS_PRE_WRAP;
if (!strcmp(value->data, "pre-line")) return WS_PRE_LINE;
}
return WS_NORMAL;
}
void
default_computed_style(struct computed_style *style)
{
memset(style, 0, sizeof *style);
style->text_align = TA_LEFT;
style->vertical_align = 0;
style->white_space = WS_NORMAL;
style->font_size = make_number(1, N_SCALE);
style->background_color = make_color(0, 0, 0, 0);
style->color = make_color(0, 0, 0, 255);
}
void
compute_style(html_document *doc, struct computed_style *style, struct style *node)
{
struct value *value;
default_computed_style(style);
style->white_space = get_style_property_white_space(node);
value = get_style_property(node, "text-align");
if (value)
{
if (!strcmp(value->data, "left"))
style->text_align = TA_LEFT;
if (!strcmp(value->data, "right"))
style->text_align = TA_RIGHT;
if (!strcmp(value->data, "center"))
style->text_align = TA_CENTER;
if (!strcmp(value->data, "justify"))
style->text_align = TA_JUSTIFY;
}
value = get_style_property(node, "vertical-align");
if (value)
{
if (!strcmp(value->data, "super"))
style->vertical_align = 1;
if (!strcmp(value->data, "sub"))
style->vertical_align = -1;
}
value = get_style_property(node, "font-size");
if (value)
{
if (!strcmp(value->data, "xx-large")) style->font_size = make_number(20, N_NUMBER);
else if (!strcmp(value->data, "x-large")) style->font_size = make_number(16, N_NUMBER);
else if (!strcmp(value->data, "large")) style->font_size = make_number(14, N_NUMBER);
else if (!strcmp(value->data, "medium")) style->font_size = make_number(12, N_NUMBER);
else if (!strcmp(value->data, "small")) style->font_size = make_number(10, N_NUMBER);
else if (!strcmp(value->data, "x-small")) style->font_size = make_number(8, N_NUMBER);
else if (!strcmp(value->data, "xx-small")) style->font_size = make_number(6, N_NUMBER);
else if (!strcmp(value->data, "larger")) style->font_size = make_number(1.25f, N_SCALE);
else if (!strcmp(value->data, "smaller")) style->font_size = make_number(0.8f, N_SCALE);
else style->font_size = number_from_value(value, 12, N_NUMBER);
}
else
{
style->font_size = make_number(1, N_SCALE);
}
style->line_height = number_from_property(node, "line-height", 1.2, N_SCALE);
style->text_indent = number_from_property(node, "text-indent", 0, N_NUMBER);
style->margin[0] = number_from_property(node, "margin-top", 0, N_NUMBER);
style->margin[1] = number_from_property(node, "margin-right", 0, N_NUMBER);
style->margin[2] = number_from_property(node, "margin-bottom", 0, N_NUMBER);
style->margin[3] = number_from_property(node, "margin-left", 0, N_NUMBER);
style->padding[0] = number_from_property(node, "padding-top", 0, N_NUMBER);
style->padding[1] = number_from_property(node, "padding-right", 0, N_NUMBER);
style->padding[2] = number_from_property(node, "padding-bottom", 0, N_NUMBER);
style->padding[3] = number_from_property(node, "padding-left", 0, N_NUMBER);
style->color = color_from_property(node, "color");
style->background_color = color_from_property(node, "background-color");
{
const char *font_family = get_style_property_string(node, "font-family", "serif");
const char *font_variant = get_style_property_string(node, "font-variant", "normal");
const char *font_style = get_style_property_string(node, "font-style", "normal");
const char *font_weight = get_style_property_string(node, "font-weight", "normal");
style->font = html_load_font(doc, font_family, font_variant, font_style, font_weight);
}
}
void
print_style(struct computed_style *style)
{
printf("style {\n");
printf("\tfont-size = %g%c;\n", style->font_size.value, style->font_size.unit);
printf("\tfont = %s;\n", style->font->name);
printf("\tline-height = %g%c;\n", style->line_height.value, style->line_height.unit);
printf("\ttext-indent = %g%c;\n", style->text_indent.value, style->text_indent.unit);
printf("\ttext-align = %d;\n", style->text_align);
printf("\tvertical-align = %d;\n", style->vertical_align);
printf("\tmargin = %g%c %g%c %g%c %g%c;\n",
style->margin[0].value, style->margin[0].unit,
style->margin[1].value, style->margin[1].unit,
style->margin[2].value, style->margin[2].unit,
style->margin[3].value, style->margin[3].unit);
printf("\tpadding = %g%c %g%c %g%c %g%c;\n",
style->padding[0].value, style->padding[0].unit,
style->padding[1].value, style->padding[1].unit,
style->padding[2].value, style->padding[2].unit,
style->padding[3].value, style->padding[3].unit);
printf("}\n");
}