/* * CSS core for qemacs. * Copyright (c) 2000, 2001, 2002 Fabrice Bellard. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "qe.h" #include "qfribidi.h" #include "css.h" //#define DEBUG #ifdef DEBUG #define dprintf(fmt, args...) printf(fmt, ##args) #else #define dprintf(fmt, args...) #define NDEBUG #endif #define ddprintf(fmt, args...) #include static void css_counter_str(char *text, int text_size, int index, int list_style_type, int adjust); #define CSSDEF(str, ident, inherited, type) \ { str, (int)&((CSSState *)0)->ident, CSS_STORAGE_INT, \ type | (inherited ? CSS_TYPE_INHERITED : 0) }, #define CSSDEF1(str, ident, inherited, type, storage) \ { str, (int)&((CSSState *)0)->ident, storage, \ type | (inherited ? CSS_TYPE_INHERITED : 0) }, #define CSSUNDEF(str) const CSSPropertyDef css_properties[NB_PROPERTIES] = { CSSDEF("display\0inline,block,table,table-row,table-row-group,table-header-group,table-footer-group,table-column,table-column-group,table-cell,table-caption,list-item,marker,inline-block,inline-table,none", display, 0, CSS_TYPE_ENUM) CSSDEF("color", color, 1, CSS_TYPE_COLOR) CSSDEF("background-color", bgcolor, 0, CSS_TYPE_COLOR) CSSDEF("white-space\0normal,pre,nowrap,prewrap", white_space, 1, CSS_TYPE_ENUM) CSSDEF("direction\0ltr,rtl", direction, 1, CSS_TYPE_ENUM) CSSDEF("float\0none,left,right", block_float, 0, CSS_TYPE_ENUM) CSSDEF("font-family", font_family, 1, CSS_TYPE_FONT_FAMILY) CSSDEF("font-style\0normal,italic,oblique", font_style, 1, CSS_TYPE_ENUM) CSSDEF("font-weight\0normal,bold,bolder,lighter", font_weight, 1, CSS_TYPE_ENUM) CSSDEF("font-size\0xx-small,x-small,small,medium,large,x-large,xx-large,smaller,larger", font_size, 1, CSS_TYPE_LENGTH | CSS_TYPE_ENUM) CSSDEF("text-decoration\0none,underline,line-through", text_decoration, 1, CSS_TYPE_ENUM) CSSDEF("text-align\0left,right,center,justify", text_align, 1, CSS_TYPE_ENUM) CSSDEF("width", width, 0, CSS_TYPE_LENGTH | CSS_TYPE_AUTO) CSSDEF("height", height, 0, CSS_TYPE_LENGTH | CSS_TYPE_AUTO) CSSDEF("unicode-bidi\0normal,embed,bidi-override", unicode_bidi, 0, CSS_TYPE_ENUM) CSSDEF("border-width", border, 0, CSS_TYPE_LENGTH | CSS_TYPE_FOUR) CSSDEF("border-left-width", border.x1, 0, CSS_TYPE_LENGTH) CSSDEF("border-top-width", border.y1, 0, CSS_TYPE_LENGTH) CSSDEF("border-right-width", border.x2, 0, CSS_TYPE_LENGTH) CSSDEF("border-bottom-width", border.y2, 0, CSS_TYPE_LENGTH) CSSDEF("border-color", border_colors, 0, CSS_TYPE_COLOR | CSS_TYPE_FOUR) CSSDEF("border-left-color", border_colors[0], 0, CSS_TYPE_COLOR) CSSDEF("border-top-color", border_colors[1], 0, CSS_TYPE_COLOR) CSSDEF("border-right-color", border_colors[2], 0, CSS_TYPE_COLOR) CSSDEF("border-bottom-color", border_colors[3], 0, CSS_TYPE_COLOR) CSSDEF("border-style", border_styles, 0, CSS_TYPE_BORDER_STYLE | CSS_TYPE_FOUR) CSSDEF("border-left-style", border_styles[0], 0, CSS_TYPE_BORDER_STYLE) CSSDEF("border-top-style", border_styles[1], 0, CSS_TYPE_BORDER_STYLE) CSSDEF("border-right-style", border_styles[2], 0, CSS_TYPE_BORDER_STYLE) CSSDEF("border-bottom-style", border_styles[3], 0, CSS_TYPE_BORDER_STYLE) CSSDEF("border", border, 0, CSS_TYPE_LENGTH | CSS_TYPE_COLOR | CSS_TYPE_BORDER_STYLE | CSS_TYPE_SPECIAL) CSSDEF("border-left", border, 0, CSS_TYPE_LENGTH | CSS_TYPE_COLOR | CSS_TYPE_BORDER_STYLE | CSS_TYPE_SPECIAL) CSSDEF("border-top", border, 0, CSS_TYPE_LENGTH | CSS_TYPE_COLOR | CSS_TYPE_BORDER_STYLE | CSS_TYPE_SPECIAL) CSSDEF("border-right", border, 0, CSS_TYPE_LENGTH | CSS_TYPE_COLOR | CSS_TYPE_BORDER_STYLE | CSS_TYPE_SPECIAL) CSSDEF("border-bottom", border, 0, CSS_TYPE_LENGTH | CSS_TYPE_COLOR | CSS_TYPE_BORDER_STYLE | CSS_TYPE_SPECIAL) CSSDEF("padding", padding, 0, CSS_TYPE_LENGTH | CSS_TYPE_FOUR) CSSDEF("padding-left", padding.x1, 0, CSS_TYPE_LENGTH) CSSDEF("padding-top", padding.y1, 0, CSS_TYPE_LENGTH) CSSDEF("padding-right", padding.x2, 0, CSS_TYPE_LENGTH) CSSDEF("padding-bottom", padding.y2, 0, CSS_TYPE_LENGTH) CSSDEF("margin", margin, 0, CSS_TYPE_LENGTH | CSS_TYPE_FOUR) CSSDEF("margin-left", margin.x1, 0, CSS_TYPE_LENGTH) CSSDEF("margin-top", margin.y1, 0, CSS_TYPE_LENGTH) CSSDEF("margin-right", margin.x2, 0, CSS_TYPE_LENGTH) CSSDEF("margin-bottom", margin.y2, 0, CSS_TYPE_LENGTH) CSSDEF("clear\0none,left,right,both", clear, 0, CSS_TYPE_ENUM) CSSDEF("overflow\0visible,hidden", overflow, 0, CSS_TYPE_ENUM) CSSDEF("visibility\0visible,hidden", visibility, 0, CSS_TYPE_ENUM) CSSDEF("table-layout\0auto,fixed", table_layout, 0, CSS_TYPE_ENUM) CSSDEF("vertical-align\0baseline,sub,super,top,text-top,middle,bottom,text-bottom", vertical_align, 0, CSS_TYPE_ENUM) CSSDEF("border-collapse\0collapse,separate", border_collapse, 1, CSS_TYPE_ENUM) CSSDEF("border-spacing", border_spacing_horizontal, 1, CSS_TYPE_LENGTH | CSS_TYPE_TWO) CSSDEF("border-spacing-horizontal", border_spacing_horizontal, 1, CSS_TYPE_LENGTH) CSSDEF("border-spacing-vertical", border_spacing_vertical, 1, CSS_TYPE_LENGTH) /* XXX: missing types */ CSSDEF("line-height", line_height, 1, CSS_TYPE_LENGTH | CSS_TYPE_AUTO) CSSDEF("position\0static,relative,absolute,fixed", position, 0, CSS_TYPE_ENUM) CSSDEF1("content", content, 0, CSS_TYPE_STRING | CSS_TYPE_ATTR | CSS_TYPE_COUNTER | CSS_TYPE_ARGS, CSS_STORAGE_PTR) CSSDEF("caption-side\0top,bottom,left,right", caption_side, 1, CSS_TYPE_ENUM) CSSDEF("marker-offset", marker_offset, 0, CSS_TYPE_LENGTH | CSS_TYPE_AUTO) CSSDEF("list-style-type", list_style_type, 1, CSS_TYPE_LIST_STYLE) CSSDEF("column-span", column_span, 0, CSS_TYPE_INTEGER) CSSDEF("row-span", row_span, 0, CSS_TYPE_INTEGER) CSSDEF1("content-alt", content_alt, 0, CSS_TYPE_STRING, CSS_STORAGE_PTR) CSSDEF("list-style-position\0outside,inside", list_style_position, 1, CSS_TYPE_ENUM) CSSDEF1("counter-reset\0none", counter_reset, 0, CSS_TYPE_ENUM | CSS_TYPE_INTEGER | CSS_TYPE_IDENT | CSS_TYPE_ARGS, CSS_STORAGE_PTR) CSSDEF1("counter-increment\0none", counter_increment, 0, CSS_TYPE_ENUM | CSS_TYPE_INTEGER | CSS_TYPE_IDENT | CSS_TYPE_ARGS, CSS_STORAGE_PTR) CSSDEF("bidi-mode\0normal,test", bidi_mode, 1, CSS_TYPE_ENUM) CSSDEF("left", left, 0, CSS_TYPE_LENGTH | CSS_TYPE_AUTO) CSSDEF("top", top, 0, CSS_TYPE_LENGTH | CSS_TYPE_AUTO) CSSDEF("right", right, 0, CSS_TYPE_LENGTH | CSS_TYPE_AUTO) CSSDEF("bottom", bottom, 0, CSS_TYPE_LENGTH | CSS_TYPE_AUTO) /* unsupported properties. We list them to ease documentation generation */ CSSUNDEF("background") CSSUNDEF("background-attachment") CSSUNDEF("background-image") CSSUNDEF("background-position") CSSUNDEF("background-repeat") CSSUNDEF("clip") CSSUNDEF("cursor") CSSUNDEF("empty-cells") CSSUNDEF("font") CSSUNDEF("font-size-adjust") CSSUNDEF("font-stretch") CSSUNDEF("font-variant") CSSUNDEF("letter-spacing") CSSUNDEF("list-style") CSSUNDEF("list-style-image") CSSUNDEF("max-height") CSSUNDEF("max-width") CSSUNDEF("min-height") CSSUNDEF("min-width") CSSUNDEF("outline") CSSUNDEF("outline-color") CSSUNDEF("outline-style") CSSUNDEF("outline-width") CSSUNDEF("quotes") CSSUNDEF("text-indent") CSSUNDEF("text-shadow") CSSUNDEF("text-transform") CSSUNDEF("word-spacing") CSSUNDEF("z-index") /* paged media */ CSSUNDEF("marks") CSSUNDEF("page") CSSUNDEF("page-break-after") CSSUNDEF("page-break-before") CSSUNDEF("page-break-inside") CSSUNDEF("size") CSSUNDEF("orphans") CSSUNDEF("widows") /* all aural properties are unsupported */ CSSUNDEF("azimuth") CSSUNDEF("cue") CSSUNDEF("cue-after") CSSUNDEF("cue-before") CSSUNDEF("elevation") CSSUNDEF("pause") CSSUNDEF("pause-after") CSSUNDEF("pause-before") CSSUNDEF("pitch") CSSUNDEF("pitch-range") CSSUNDEF("pitch-during") CSSUNDEF("richness") CSSUNDEF("speak") CSSUNDEF("speak-header") CSSUNDEF("speak-punctuation") CSSUNDEF("speak-rate") CSSUNDEF("stress") CSSUNDEF("voice-family") CSSUNDEF("volume") }; /*************************************************/ /* the following will be moved to other files */ /* XXX: use unicode */ typedef int (*NextCharFunc)(EditBuffer *b, unsigned long *offset); int eb_nextc1(EditBuffer *b, unsigned long *offset_ptr) { unsigned long offset1; int ch, ch1; char name[16], *q; ch = eb_nextc(b, *offset_ptr, (int *)offset_ptr); if (ch == '&') { /* read entity */ offset1 = *offset_ptr; q = name; for(;;) { ch1 = eb_nextc(b, *offset_ptr, (int *)offset_ptr); if (ch1 == '\n' || ch1 == ';') break; *q++ = ch1; if (q >= name + sizeof(name) - 1) break; } *q = '\0'; ch1 = find_entity(name); if (ch1 >= 0) { ch = ch1; } else { /* entity not found: roll back */ *offset_ptr = offset1; } } return ch; } int str_nextc(EditBuffer *b, unsigned long *offset_ptr) { const unsigned char *ptr; int ch; ptr = *(const unsigned char **)offset_ptr; ch = *ptr; if (ch >= 128) { ch = utf8_decode((const char **)&ptr); } else { ptr++; } *(const unsigned char **)offset_ptr = ptr; return ch; } static NextCharFunc get_nextc(CSSBox *box) { switch(box->content_type) { case CSS_CONTENT_TYPE_STRING: return str_nextc; case CSS_CONTENT_TYPE_BUFFER: return eb_nextc1; default: assert(1); return NULL; } } /***********************************************************/ /* CSS identifier manager */ #define CSS_IDENT_HASH_SIZE 521 #define CSS_IDENT_INCR 128 static CSSIdentEntry *hash_ident[CSS_IDENT_HASH_SIZE]; static CSSIdentEntry **table_ident; static int table_ident_nb, table_ident_allocated; const char css_idents[] = "\0" "*\0" #define CSSID(id) #id "\0" #define CSSIDSTR(id, str) str "\0" #include "cssid.h" #undef CSSID #undef CSSIDSTR "\1"; static unsigned int hash_str(const char *str, unsigned int hash_size) { unsigned int h, ch; h = 1; while (*str) { ch = *(unsigned char *)str++; h = ((h << 8) + ch) % hash_size; } return h; } const char *css_ident_str(CSSIdent id) { return table_ident[id]->str; } CSSIdent css_new_ident(const char *str) { CSSIdentEntry **pp, *p; int n; pp = &hash_ident[hash_str(str, CSS_IDENT_HASH_SIZE)]; p = *pp; while (p != NULL) { if (!strcmp(str, p->str)) { return p->id; } p = p->hash_next; } p = malloc(sizeof(CSSIdentEntry) + strlen(str)); if (!p) return CSS_ID_NIL; p->id = table_ident_nb; strcpy(p->str, str); p->hash_next = *pp; *pp = p; /* put ident in table */ if (table_ident_nb == table_ident_allocated) { n = table_ident_allocated + CSS_IDENT_INCR; pp = realloc(table_ident, n * sizeof(CSSIdentEntry *)); if (!pp) { free(p); return CSS_ID_NIL; } table_ident = pp; table_ident_allocated = n; } table_ident[table_ident_nb++] = p; return p->id; } static void css_init_idents(void) { const char *p, *r; char buf[64]; if (table_ident_nb != 0) return; for(p = css_idents; *p != '\1'; p = r + 1) { r = p; while (*r) r++; memcpy(buf, p, r - p); buf[r - p] = '\0'; css_new_ident(buf); } } /****************************************************/ /* set a counter in the current element to value "value". If not present, then add it */ static void set_counter(CSSContext *s, CSSIdent counter_id, int value) { CSSCounterValue *p; for(p = s->counter_stack_ptr; p != s->counter_stack_base; p = p->prev) { if (p->counter_id == counter_id) { p->value = value; } } p = malloc(sizeof(CSSCounterValue)); if (!p) return; p->counter_id = counter_id; p->value = value; p->prev = s->counter_stack_ptr; s->counter_stack_ptr = p; } /* save counter stack so that we can free counters later */ static CSSCounterValue *push_counters(CSSContext *s) { CSSCounterValue *p; p = s->counter_stack_base; s->counter_stack_base = s->counter_stack_ptr; return p; } /* pop counters until counter_stack_ptr reaches 'counter_stack_base' */ static void pop_counters(CSSContext *s, CSSCounterValue *p2) { CSSCounterValue *p, *p1; for(p = s->counter_stack_ptr; p != s->counter_stack_base; p = p1) { p1 = p->prev; free(p); p = p1; } s->counter_stack_ptr = s->counter_stack_base; s->counter_stack_base = p2; } static void incr_counter(CSSContext *s, CSSIdent counter_id, int incr) { CSSCounterValue *p; for(p = s->counter_stack_ptr; p != NULL; p = p->prev) { if (p->counter_id == counter_id) { p->value += incr; return; } } set_counter(s, counter_id, incr); } /* return the value of a given counter, and zero if it is not defined */ static int get_counter(CSSContext *s, CSSIdent counter_id) { CSSCounterValue *p; for(p = s->counter_stack_ptr; p != NULL; p = p->prev) { if (p->counter_id == counter_id) { return p->value; } } return 0; /* default value is zero */ } static QEFont *css_select_font(QEditScreen *screen, CSSState *props); static void css_eval_property(CSSContext *s, CSSState *state, CSSProperty *p, CSSState *state_parent, CSSBox *box) { int *ptr, val; const CSSPropertyDef *def; if (p->property >= NB_PROPERTIES) { dprintf("css_eval: invalid property: %d\n", p->property); return; } def = &css_properties[p->property]; ptr = (int *)((char *)state + def->struct_offset); if (p->value.u.val == CSS_INHERIT) { /* XXX: invalid if pointer */ switch(def->storage) { default: case CSS_STORAGE_INT: *ptr = *(int *)((char *)state_parent + def->struct_offset); break; case CSS_STORAGE_PTR: *(void **)ptr = *(void **)((char *)state_parent + def->struct_offset); break; } } else { if (def->storage == CSS_STORAGE_PTR) { *(void **)ptr = p; } else { switch(p->value.type) { case CSS_VALUE_COLOR: case CSS_UNIT_NONE: case CSS_VALUE_INTEGER: *ptr = p->value.u.val; break; case CSS_UNIT_PIXEL: /* convert from px to display pixels */ *ptr = (p->value.u.val * s->px_size) >> CSS_LENGTH_FRAC_BITS; break; case CSS_UNIT_IN: /* convert from inches to display pixels */ *ptr = (p->value.u.val * s->dots_per_inch) >> CSS_LENGTH_FRAC_BITS; break; case CSS_UNIT_PERCENT: val = *(int *)((char *)state_parent + def->struct_offset); *ptr = (p->value.u.val * val) >> CSS_LENGTH_FRAC_BITS; break; case CSS_UNIT_EX: /* currently, we do not use the font metrics */ val = (state->font_size * CSS_EX_SCALE) >> CSS_LENGTH_FRAC_BITS; *ptr = (p->value.u.val * val) >> CSS_LENGTH_FRAC_BITS; break; case CSS_UNIT_EM: /* special case for font size : inherit from parent */ if (p->property == CSS_font_size) val = state_parent->font_size; else val = state->font_size; *ptr = (p->value.u.val * val) >> CSS_LENGTH_FRAC_BITS; break; } } } } static inline int attribute_match(CSSStyleSheetAttributeEntry *e, CSSAttribute *a) { CSSIdent attr; int op; /* no match possible if no attribute */ if (!a) return 0; attr = e->attr; op = e->op; do { if (attr == a->attr) { switch(op) { case CSS_ATTR_OP_SET: return 1; case CSS_ATTR_OP_EQUAL: /* XXX: use global case setting, as in parsing */ if (!strcasecmp(e->value, a->value)) return 1; break; case CSS_ATTR_OP_IN_LIST: case CSS_ATTR_OP_IN_HLIST: /* XXX: todo */ break; } } a = a->next; } while (a != NULL); return 0; } /* return true if (box,pelement) matches simple selector ss (may recurse) */ /* XXX: exclude anonymous boxes */ static int selector_match(CSSSimpleSelector *ss, CSSBox *box) { CSSStyleSheetAttributeEntry *ae; CSSBox *box1, *lbox; if (ss->tag != box->tag && ss->tag != CSS_ID_ALL) return 0; /* first child test */ if (ss->pclasses & CSS_PCLASS_FIRST_CHILD) { box1 = box->parent; if (box1 && box != box1->u.child.first) return 0; } switch(ss->tree_op) { case CSS_TREE_OP_DESCENDANT: box1 = box->parent; while (box1 != NULL) { if (selector_match(ss->next, box1)) goto found; box1 = box1->parent; } return 0; case CSS_TREE_OP_CHILD: box1 = box->parent; if (box1 && selector_match(ss->next, box1)) goto found; return 0; case CSS_TREE_OP_PRECEEDED: /* search previous box */ box1 = box->parent; if (box1 && box1->content_type == CSS_CONTENT_TYPE_CHILDS) { box1 = box1->u.child.first; lbox = NULL; while (box1 != NULL && box1 != box) { lbox = box1; box1 = box1->next; } if (lbox && selector_match(ss->next, lbox)) goto found; } return 0; default: break; /* match */ } found: /* verify that attributes match */ ae = ss->attrs; while (ae != NULL) { if (!attribute_match(ae, box->attrs)) return 0; ae = ae->next; } return 1; } #define PELEMENTS_MASK (~CSS_PCLASS_FIRST_CHILD) static int apply_properties(CSSContext *s, CSSIdent tag, CSSBox *box, int pelement, CSSState *state, CSSState *state_parent) { CSSStyleSheetEntry *e; CSSProperty *p; int pelement_found; /* go thru the style sheet */ e = s->style_sheet->tag_hash[css_hash_ident(tag, CSS_TAG_HASH_SIZE)]; pelement_found = 0; while (e != NULL) { if (e->sel.tag == tag) { /* verify media */ if ((s->media & e->media) == 0) goto next; if (selector_match(&e->sel, box)) { /* add pseudo classes found */ pelement_found |= e->sel.pclasses; /* see if selector pseudo classes matches pseudo element (null means none) */ if ((pelement == 0 && (e->sel.pclasses & PELEMENTS_MASK) == 0) || (pelement != 0 && (e->sel.pclasses & pelement) != 0)) { /* apply properties */ p = e->props; while (p != NULL) { css_eval_property(s, state, p, state_parent, box); p = p->next; } } } } next: e = e->hash_next; } return pelement_found; } static char *eval_content(CSSContext *s, CSSProperty *p, CSSBox *box) { CSSPropertyValue *value; CSSAttribute *attr; char buf[4096]; /* XXX: make it dynamic ? */ char buf1[256]; int i, index; buf[0] = '\0'; value = &p->value; for(i=0;inb_values;i++) { switch(value->type) { case CSS_VALUE_STRING: pstrcat(buf, sizeof(buf), value->u.str); break; case CSS_VALUE_ATTR: for(attr = box->attrs; attr != NULL; attr = attr->next) { if (attr->attr == value->u.attr_id) { pstrcat(buf, sizeof(buf), attr->value); break; } } break; case CSS_VALUE_COUNTER: index = get_counter(s, value->u.counter.counter_id); css_counter_str(buf1, sizeof(buf1), index, value->u.counter.type, 0); pstrcat(buf, sizeof(buf), buf1); break; } value++; } if (buf[0] != '\0') return strdup(buf); else return NULL; } static void eval_counter_update(CSSContext *s, CSSProperty *p) { CSSPropertyValue *value = &p->value; int counter_id, n, i; for(i=0;inb_values;) { if (value->type != CSS_VALUE_IDENT) break; counter_id = value->u.attr_id; i++; value++; if (p->property == CSS_counter_reset) n = 0; else n = 1; /* optional init value */ if (i < p->nb_values && value->type == CSS_VALUE_INTEGER) { n = value->u.val; i++; value++; } if (p->property == CSS_counter_reset) set_counter(s, counter_id, n); else incr_counter(s, counter_id, n); } } /* only rules matching (box, pelement) are considered. All the pseudo element found for the box are returned, so that new boxes can be created for before & after pseudo elements. */ static int css_eval(CSSContext *s, CSSState *state, CSSBox *box, int pelement, CSSState *state_parent) { const CSSPropertyDef *def; int *ptr_parent, *ptr, type, val, i; CSSProperty *p; int pelement_found; /* inherit properties or set to default value */ def = css_properties; while (def < css_properties + NB_PROPERTIES) { type = def->type; /* the TYPE_FOUR are not real css properties */ if (!(type & CSS_TYPE_FOUR)) { ptr = (int *)((char *)state + def->struct_offset); if (def->type & CSS_TYPE_INHERITED) { /* inherit value */ ptr_parent = (int *)((char *)state_parent + def->struct_offset); val = *ptr_parent; } else { /* default values: color assumed to transparent, and if auto is a possible value, then it is set. Otherwise zero is the value */ if (type & CSS_TYPE_COLOR) { val = COLOR_TRANSPARENT; } else if (type & CSS_TYPE_AUTO) { val = CSS_AUTO; } else { val = 0; } } *ptr = val; } def++; } /* apply generic attributes */ pelement_found = apply_properties(s, CSS_ID_ALL, box, pelement, state, state_parent); if (box->tag) { pelement_found |= apply_properties(s, box->tag, box, pelement, state, state_parent); } /* apply explicit properties */ p = box->properties; while (p != NULL) { css_eval_property(s, state, p, state_parent, box); p = p->next; } /* first reset counters */ if (state->counter_reset) eval_counter_update(s, state->counter_reset); /* then increment */ if (state->counter_increment) eval_counter_update(s, state->counter_increment); /* alternate content if image (need more ideas) */ if (state->content_alt && box->content_type == CSS_CONTENT_TYPE_IMAGE) { box->u.image.content_alt = eval_content(s, state->content_alt, box); } /* border colors are set to color by default */ for(i=0;i<4;i++) { if (state->border_colors[i] == COLOR_TRANSPARENT) state->border_colors[i] = state->color; } return pelement_found; } static void set_default_props(CSSContext *s, CSSState *props) { memset(props, 0, sizeof(CSSState)); /* size of 12 points */ props->font_size = (12 * s->dots_per_inch) / 72; props->font_family = QE_FAMILY_SERIF; props->border_colors[0] = COLOR_TRANSPARENT; props->border_colors[1] = COLOR_TRANSPARENT; props->border_colors[2] = COLOR_TRANSPARENT; props->border_colors[3] = COLOR_TRANSPARENT; props->bgcolor = COLOR_TRANSPARENT; props->line_height = CSS_AUTO; } #if 0 /* accepts null strings */ static inline int strcmp_null(const char *s1, const char *s2) { if (s1 == NULL || s2 == NULL) { return (s1 != s2); } else { return strcmp(s1, s2); } } #endif /* hash a byte string */ /* XXX: faster hash ? */ static inline unsigned int hash_bytes(unsigned int h, const unsigned char *p, int len) { const unsigned char *p_end; p_end = p + len; for(; p < p_end; p++) { h = ((h << 8) + p[0]) % PROPS_HASH_SIZE; } return h; } static unsigned int hash_props(CSSState *props) { unsigned int h; /* since no holes are the the structure, we can compute the hash on the bytes */ h = hash_bytes(1, (unsigned char *)props, PROPS_SIZE); return h; } static int is_equal_props(CSSState *p1, CSSState *p2) { if (memcmp(p1, p2, PROPS_SIZE) != 0) return 0; return 1; } /* allocate a memory slot for 'props' which is shared with others */ static CSSState *allocate_props(CSSContext *s, CSSState *props) { CSSState **pp, *p; pp = &s->hash_props[hash_props(props)]; for(;;) { p = *pp; if (!p) break; /* if properties are already there, then no need to allocate */ if (is_equal_props(p, props)) return p; pp = &p->hash_next; } /* add new props */ p = malloc(sizeof(CSSState)); if (!p) return NULL; s->nb_props++; memcpy(p, props, sizeof(CSSState)); *pp = p; p->hash_next = NULL; return p; } /* free one CSSState */ static void free_props(CSSState *props) { free(props); } static int css_compute_block(CSSContext *s, CSSBox *box, CSSState *parent_props); static CSSBox *add_before_after_box(CSSContext *s, CSSBox *box, int pelement) { CSSState pelement_props1, *pelement_props = &pelement_props1; CSSBox *box1; char *content; css_eval(s, pelement_props, box, pelement, box->props); if (!pelement_props->content) return NULL; content = eval_content(s, pelement_props->content, box); if (!content) return NULL; box1 = css_new_box(CSS_ID_NIL, NULL); if (!box1) return NULL; css_compute_block(s, box1, pelement_props); css_set_text_string(box1, content); free(content); /* XXX: make child box */ return box1; } /* NOTE: 'buf' must be large enough */ /* XXX: find a shorter code :-) */ static void css_to_roman(char *buf, int n) { int n1, n10; static const char roman_digits[] = "IVXLCDM"; const char *p; char buf1[17], *q; if (n <= 0 || n >= 4000) { sprintf(buf, "%d", n); return; } p = roman_digits; q = buf1; while (n != 0) { n10 = n % 10; n1 = n10 % 5; if (n1 == 4) { *q++ = p[1 + (n10 == 9)]; *q++ = p[0]; } else { while (n1--) *q++ = p[0]; if (n10 >= 5) *q++ = p[1]; } n = n / 10; p += 2; } while (--q >= buf1) *buf++ = *q; *buf = '\0'; } /* if adjust is true, then always use zero base and increment if necessary */ static void css_counter_str(char *text, int text_size, int index, int list_style_type, int adjust) { /* insert marker text */ switch(list_style_type) { case CSS_LIST_STYLE_TYPE_DISC: strcpy(text, "o"); break; case CSS_LIST_STYLE_TYPE_CIRCLE: strcpy(text, "o"); break; case CSS_LIST_STYLE_TYPE_SQUARE: strcpy(text, "."); break; case CSS_LIST_STYLE_TYPE_DECIMAL: snprintf(text, text_size, "%d", index + adjust); goto add_dot; case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: if (index > 25) index = 25; text[0] = 'A' + index; text[1] = '\0'; goto add_dot; case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: css_to_roman(text, index + adjust); add_dot: if (adjust) pstrcat(text, text_size, "."); break; default: text[0] = '\0'; break; } if (list_style_type == CSS_LIST_STYLE_TYPE_LOWER_ALPHA || list_style_type == CSS_LIST_STYLE_TYPE_LOWER_ROMAN) css_strtolower(text, text_size); } /* add a marker or an inline box to a list item box */ static CSSBox *add_marker_box(CSSContext *s, CSSBox *box) { CSSState marker_props1, *marker_props = &marker_props1, *aprops; CSSBox *box1; char text[256]; int index; int position; box1 = css_new_box(CSS_ID_NIL, NULL); if (!box1) return NULL; position = box->props->list_style_position; /* create marker properties */ *marker_props = *box->props; if (position == CSS_LIST_STYLE_POSITION_OUTSIDE) { marker_props->display = CSS_DISPLAY_MARKER; marker_props->bgcolor = COLOR_TRANSPARENT; } else { /* if inside, then create it as an inline box */ marker_props->display = CSS_DISPLAY_INLINE; } aprops = allocate_props(s, marker_props); if (!aprops) { css_delete_box(box1); return NULL; } box1->props = aprops; index = get_counter(s, CSS_ID_list_item); incr_counter(s, CSS_ID_list_item, 1); css_counter_str(text, sizeof(text), index, marker_props->list_style_type, 1); if (position == CSS_LIST_STYLE_POSITION_INSIDE) { /* add an extra space if inside */ pstrcat(text, sizeof(text), " "); } css_set_text_string(box1, text); if (marker_props->display == CSS_DISPLAY_MARKER) { CSSBox *box2; css_make_child_box(box1); /* add the inline text inside */ /* XXX: alloc error testing ? */ box2 = box1->u.child.first; marker_props->display = CSS_DISPLAY_INLINE; aprops = allocate_props(s, marker_props); box2->props = aprops; } return box1; } /* compute the CSS properties of a box */ static int css_compute_block(CSSContext *s, CSSBox *box, CSSState *parent_props) { CSSState *aprops; CSSState props1, *props = &props1; CSSBox *box1, *box_next, **pbox; int pelement_found; CSSCounterValue *counter_stack; pelement_found = css_eval(s, props, box, 0, parent_props); /* allocate the properties for this box */ aprops = allocate_props(s, props); if (!aprops) return -1; box->props = aprops; /* if the box is of type block, then it must contains childs, so we add a child as a dummy box */ if (props->display != CSS_DISPLAY_INLINE && box->content_type != CSS_CONTENT_TYPE_CHILDS && box->content_type != CSS_CONTENT_TYPE_IMAGE) { css_make_child_box(box); } /* if boxes are inside, then evaluate their properties too */ if (box->content_type == CSS_CONTENT_TYPE_CHILDS) { /* other boxes are inside: handle them */ counter_stack = push_counters(s); for(box1 = box->u.child.first; box1 != NULL; box1 = box_next) { /* need to take next here because of :after inserted boxes */ box_next = box1->next; if (css_compute_block(s, box1, props) < 0) return -1; } pop_counters(s, counter_stack); /* for :after and :before we create new boxes at the start or the end of the child list */ /* XXX: mark them as temporary */ box1 = NULL; if (pelement_found & CSS_PCLASS_BEFORE) { box1 = add_before_after_box(s, box, CSS_PCLASS_BEFORE); if (box1) { box1->next = box->u.child.first; box->u.child.first = box1; box1->parent = box; } } /* for list items, we must generate a marker/inline box, unless it was already generated by ':before' */ if (props->display == CSS_DISPLAY_LIST_ITEM && (!box1 || box1->props->display != CSS_DISPLAY_MARKER)) { box1 = add_marker_box(s, box); /* add it as first box */ box1->next = box->u.child.first; box->u.child.first = box1; box1->parent = box; } if (pelement_found & CSS_PCLASS_AFTER) { box1 = add_before_after_box(s, box, CSS_PCLASS_AFTER); if (box1) { pbox = &box->u.child.first; while (*pbox != NULL) pbox = &(*pbox)->next; *pbox = box1; box1->next = NULL; } } } else { /* inline case for before & after : insert directly in list */ if (pelement_found & CSS_PCLASS_BEFORE) { box1 = add_before_after_box(s, box, CSS_PCLASS_BEFORE); /* add before box */ if (box1) { pbox = &box->parent->u.child.first; while (*pbox != box) pbox = &(*pbox)->next; box1->next = *pbox; *pbox = box1; box1->parent = box->parent; } } if (pelement_found & CSS_PCLASS_AFTER) { box1 = add_before_after_box(s, box, CSS_PCLASS_AFTER); /* add after box */ if (box1) { box1->next = box->next; box->next = box1; box1->parent = box->parent; } } } return 0; } /* compute the CSS properties of a complete document */ int css_compute(CSSContext *s, CSSBox *box) { CSSState props1, *default_props = &props1; int ret; // css_dump(box); set_default_props(s, default_props); s->counter_stack_base = NULL; s->counter_stack_ptr = NULL; ret = css_compute_block(s, box, default_props); pop_counters(s, NULL); // printf("nb_props=%d\n", s->nb_props); return ret; } /* split a css inline box at text offset 'offset' */ /* XXX: handle last_space */ static void css_box_split(CSSBox *box1, int offset) { CSSBox *box2; #if 0 { dprintf("css_box_split: box=%p %d\n", box1, offset); if (!(offset >= box1->u.buffer.start && offset < box1->u.buffer.end)) { dprintf("css_box_split: error offset=%d start=%lu end=%lu\n", offset, box1->u.buffer.start, box1->u.buffer.end); } } #endif box2 = malloc(sizeof(CSSBox)); if (!box2) return; memset(box2, 0, sizeof(CSSBox)); box2->split = 1; box2->props = box1->props; /* same properties */ box2->content_type = box1->content_type; box2->content_eol = box1->content_eol; box1->content_eol = 0; box2->u.buffer.start = offset; box2->u.buffer.end = box1->u.buffer.end; box1->u.buffer.end = offset; box2->next = box1->next; box1->next = box2; box2->embedding_level = box1->embedding_level; box2->parent = box1->parent; } /******************************************************************/ /* text layout */ typedef struct LayoutOutput { int margin_top, margin_bottom; int baseline; int min_width, max_width; } LayoutOutput; typedef struct FloatBlock { /* absolute position of the float, including h margins */ int x, y, width, height; int float_type; /* -1 indicate that the float is not layouted yet */ CSSBox *box; struct FloatBlock *next; } FloatBlock; typedef struct LayoutState { CSSContext *ctx; FloatBlock *first_float; } LayoutState; static int css_layout_block(CSSContext *s, LayoutOutput *block_layout, CSSBox *block_box); static int css_layout_block_min_max(CSSContext *s, int *min_width_ptr, int *max_width_ptr, CSSBox *block_box); typedef struct BidirAttrState { CSSContext *ctx; TypeLink *list_end; TypeLink *list_ptr; /* current type pointer */ FriBidiCharType ltype; /* last type */ int pos; /* current char position */ } BidirAttrState; static void bidir_compute_attributes_box(BidirAttrState *s, CSSBox *box) { CSSState *props = box->props; int c, pos; unsigned long offset; TypeLink *p; FriBidiCharType type, ltype; NextCharFunc nextc = get_nextc(box); int bidi_mode; /* init the embedding_level to a known value to permit multiple layouts */ box->embedding_level = 0; pos = s->pos; p = s->list_ptr; ltype = s->ltype; /* add bidi embed/override if needed */ if (props->unicode_bidi != CSS_UNICODE_BIDI_NORMAL) { if (props->unicode_bidi == CSS_UNICODE_BIDI_EMBED) { if (props->direction == CSS_DIRECTION_LTR) type = FRIBIDI_TYPE_LRE; else type = FRIBIDI_TYPE_RLE; } else { if (props->direction == CSS_DIRECTION_LTR) type = FRIBIDI_TYPE_LRO; else type = FRIBIDI_TYPE_RLO; } if (type != ltype && p < s->list_end) { p->type = type; p->pos = pos; p->len = 1; p++; ltype = type; } else { p[-1].len++; } } if (props->display == CSS_DISPLAY_INLINE_TABLE || props->display == CSS_DISPLAY_INLINE_BLOCK) { /* add a neutral type for images or inline tables */ type = FRIBIDI_TYPE_ON; if (type != ltype && p < s->list_end) { p->type = type; p->pos = pos; p->len = 1; p++; ltype = type; } else { p[-1].len++; } pos++; } else { offset = box->u.buffer.start; bidi_mode = props->bidi_mode; while (offset < box->u.buffer.end) { c = nextc(s->ctx->b, &offset); pos++; if (bidi_mode == CSS_BIDI_MODE_TEST) type = fribidi_get_type_test(c); else type = fribidi_get_type(c); /* if not enough room, increment last link */ if (type != ltype && p < s->list_end) { p->type = type; p->pos = pos - 1; p->len = 1; p++; ltype = type; } else { p[-1].len++; } } } /* add end of bidi embed/override if needed */ if (props->unicode_bidi != CSS_UNICODE_BIDI_NORMAL) { type = FRIBIDI_TYPE_PDF; if (type != ltype && p < s->list_end) { p->type = type; p->pos = pos; p->len = 1; p++; ltype = type; } else { p[-1].len++; } } s->pos = pos; s->list_ptr = p; s->ltype = ltype; } /* max_size should be >= 2 */ /* XXX: add CSS unicode-bidi property handling */ static int bidir_compute_attributes(CSSContext *ctx, TypeLink *list_tab, int max_size, CSSBox *first_box) { CSSBox *box; TypeLink *p; BidirAttrState bidir_state, *s = &bidir_state; p = list_tab; /* Add the starting link */ p->type = FRIBIDI_TYPE_SOT; p->len = 0; p->pos = 0; p++; s->ctx = ctx; s->list_end = list_tab + max_size - 1; s->list_ptr = p; s->ltype = FRIBIDI_TYPE_SOT; s->pos = 0; for(box = first_box; box != NULL; box = box->next_inline) { bidir_compute_attributes_box(s, box); } /* Add the ending link */ p = s->list_ptr; p->type = FRIBIDI_TYPE_EOT; p->len = 0; p->pos = s->pos; p++; return p - list_tab; } /* split the boxes so that the embedding is constant inside a box. */ typedef struct BidirSplitState { CSSContext *ctx; TypeLink *l; int pos; } BidirSplitState; static void css_bidir_split_box(BidirSplitState *s, CSSBox *box) { CSSState *props = box->props; TypeLink *l; int c, pos; unsigned long offset; NextCharFunc nextc; l = s->l; pos = s->pos; if (props->display == CSS_DISPLAY_INLINE_TABLE || props->display == CSS_DISPLAY_INLINE_BLOCK) { /* single position increment, as in a attribute compute */ if (pos >= l[1].pos) l++; pos++; } else { offset = box->u.buffer.start; nextc = get_nextc(box); while (offset < box->u.buffer.end) { if (pos >= l[1].pos) { l++; if (offset > box->u.buffer.start && l[0].level != l[-1].level) { /* different level : split box */ box->embedding_level = l[-1].level; css_box_split(box, offset); /* update next_inline field */ box->next->next_inline = box->next_inline; box->next_inline = box->next; goto the_end; } } c = nextc(s->ctx->b, &offset); pos++; } } box->embedding_level = l[0].level; the_end: s->l = l; s->pos = pos; } static void css_bidir_split(CSSContext *s, CSSBox *first_box, TypeLink *l) { CSSBox *box; BidirSplitState bidir_split; bidir_split.ctx = s; bidir_split.l = l + 1; bidir_split.pos = 0; for(box = first_box; box != NULL; box = box->next_inline) { css_bidir_split_box(&bidir_split, box); } } #define RLE_EMBEDDINGS_SIZE 128 typedef struct BidirComputeState { CSSContext *ctx; int inline_layout; CSSBox *first_inline; /* first inline box of current inline formatting context */ CSSBox **pbox; /* pointer to last next_inline field */ } BidirComputeState; static void bidir_start_inline(BidirComputeState *s) { s->pbox = &s->first_inline; s->inline_layout = 1; } static void bidir_end_inline(BidirComputeState *s) { TypeLink embeds[RLE_EMBEDDINGS_SIZE]; int embedding_max_level; FriBidiCharType base; /* mark last box */ *s->pbox = NULL; /* now we can do the bidir compute easily */ if (bidir_compute_attributes(s->ctx, embeds, RLE_EMBEDDINGS_SIZE, s->first_inline) > 2) { base = FRIBIDI_TYPE_WL; fribidi_analyse_string(embeds, &base, &embedding_max_level); #if 0 { TypeLink *p; p = embeds; printf("bidi_start:\n"); for(;;) { printf("type=%d pos=%d len=%d level=%d\n", p->type, p->pos, p->len, p->level); if (p->type == FRIBIDI_TYPE_EOT) break; p++; } } #endif /* optimization: no need to split if no embedding */ if (embedding_max_level > 0) css_bidir_split(s->ctx, s->first_inline, embeds); } /* terminate inline layout */ s->inline_layout = 0; } static int css_layout_bidir_block(CSSContext *ctx, CSSBox *box); /* layout a box inside a block or inline formatting context */ static int css_layout_bidir_box(BidirComputeState *s, CSSBox *box) { CSSState *props = box->props; CSSBox *box1; int ret; if (props->block_float != CSS_FLOAT_NONE) { if (props->display != CSS_DISPLAY_NONE) css_layout_bidir_block(s->ctx, box); } else { /* now do the layout, according to the display type */ switch(props->display) { case CSS_DISPLAY_INLINE: case CSS_DISPLAY_INLINE_TABLE: case CSS_DISPLAY_INLINE_BLOCK: if (!s->inline_layout) bidir_start_inline(s); if (props->display == CSS_DISPLAY_INLINE_TABLE || props->display == CSS_DISPLAY_INLINE_BLOCK) { ret = css_layout_bidir_block(s->ctx, box); if (ret) return ret; } if (props->display != CSS_DISPLAY_INLINE_TABLE && props->display != CSS_DISPLAY_INLINE_BLOCK && box->content_type == CSS_CONTENT_TYPE_CHILDS) { /* recurse inside in same formatting context */ for(box1 = box->u.child.first; box1 != NULL; box1 = box1->next) { ret = css_layout_bidir_box(s, box1); if (ret) return ret; } } else { /* add the box in the inline box list */ *s->pbox = box; s->pbox = &box->next_inline; } break; case CSS_DISPLAY_NONE: break; default: /* all other boxes are considered as block boxes */ if (s->inline_layout) bidir_end_inline(s); ret = css_layout_bidir_block(s->ctx, box); if (ret) return ret; break; } } return 0; } /* bidir compute of the inside of a block */ static int css_layout_bidir_block(CSSContext *ctx, CSSBox *box) { BidirComputeState bidi_state, *s = &bidi_state; CSSBox *box1; int ret; if (box->content_type != CSS_CONTENT_TYPE_CHILDS) return 0; s->inline_layout = 0; s->ctx = ctx; for(box1 = box->u.child.first; box1 != NULL; box1 = box1->next) { ret = css_layout_bidir_box(s, box1); if (ret) return ret; } if (s->inline_layout) bidir_end_inline(s); return 0; } typedef struct { CSSBox *box; short baseline_delta; /* delta to the line baseline */ short ascent; /* font ascent */ } InlineBox; static void reverse_boxes(InlineBox *str, int len) { int i, len2 = len / 2; for (i = 0; i < len2; i++) { InlineBox tmp = str[i]; str[i] = str[len - 1 - i]; str[len - 1 - i] = tmp; } } static void embed_boxes(InlineBox *line, int len, int level_max) { int level, pos, p; for (level = level_max; level > 0; level--) { pos = 0; while (pos < len) { if (line[pos].box->embedding_level >= level) { /* find all chars >= level */ for(p = pos + 1; p < len && line[p].box->embedding_level >= level; p++); reverse_boxes(line + pos, p - pos); pos = p + 1; } else { pos++; } } } } #define NB_LINE_BOXES_MAX 100 #define BOX_STACK_SIZE 200 /* layout context type */ #define LAYOUT_TYPE_BLOCK 0 #define LAYOUT_TYPE_INLINE 1 typedef struct InlineLayout { /* info for both inline/block layout */ LayoutState *layout_state; CSSContext *ctx; int x0, y0; /* origin of the current inline layout context, used when floats are present */ int total_width; /* total width available (not counting floats) */ int y; /* current y (top of current line) */ int layout_type; int is_first_box; int margin_top; int last_ymargin; int line_count; int first_line_baseline; /* baseline of the first line */ CSSBox *marker_box; /* pointer to the last marker box */ int marker_baseline; /* marker baseline position */ /* inline layout context */ int x; /* current x */ int last_space; /* true if last char was a space */ /* memorize the position of the beginning of the current word */ int index_bow, offset_bow, width_bow; int lastwordspace; int line_pos; /* box number on the current line */ int char_pos; /* character count on the current line */ int xstart, avail_width; /* line position including floats */ int word_index; int box_stack_index; /* current position in box stack */ /* min/max width computation */ int compute_min_max; int min_width, max_width; InlineBox line_boxes[NB_LINE_BOXES_MAX]; /* fragment layout = part of line */ unsigned int word_buf[MAX_WORD_SIZE]; int word_offsets[MAX_WORD_SIZE]; /* the box stack contains the boxes which where after the last word of the current line and which must be layouted on the next line */ CSSBox *box_stack[BOX_STACK_SIZE]; } InlineLayout; /* layout a float box and position it */ static int css_layout_float(InlineLayout *s, FloatBlock *b) { CSSBox *box = b->box; CSSState *props = box->props; LayoutOutput layout; FloatBlock *b1; int lmargin, rmargin, tmargin, bmargin, y, y2, x1, x2, y_next; /* set the width */ if (props->width == CSS_AUTO) { int min_w, max_w; /* layout to get the dimensions */ css_layout_block_min_max(s->ctx, &min_w, &max_w, box); box->width = max_w; } else { box->width = props->width; } if (props->height != CSS_AUTO) box->height = props->height; #if 0 printf("layout float: %s w=%d h=%d\n", css_ident_str(box->tag), box->width, box->height); #endif /* layout the interior */ if (css_layout_block(s->ctx, &layout, box)) { free(b); return -1; } /* add the float in the float list */ b->float_type = props->block_float; /* compute the position of the float */ lmargin = props->margin.x1 + props->border.x1 + props->padding.x1; rmargin = props->margin.x2 + props->border.x2 + props->padding.x2; tmargin = props->border.y1 + props->padding.y1; bmargin = props->border.y2 + props->padding.y2; b->box = box; b->width = lmargin + box->width + rmargin; b->height = tmargin + box->height + bmargin; /* we go down until we find no floats or enough space to put the new float */ y = s->y0 + s->y; for(;;) { x1 = s->x0; x2 = s->x0 + s->total_width; y_next = MAXINT; for(b1 = s->layout_state->first_float; b1 != NULL; b1 = b1->next) { if (b1->float_type != -1) { /* if intersection in y, then update x1 and x2 */ y2 = b1->y + b1->height; if (!(y + b->height <= b1->y || y >= y2)) { if (b1->float_type == CSS_FLOAT_LEFT) x1 = max(x1, b1->x + b1->width); else x2 = min(x2, b1->x); /* compute minimum next y */ if (y2 < y_next) y_next = y2; } } } /* if enough space, then ok */ if (b->width <= (x2 - x1)) break; /* if no more boxes, we cannot do better by going down */ if (x1 == s->x0 && x2 == s->x0 + s->total_width) break; /* go down by the minimum amount of space to skip one box */ y = y_next; } /* compute box position */ if (b->float_type == CSS_FLOAT_LEFT) b->x = x1; else b->x = x2 - b->width; b->y = y; box->x = (b->x - s->x0) + lmargin; box->y = (b->y - s->y0) + tmargin; return 0; } /* layout all non layouted floats. Need to work that way because floats cannot be positionned while composing the current line */ static int css_layout_floats(InlineLayout *s) { FloatBlock *b; int ret; for(b = s->layout_state->first_float; b != NULL; b = b->next) { if (b->float_type == -1) { ret = css_layout_float(s, b); if (ret) return ret; } } return 0; } /* analyse the floating blocks and compute the available line size (fields y, xstart and avail_width are modified) */ static void css_prepare_line(InlineLayout *s, int clear_type) { LayoutState *l; FloatBlock *b, **pb; int x1, x2, y; /* layout all pending floats */ css_layout_floats(s); l = s->layout_state; /* special case handled separately for speed : no floats */ if (!l->first_float) { s->xstart = 0; s->avail_width = s->total_width; return; } x1 = s->x0; x2 = s->x0 + s->total_width; y = s->y0 + s->y; if (clear_type != CSS_CLEAR_NONE) { pb = &l->first_float; /* first we handle clear and suppress the box from the list */ while ((*pb) != NULL) { b = *pb; if (b->float_type != -1) { if (y >= b->y && y < b->y + b->height) { if (((clear_type & CSS_CLEAR_LEFT) && b->float_type == CSS_FLOAT_LEFT) || ((clear_type & CSS_CLEAR_RIGHT) && b->float_type == CSS_FLOAT_RIGHT)) { /* test if the box is in the rendering line */ if (!(b->x + b->height <= x1 || b->x >= x2)) { /* update y to go just after the cleared box */ y = max(y, (*pb)->y + b->height); } } } /* suppress from the float list if no longer active */ if (y >= b->y + b->height) { *pb = (*pb)->next; continue; } } pb = &(*pb)->next; } } /* then we handle the floats */ pb = &l->first_float; /* first we handle clear and suppress the box from the list */ while ((*pb) != NULL) { b = *pb; if (b->float_type != -1) { if (y >= b->y && y < b->y + b->height) { if (b->float_type == CSS_FLOAT_LEFT) { x1 = max(x1, b->x + b->width); } else { x2 = min(x2, b->x); } } /* suppress from the float list if no longer active */ if (y >= b->y + b->height) { *pb = (*pb)->next; continue; } } pb = &(*pb)->next; } /* compute x and y in local coordinates */ s->y = y - s->y0; s->xstart = x1 - s->x0; s->avail_width = x2 - x1; } /* do the layout of a line of boxes */ static void css_flush_line(InlineLayout *s, InlineBox *line_boxes, int nb_boxes, CSSState *line_props) { CSSBox *box; CSSState *props; int available_width, line_width, baseline, line_height, descent; int level_max, i, x, y, b, v, left_pad, right_pad; InlineBox *box_table, *box_table1, *ib; if (s->compute_min_max) { s->max_width = max(s->max_width, s->x); goto the_end; } available_width = s->avail_width; /* compute global line parameters */ line_width = 0; baseline = 0; descent = 0; level_max = 0; for(i=0;iprops; line_width += box->width; if (props->display != CSS_DISPLAY_INLINE) { line_width += props->margin.x1 + props->border.x1 + props->padding.x1 + props->padding.x2 + props->margin.x2 + props->border.x2; } /* adjust baseline */ b = line_boxes[i].ascent; v = line_boxes[i].baseline_delta; if (v < 0) b += v; if (b > baseline) baseline = b; /* adjust descent */ b = box->height - line_boxes[i].ascent; if (v > 0) b += v; if (b > descent) descent = b; if (box->embedding_level > level_max) level_max = box->embedding_level; } /* XXX: correct line_height behaviour (value normal, etc...) */ line_height = baseline + descent; if (line_props->line_height != CSS_AUTO) line_height = line_props->line_height; if (level_max > 0) { /* needed to do bidir reordering */ box_table = malloc(sizeof(InlineBox) * nb_boxes); if (box_table) { /* record the logical order of the boxes */ memcpy(box_table, line_boxes, nb_boxes * sizeof(InlineBox)); /* rearrange them to match visual order */ embed_boxes(box_table, nb_boxes, level_max); } } else { box_table = NULL; } if (box_table) box_table1 = box_table; else box_table1 = line_boxes; /* now do the real layout ! */ switch(line_props->text_align) { case CSS_TEXT_ALIGN_LEFT: default: x = 0; break; case CSS_TEXT_ALIGN_RIGHT: x = available_width - line_width; break; case CSS_TEXT_ALIGN_CENTER: x = (available_width - line_width) / 2; break; } if (x < 0) x = 0; x += s->xstart; y = s->y; for(i=0;ibox; props = box->props; if (props->display != CSS_DISPLAY_INLINE) { left_pad = props->padding.x1 + props->margin.x1 + props->border.x1; right_pad = props->margin.x2 + props->border.x2 + props->padding.x2; } else { left_pad = 0; right_pad = 0; } x += left_pad; box->x = x; switch(box->props->vertical_align) { case CSS_VERTICAL_ALIGN_TOP: box->y = y; break; case CSS_VERTICAL_ALIGN_BOTTOM: box->y = y + line_height - box->height; break; default: /* align to baseline */ box->y = y + baseline + ib->baseline_delta - ib->ascent; break; } /* put real ascent in box */ box->ascent = ib->ascent; /* correct borders */ if (props->display != CSS_DISPLAY_INLINE) { box->y += props->margin.y1 + props->border.y1 + props->padding.y1; } x += box->width; x += right_pad; } /* also align marker box if present */ box = s->marker_box; if (box) { box->y = y + baseline - s->marker_baseline; s->marker_box = NULL; } /* save baseline (for vertical align of table cells or markers) */ if (s->line_count == 0) s->first_line_baseline = baseline; s->line_count++; y += line_height; s->y = y; if (box_table) free(box_table); /* prepare for next line */ the_end: s->x = 0; s->line_pos = 0; s->char_pos = 0; s->index_bow = 0; s->offset_bow = 0; s->width_bow = 0; } static QEFont *css_select_font(QEditScreen *screen, CSSState *props) { int style; /* select the correct font */ style = 0; if (props->font_style == CSS_FONT_STYLE_ITALIC) style |= QE_STYLE_ITALIC; if (props->font_weight == CSS_FONT_WEIGHT_BOLD || props->font_weight == CSS_FONT_WEIGHT_BOLDER) style |= QE_STYLE_BOLD; if (props->text_decoration == CSS_TEXT_DECORATION_UNDERLINE) style |= QE_STYLE_UNDERLINE; else if (props->text_decoration == CSS_TEXT_DECORATION_LINE_THROUGH) style |= QE_STYLE_LINE_THROUGH; style |= props->font_family; return select_font(screen, style, props->font_size); } /* flush a text fragment. return non zero if the layout of the current box must be interrupted because a new line has been emitted */ static int css_flush_fragment(InlineLayout *s, CSSBox *box, CSSState *props, QEFont *font) { int w, line_size, split, ret, i, h; CSSBox *box_bow; QECharMetrics metrics; if (s->word_index == 0) return 0; /* flush the current word */ /* XXX: convert to glyphs */ text_metrics(s->ctx->screen, font, &metrics, s->word_buf, s->word_index); w = metrics.width; #if 0 { int i; printf("flush_word: '"); for(i=0;iword_index;i++) printf("%c", s->word_buf[i]); printf("' x=%d w=%d avail=%d white_space=%d word_index=%d\n", s->x, w, s->avail_width, props->white_space, s->word_index); } #endif if (s->compute_min_max) { /* min/max computation */ if (props->white_space == CSS_WHITE_SPACE_NORMAL) { /* XXX: not quite exact */ s->min_width = max(s->min_width, w); } s->x += w; if (props->white_space != CSS_WHITE_SPACE_NORMAL) s->min_width = max(s->min_width, s->x); ret = 0; } else if (props->white_space == CSS_WHITE_SPACE_PRE || props->white_space == CSS_WHITE_SPACE_NOWRAP || s->x + w <= s->avail_width || (props->white_space == CSS_WHITE_SPACE_NORMAL && s->index_bow == 0 && s->width_bow == 0)) { /* simple case : increment position */ s->x += w; box->width += w; /* update the box height info if specific word metrics are different from font metrics (can happen in case of font fallback). XXX: need further checks for ascent patch in line_boxes */ h = metrics.font_ascent + metrics.font_descent; if (h > box->height) box->height = h; if (metrics.font_ascent > s->line_boxes[s->line_pos - 1].ascent) s->line_boxes[s->line_pos - 1].ascent = metrics.font_ascent; ret = 0; } else { /* end of line reached */ if (props->white_space == CSS_WHITE_SPACE_PREWRAP) { /* try to put as much chars as possible on the line (maybe none) */ /* XXX: use dichotomy */ for(;;) { s->word_index--; text_metrics(s->ctx->screen, font, &metrics, s->word_buf, s->word_index); w = metrics.width; if (s->x + w <= s->avail_width) break; } /* put the end of the word on the next line */ box->width += w; h = metrics.font_ascent + metrics.font_descent; if (h > box->height) box->height = h; if (metrics.font_ascent > s->line_boxes[s->line_pos - 1].ascent) s->line_boxes[s->line_pos - 1].ascent = metrics.font_ascent; s->index_bow = s->line_pos - 1; s->offset_bow = s->word_offsets[s->word_index]; } else { /* correct the width of the box where the word starts (width_bow was stored at the beginning of the word) */ s->line_boxes[s->index_bow].box->width = s->width_bow; } /* split the box containing the start of the word, if needed */ box_bow = s->line_boxes[s->index_bow].box; if (s->offset_bow > box_bow->u.buffer.start) { /* split the box containing the start of the word */ css_box_split(box_bow, s->offset_bow); /* include the start of the splitted box */ split = 1; box_bow = box_bow->next; } else { split = 0; } /* push in the stack all remaining boxes for the next line */ if (s->box_stack_index < BOX_STACK_SIZE) s->box_stack[s->box_stack_index++] = box_bow; for(i=s->index_bow + 1;iline_pos;i++) { if (s->box_stack_index < BOX_STACK_SIZE) s->box_stack[s->box_stack_index++] = s->line_boxes[i].box; } /* do the line layout */ /* XXX: we do not use exactly the right properties. The CSS2 spec is not precise enough */ line_size = s->index_bow + split; css_flush_line(s, s->line_boxes, line_size, props); ret = 1; } s->word_index = 0; return ret; } /* XXX: find better values ? */ #define SUPER_PERCENT 80 #define SUB_PERCENT 40 /* Layout one inline box. Return the next box to layout (may be before 'box' in case of line cut) */ static int css_layout_inline_box(InlineLayout *s, CSSBox *box, int baseline) { CSSState *props = box->props; unsigned long offset, offset0; int ch, space, eob, ret, box_stack_base, i; QEFont *font; NextCharFunc nextc; if (s->ctx->abort_func(s->ctx->abort_opaque)) return -1; font = css_select_font(s->ctx->screen, props); if (!s->compute_min_max && box->parent && props->vertical_align != CSS_VERTICAL_ALIGN_BASELINE && props->vertical_align != CSS_VERTICAL_ALIGN_TOP && props->vertical_align != CSS_VERTICAL_ALIGN_BOTTOM) { int ascent, descent; QEFont *parent_font; parent_font = css_select_font(s->ctx->screen, box->parent->props); ascent = parent_font->ascent; descent = parent_font->descent; /* align vertically with respect to the parent */ switch(props->vertical_align) { case CSS_VERTICAL_ALIGN_SUPER: baseline -= (ascent * SUPER_PERCENT) / 100; break; case CSS_VERTICAL_ALIGN_SUB: baseline += (ascent * SUB_PERCENT) / 100; break; case CSS_VERTICAL_ALIGN_TEXT_TOP: baseline += font->ascent - ascent; break; case CSS_VERTICAL_ALIGN_TEXT_BOTTOM: baseline += descent - font->descent; break; case CSS_VERTICAL_ALIGN_MIDDLE: /* XXX: correct me */ baseline += (font->ascent + font->descent - ascent - descent) / 2; break; default: /* top and bottom are handled in the line layout */ break; } } if (props->display == CSS_DISPLAY_INLINE_TABLE || props->display == CSS_DISPLAY_INLINE_BLOCK) { int w, w1; LayoutOutput layout; /* if first box on the line, then compute available size by taking floats into account */ if (s->line_pos == 0 && !s->compute_min_max) { css_prepare_line(s, props->clear); } /* XXX: no pre like formatting handled here */ if (props->width == CSS_AUTO) { int min_w, max_w; /* layout to get the dimensions */ css_layout_block_min_max(s->ctx, &min_w, &max_w, box); w1 = max_w; } else { w1 = props->width; } w = props->margin.x1 + props->border.x1 + props->padding.x1 + w1 + props->padding.x2 + props->border.x2 + props->margin.x2; if (s->compute_min_max) { s->min_width = max(s->min_width, w); s->x += w; } else { box->width = w1; if (props->height != CSS_AUTO) { box->height = props->height; } else { /* we use the font width as default */ box->height = font->ascent + font->descent; } if (css_layout_block(s->ctx, &layout, box)) return -1; if (s->x + w <= s->avail_width || s->x == 0) { s->x += w; } else { /* flush line if we cannot put the image */ css_flush_line(s, s->line_boxes, s->line_pos, props); s->x += w; } /* add the box on the current line */ { InlineBox *b = &s->line_boxes[s->line_pos]; b->box = box; b->baseline_delta = 0; b->ascent = box->height + props->margin.y1 + props->border.y1 + props->padding.y1 + props->padding.y2 + props->border.y2 + props->margin.y2; } s->line_pos++; } s->char_pos++; s->last_space = 0; s->lastwordspace = -1; } else if (box->content_type == CSS_CONTENT_TYPE_CHILDS) { CSSBox *box1; box1 = box->u.child.first; while (box1 != NULL) { ret = css_layout_inline_box(s, box1, baseline); if (ret) return ret; box1 = box1->next; } } else { box_stack_base = s->box_stack_index; /* if first box on the line, then compute available size by taking floats into account */ if (s->line_pos == 0 && !s->compute_min_max) { css_prepare_line(s, props->clear); } nextc = get_nextc(box); offset = box->u.buffer.start; /* loop over each char of the box and separate into words */ eob = 0; ch = 0; /* not used */ space = 0; /* not used */ offset0 = 0; /* not used */ s->word_index = 0; if (!s->compute_min_max) { box->width = 0; box->last_space = s->last_space; /* compute vertical dimensions from the font parameters */ box->height = font->ascent + font->descent; /* add the box on the current line */ { InlineBox *b = &s->line_boxes[s->line_pos]; b->box = box; b->baseline_delta = baseline; b->ascent = font->ascent; } s->line_pos++; } for(;;) { /* end of block reached ? */ if (offset >= box->u.buffer.end) { css_flush_fragment(s, box, props, font); break; } else { /* get next char */ offset0 = offset; ch = nextc(s->ctx->b, &offset); /* special case: '\n' is handled in pre mode, or special '\A' character in css content property. */ if ((ch == '\n' && props->white_space == CSS_WHITE_SPACE_PRE) || ch == CSS_CONTENT_EOL) { s->last_space = 1; /* cannot split in PRE mode, so no handling necessary */ css_flush_fragment(s, box, props, font); /* split box if '\n' is not at the end */ if (offset < box->u.buffer.end) { css_box_split(box, offset); /* add the splitted box in the stack for next line */ if (s->box_stack_index < BOX_STACK_SIZE) s->box_stack[s->box_stack_index++] = box->next; } css_flush_line(s, s->line_boxes, s->line_pos, props); break; } /* special case for tabulation in pre mode */ if (ch == '\t' && (props->white_space == CSS_WHITE_SPACE_PRE || props->white_space == CSS_WHITE_SPACE_PREWRAP)) { int tab_width, split, w; /* XXX: should not wrap there */ css_flush_fragment(s, box, props, font); /* split box after tab */ split = 0; if (offset < box->u.buffer.end) { css_box_split(box, offset); /* add the splitted box in the stack for next line */ if (s->box_stack_index < BOX_STACK_SIZE) s->box_stack[s->box_stack_index++] = box->next; split = 1; } /* modify current position to take tab into account */ /* XXX: should compute that metrics before */ /* XXX: min/max compute ? */ tab_width = glyph_width(s->ctx->screen, font, ' ') * 8; w = (tab_width - (s->x % tab_width)); box->width += w; if (split) break; } space = css_is_space(ch); /* collapse spaces if needed */ if (space && s->last_space && (props->white_space == CSS_WHITE_SPACE_NORMAL || props->white_space == CSS_WHITE_SPACE_NOWRAP)) { continue; } if ((s->word_index >= MAX_WORD_SIZE) || (s->word_index >= 1 && space != s->last_space)) { if (css_flush_fragment(s, box, props, font)) break; } } /* memorize the beginning of the word (needed because the word may span several boxes) */ if (s->word_index == 0 && space != s->lastwordspace) { s->index_bow = s->line_pos - 1; s->offset_bow = offset0; s->width_bow = box->width; s->lastwordspace = space; } if (space) ch = ' '; s->word_buf[s->word_index] = ch; s->word_offsets[s->word_index] = offset0; s->word_index++; s->char_pos++; s->last_space = space; } /* end of the current box */ /* layout boxes at the end of the line if needed */ /* XXX: use another function */ for(i=box_stack_base; i < s->box_stack_index; i++) { css_layout_inline_box(s, s->box_stack[i], baseline); } /* remove all boxes from the stack */ s->box_stack_index = box_stack_base; } return 0; } static void css_start_inline_layout(InlineLayout *s) { s->x = 0; s->line_pos = 0; s->char_pos = 0; s->last_space = 1; s->lastwordspace = -1; s->index_bow = 0; s->offset_bow = 0; s->width_bow = 0; s->box_stack_index = 0; s->layout_type = LAYOUT_TYPE_INLINE; } static void css_end_inline_layout(InlineLayout *s) { /* do the last line layout if any boxes remain */ if (s->char_pos > 0) { /* XXX: we do not use exactly the right properties. The CSS2 spec is not precise enough */ css_flush_line(s, s->line_boxes, s->line_pos, s->line_pos > 0 ? s->line_boxes[0].box->props : NULL); } /* layout all remaining floats */ if (!s->compute_min_max) { css_layout_floats(s); s->marker_box = NULL; } s->layout_type = LAYOUT_TYPE_BLOCK; } /******************************************************************/ /* table layout */ typedef struct ColStruct { int width; /* column width, result from layout fixed or auto */ int width_fixed; /* auto layout only */ int min_width; int max_width; /* the following used only during render_table_row() */ CSSBox *cell; int baseline; int height; int vertical_align; int row_span_left; /* used for row span handling. If non zero, then a cell from a preceeding row is extending downwards. also used in auto layout */ int prev_row_height; /* used if row span to compute cell height */ } ColStruct; typedef struct TableLayout { CSSContext *ctx; /* min/max compute */ int compute_min_max; int min_width; int max_width; /* table border information */ int border_collapse, border_h, border_v; ColStruct *cols; int nb_cols; /* number of columns */ CSSBox *caption_box; /* TABLE_LAYOUT_COMPUTE_COL_FIXED state */ int nb_cols_allocated; /* currently allocate cols array size */ int column_index; /* index for use with table-column boxes */ /* TABLE_LAYOUT_RENDER state */ int y; /* when rendering, current y value */ int table_width; int row; } TableLayout; #define COL_INCR 10 /* allocate a new column */ static void allocate_column(TableLayout *s) { s->nb_cols++; if (s->nb_cols > s->nb_cols_allocated) { s->nb_cols_allocated = s->nb_cols_allocated + COL_INCR; s->cols = realloc(s->cols, s->nb_cols_allocated * sizeof(ColStruct)); memset(s->cols + s->nb_cols_allocated - COL_INCR, 0, COL_INCR * sizeof(ColStruct)); } } /* rounding rule for border which always gives correct sum : div2rnd(x, i) + div2rnd(x, i + 1) = x */ static inline int div2rnd(int x, int i) { return (x + (i & 1)) / 2; } /**************************************************************/ /* 'fixed' layout algorithm */ static void layout_table_row_fixed(TableLayout *s, CSSBox *row) { CSSBox *cell; CSSState *cell_props; int i, colspan, w; /* we only process the first row */ if (s->row != 0) return; for(cell = row->u.child.first; cell != NULL; cell = cell->next) { cell_props = cell->props; if (cell_props->display == CSS_DISPLAY_TABLE_CELL) { colspan = cell_props->column_span; if (colspan < 1) colspan = 1; for(i=0;iwidth != CSS_AUTO) { /* XXX: margins ? */ /* XXX: dispatch width */ w = cell_props->width / colspan; if (w < 1) w = 1; for(i=0;icols[s->nb_cols - colspan + i].width_fixed = 1; s->cols[s->nb_cols - colspan + i].width = max(w, s->cols[s->nb_cols - colspan + i].width); } } } } } static int layout_table_fixed(TableLayout *s, CSSBox *parent_box) { CSSState *props; CSSBox *box; /* get the first row and examine each column element */ box = parent_box->u.child.first; while (box != NULL) { props = box->props; switch(props->display) { case CSS_DISPLAY_TABLE_ROW: layout_table_row_fixed(s, box); s->row++; break; case CSS_DISPLAY_TABLE_ROW_GROUP: case CSS_DISPLAY_TABLE_HEADER_GROUP: case CSS_DISPLAY_TABLE_FOOTER_GROUP: if (layout_table_fixed(s, box)) return -1; break; case CSS_DISPLAY_TABLE_COLUMN_GROUP: if (layout_table_fixed(s, box)) return -1; break; case CSS_DISPLAY_TABLE_COLUMN: if (++s->column_index > s->nb_cols) allocate_column(s); if (props->width != CSS_AUTO) { /* XXX: margins ? */ s->cols[s->column_index - 1].width = max(s->cols[s->column_index - 1].width, props->width); s->cols[s->column_index - 1].width_fixed = 1; } break; default: break; } box = box->next; } return 0; } static int layout_table_fixed1(TableLayout *tl, CSSBox *table_box) { int nb_auto_cols, available_width, available_width1, i, j, tot_width; int cell_width; tl->nb_cols = 0; tl->nb_cols_allocated = 0; tl->cols = NULL; tl->column_index = 0; tl->row = 0; tl->table_width = table_box->props->width; if (layout_table_fixed(tl, table_box)) return -1; /* compute the column widths */ available_width = tl->table_width; available_width -= tl->border_h * (tl->nb_cols + 1); available_width1 = available_width; nb_auto_cols = 0; for(i=0;inb_cols;i++) { if (tl->cols[i].width_fixed) available_width -= tl->cols[i].width; else nb_auto_cols++; } if (nb_auto_cols > 0) { cell_width = available_width / nb_auto_cols; } else { cell_width = 0; } tot_width = 0; for(i=0;inb_cols;i++) { if (!tl->cols[i].width_fixed) tl->cols[i].width = cell_width; tot_width += tl->cols[i].width; } /* modify the widths so that their sum is exactly the table width */ j = 0; while (tot_width < available_width1) { while (j < tl->nb_cols && tl->cols[j].width_fixed) j++; if (j == tl->nb_cols) break; tl->cols[j].width++; tot_width++; j++; } /* the table width is fixed */ tl->min_width = tl->table_width; tl->max_width = tl->table_width; return 0; } /**************************************************************/ /* 'auto' layout algorithm */ static int layout_table_row_auto(TableLayout *s, CSSBox *row) { CSSBox *cell, *cell1; CSSState *props; CSSRect border; int i, colspan, min_w, max_w, col, col1, w, fixed; ColStruct *c; col = 0; cell1 = row->u.child.first; for(;;) { /* XXX: factorize with rendering step */ if (col < s->nb_cols) c = &s->cols[col]; else c = NULL; if (c && c->row_span_left != 0) { cell = c->cell; } else { cell = cell1; if (!cell) break; cell1 = cell1->next; if (cell->props->display != CSS_DISPLAY_TABLE_CELL) continue; } props = cell->props; colspan = props->column_span; if (colspan < 1) colspan = 1; if (c && c->row_span_left != 0) { c->row_span_left--; } else { /* increase number of column if needed */ col1 = col + colspan; for(i=s->nb_cols;ictx, &min_w, &max_w, cell) < 0) return -1; fixed = 1; if (props->width != CSS_AUTO) { min_w = max(props->width, min_w); max_w = max(props->width, max_w); fixed = 1; } /* take into account the padding & borders */ if (s->border_collapse == CSS_BORDER_COLLAPSE_SEPARATE) { border = props->border; } else { border.x1 = div2rnd(props->border.x1, col); border.x2 = div2rnd(props->border.x2, col + 1); } w = props->padding.x1 + props->padding.x2 + border.x1 + border.x2; min_w += w; max_w += w; c = &s->cols[col]; /* handle row span */ if (props->row_span > 1) { c->row_span_left = props->row_span - 1; c->cell = cell; } /* XXX: margins ? */ if (colspan == 1) { /* simple case: just increase widths */ c->min_width = max(c->min_width, min_w); c->max_width = max(c->max_width, max_w); c->width_fixed = fixed; } else { int min_w1, max_w1, delta, d, r; /* increase min & max widths so that they are at least larger enough for the cell */ min_w1 = 0; max_w1 = 0; for(i=0;iwidth_fixed = fixed; min_w1 += c->min_width; max_w1 += c->max_width; c++; } delta = min_w - min_w1; if (delta > 0) { d = delta / colspan; r = delta % colspan; c = &s->cols[col]; for(i=0;imin_width += d; if (i < r) c->min_width++; c++; } } delta = max_w - max_w1; if (delta > 0) { d = delta / colspan; r = delta % colspan; c = &s->cols[col]; for(i=0;imax_width += d; if (i < r) c->max_width++; c++; } } } } col += colspan; } return 0; } static int layout_table_auto(TableLayout *s, CSSBox *parent_box) { CSSState *props; CSSBox *box; /* get the first row and examine each column element */ box = parent_box->u.child.first; while (box != NULL) { props = box->props; switch(props->display) { case CSS_DISPLAY_TABLE_ROW: if (layout_table_row_auto(s, box) < 0) return -1; s->row++; break; case CSS_DISPLAY_TABLE_ROW_GROUP: case CSS_DISPLAY_TABLE_HEADER_GROUP: case CSS_DISPLAY_TABLE_FOOTER_GROUP: if (layout_table_auto(s, box)) return -1; break; case CSS_DISPLAY_TABLE_COLUMN_GROUP: /* XXX: do it */ break; case CSS_DISPLAY_TABLE_COLUMN: /* XXX: do it */ break; default: break; } box = box->next; } return 0; } static int layout_table_auto1(TableLayout *s, CSSBox *table_box) { int min_tw, max_tw, tw, i, delta, d, r; ColStruct *c; s->nb_cols = 0; s->nb_cols_allocated = 0; s->cols = NULL; s->column_index = 0; s->row = 0; if (layout_table_auto(s, table_box)) return -1; /* now we have min_width and max_width for each column */ /* compute the minimum and maximum table width */ min_tw = s->border_h * (s->nb_cols + 1); max_tw = min_tw; for(i = 0, c = s->cols; i < s->nb_cols; i++, c++) { min_tw += c->min_width; max_tw += c->max_width; } if (s->compute_min_max) { if (table_box->props->width == CSS_AUTO) { s->min_width = min_tw; s->max_width = max_tw; } else { tw = max(min_tw, table_box->props->width); s->min_width = tw; s->max_width = tw; } return 0; } /* compute the table width */ if (table_box->props->width == CSS_AUTO) { /* NOTE: in this case, table_box->width is the available width in the containing box */ if (max_tw < table_box->width) { tw = max_tw; } else { tw = max(min_tw, table_box->width); } } else { /* fixed table width */ tw = max(min_tw, table_box->props->width); } /* compute the cell widths from the computed table width */ if (tw < max_tw) { for(i = 0, c = s->cols; i < s->nb_cols; i++, c++) c->width = c->min_width; delta = tw - min_tw; } else { for(i = 0, c = s->cols; i < s->nb_cols; i++, c++) c->width = c->max_width; delta = tw - max_tw; } if (delta > 0) { /* well, could do better (use max_width ?), but it works and it is fast */ d = delta / s->nb_cols; r = delta % s->nb_cols; for(i = 0, c = s->cols; i < s->nb_cols; i++, c++) { c->width += d; if (i < r) c->width++; } } /* init again row span left, in case the row spans were incorrect */ for(i = 0, c = s->cols; i < s->nb_cols; i++, c++) c->row_span_left = 0; s->table_width = tw; table_box->width = tw; return 0; } /**************************************************************/ /* table rendering */ /* we need this function because the default align is baseline */ static inline int is_valign_baseline(int v) { return (v != CSS_VERTICAL_ALIGN_TOP && v != CSS_VERTICAL_ALIGN_BOTTOM && v != CSS_VERTICAL_ALIGN_MIDDLE); } static int render_table_row(TableLayout *s, CSSBox *row) { LayoutOutput layout; int x, row_height, col, nb_cols, w, h, baseline, colspan, i; CSSBox *cell, *cell1; CSSState *props; CSSRect border; /* compute horizontal position of cells */ x = 0; col = 0; baseline = 0; cell1 = row->u.child.first; col = 0; for(;;) { ColStruct *c = &s->cols[col]; /* compute which cell we examine */ if (c->row_span_left != 0) { cell = c->cell; } else { cell = cell1; if (!cell) break; cell1 = cell1->next; if (cell->props->display != CSS_DISPLAY_TABLE_CELL) continue; } props = cell->props; colspan = props->column_span; if (colspan < 1) colspan = 1; if (col + colspan > s->nb_cols) colspan = s->nb_cols - col; if (colspan <= 0) break; if (c->row_span_left != 0) { c->row_span_left--; /* modify x to skip column */ for(i=0;iborder_h + s->cols[col + i].width; } else { c->cell = cell; if (s->border_collapse == CSS_BORDER_COLLAPSE_SEPARATE) { border = props->border; } else { border.x1 = div2rnd(props->border.x1, col); border.x2 = div2rnd(props->border.x2, col + 1); border.y1 = div2rnd(props->border.x1, s->row); border.y2 = div2rnd(props->border.x2, s->row + 1); } /* set the cell width */ w = s->cols[col].width; for(i=1;iborder_h + s->cols[col + i].width; cell->width = w - props->padding.x1 - props->padding.x2 - border.x1 - border.x2; if (css_layout_block(s->ctx, &layout, cell)) return -1; c->height = cell->height + border.y1 + border.y2 + props->padding.y1 + props->padding.y2; /* init row span counter */ if (props->row_span > 1) c->row_span_left = props->row_span - 1; c->prev_row_height = 0; cell->x = x + s->border_h + border.x1 + props->padding.x1; x += w + s->border_h; /* align to top */ cell->y = border.y1 + props->padding.y1; /* compute baseline of the row */ c->vertical_align = props->vertical_align; if (is_valign_baseline(c->vertical_align)) { baseline = max(baseline, layout.baseline); c->baseline = layout.baseline; } } col += colspan; } nb_cols = col; /* now compute vertical position of cells if baseline and compute row_height */ row_height = 0; for(col = 0; col < nb_cols;) { ColStruct *c = &s->cols[col]; cell = c->cell; colspan = cell->props->column_span; if (colspan < 1) colspan = 1; if (c->row_span_left == 0) { h = c->height; /* align to baseline if needed */ if (h > 0 && is_valign_baseline(c->vertical_align)) { int delta = baseline - c->baseline; cell = c->cell; h += delta; cell->padding_top = delta; cell->y += delta; } if (cell->props->height != CSS_AUTO) h = max(h, cell->props->height); row_height = max(h - c->prev_row_height, row_height); } col += colspan; } if (row->props->height != CSS_AUTO) { row_height = max(row_height, row->props->height); } /* we know row_height: definitive vertical positionning */ for(col = 0; col < nb_cols;) { ColStruct *c = &s->cols[col]; int delta, cell_height; cell = c->cell; cell_height = c->prev_row_height + row_height; if (c->row_span_left == 0) { h = c->height; switch(c->vertical_align) { case CSS_VERTICAL_ALIGN_BOTTOM: delta = cell_height - c->height; cell->padding_top = delta; cell->y += delta; break; case CSS_VERTICAL_ALIGN_MIDDLE: delta = (cell_height - c->height) / 2; cell->padding_top = delta; cell->y += delta; break; default: break; } /* compute bottom padding so that we fill the row height */ cell->padding_bottom = cell_height - (h + cell->padding_top); } else { c->prev_row_height = s->border_v + cell_height; } colspan = cell->props->column_span; if (colspan < 1) colspan = 1; col += colspan; } row->width = s->table_width; row->height = row_height + s->border_v; row->x = 0; row->y = s->y + s->border_v; s->y += row->height; return 0; } static int layout_table_render(TableLayout *s, CSSBox *parent_box) { CSSState props1, *props = &props1; CSSBox *box; /* get the first row and examine each column element */ box = parent_box->u.child.first; while (box != NULL) { props = box->props; switch(props->display) { case CSS_DISPLAY_TABLE_ROW: if (render_table_row(s, box)) return -1; s->row++; break; case CSS_DISPLAY_TABLE_ROW_GROUP: case CSS_DISPLAY_TABLE_HEADER_GROUP: case CSS_DISPLAY_TABLE_FOOTER_GROUP: if (layout_table_render(s, box)) return -1; break; case CSS_DISPLAY_TABLE_CAPTION: /* save first caption box */ if (!s->caption_box) s->caption_box = box; break; case CSS_DISPLAY_TABLE_COLUMN_GROUP: case CSS_DISPLAY_TABLE_COLUMN: default: break; } box = box->next; } return 0; } /* * layout a table box. if min_max_compute is true, then only compute min/max info */ static int css_layout_table(CSSContext *s, LayoutOutput *table_layout, CSSBox *table_box, int compute_min_max) { TableLayout tl1, *tl = &tl1; CSSState *table_props = table_box->props; CSSBox *caption_box; tl->ctx = s; tl->border_collapse = table_props->border_collapse; tl->compute_min_max = compute_min_max; if (tl->border_collapse == CSS_BORDER_COLLAPSE_SEPARATE) { tl->border_h = table_props->border_spacing_horizontal; tl->border_v = table_props->border_spacing_vertical; } else { tl->border_h = 0; tl->border_v = 0; } table_layout->margin_top = table_props->margin.y1; table_layout->margin_bottom = table_props->margin.y2; if (table_props->table_layout == CSS_TABLE_LAYOUT_FIXED && table_props->width != CSS_AUTO) { /* fixed algorithm */ if (layout_table_fixed1(tl, table_box)) goto fail; } else { /* fixed algorithm */ if (layout_table_auto1(tl, table_box)) goto fail; } if (tl->compute_min_max) { table_layout->min_width = tl->min_width; table_layout->max_width = tl->max_width; return 0; } /* now we are ready to layout the table ! */ tl->row = 0; tl->y = 0; tl->caption_box = NULL; if (layout_table_render(tl, table_box)) { fail: free(tl->cols); return -1; } tl->y += tl->border_v; /* compute total table height */ table_box->height = max(tl->y, table_box->height); free(tl->cols); /* handle table caption */ caption_box = tl->caption_box; if (caption_box) { LayoutOutput caption_layout; int h; if (caption_box->props->width != CSS_AUTO) { caption_box->width = caption_box->props->width; } else if (caption_box->props->caption_side == CSS_CAPTION_SIDE_TOP || caption_box->props->caption_side == CSS_CAPTION_SIDE_BOTTOM) { caption_box->width = table_box->width; } else { /* use a default width */ /* XXX: find a better guess !! */ caption_box->width = 100; } if (caption_box->props->caption_side == CSS_CAPTION_SIDE_LEFT || caption_box->props->caption_side == CSS_CAPTION_SIDE_RIGHT) { caption_box->height = table_box->height; } if (css_layout_block(s, &caption_layout, caption_box)) return -1; h = caption_box->height + caption_box->props->border.y1 + caption_box->props->padding.y1 + caption_box->props->border.y2 + caption_box->props->padding.y2; /* put the caption in the table margin */ /* XXX: potentially incorrect: table margins */ switch(caption_box->props->caption_side) { case CSS_CAPTION_SIDE_TOP: caption_box->x = (table_box->width - caption_box->width) / 2; caption_box->y = -h; if (table_layout->margin_top < h) table_layout->margin_top = h; break; case CSS_CAPTION_SIDE_BOTTOM: caption_box->x = (table_box->width - caption_box->width) / 2; caption_box->y = table_box->height; if (table_layout->margin_bottom < h) table_layout->margin_bottom = h; break; case CSS_CAPTION_SIDE_RIGHT: caption_box->x = table_box->width; caption_box->y = 0; break; case CSS_CAPTION_SIDE_LEFT: caption_box->x = -caption_box->width; caption_box->y = 0; break; } } return 0; } /* add a float. Its layout is postponed until a start of line in an inline context is reached */ static int css_add_float(InlineLayout *s, CSSBox *box) { FloatBlock *b, **pb; b = malloc(sizeof(FloatBlock)); if (!b) return 0; b->box = box; b->float_type = -1; b->next = NULL; /* add the float at the end of the list */ pb = &s->layout_state->first_float; while (*pb != NULL) pb = &(*pb)->next; *pb = b; return 0; } static void css_free_floats(FloatBlock *b) { FloatBlock *b1; while (b != NULL) { b1 = b->next; free(b); b = b1; } } static int css_layout_block_recurse(LayoutState *s, LayoutOutput *block_layout, CSSBox *block_box, int x_parent, int y_parent); static int css_layout_block_recurse1(InlineLayout *il, CSSBox *box, int baseline); static int css_layout_block_iterate(InlineLayout *il, CSSBox *box, int baseline) { CSSBox *box1, *box2; int ret; for(box1 = box->u.child.first; box1 != NULL; box1 = box2) { box2 = box1->next; /* need to do that first because boxes may be split */ ret = css_layout_block_recurse1(il, box1, baseline); if (ret) return ret; } return 0; } /* layout one box in an inline or block context */ static int css_layout_block_recurse1(InlineLayout *il, CSSBox *box, int baseline) { CSSState *props; int ret, ymargin; LayoutOutput layout; if (il->ctx->abort_func(il->ctx->abort_opaque)) return -1; props = box->props; if (props->position == CSS_POSITION_ABSOLUTE || props->position == CSS_POSITION_FIXED) { /* XXX: fixed is not handled */ if (props->display != CSS_DISPLAY_NONE) { int min_w, w; if (props->width == CSS_AUTO) { css_layout_block_min_max(il->ctx, &min_w, &w, box); } else { w = props->width; } box->width = w; box->height = 0; if (css_layout_block(il->ctx, &layout, box)) return -1; if (props->left != CSS_AUTO) box->x = props->left; else if (props->right != CSS_AUTO) box->x = il->total_width - box->width - props->right; if (props->top != CSS_AUTO) box->y = props->top; #if 0 /* XXX: must put it in a list as float to have box height */ else if (props->bottom != CSS_AUTO) box->y = props->bottom; #endif } } else if (props->block_float != CSS_FLOAT_NONE) { /* float formatting : display is ignored except if set to none */ if (props->display != CSS_DISPLAY_NONE) { if (css_add_float(il, box)) return -1; } } else { /* now do the layout, according to the display type */ switch(props->display) { case CSS_DISPLAY_LIST_ITEM: case CSS_DISPLAY_BLOCK: case CSS_DISPLAY_TABLE: if (il->layout_type != LAYOUT_TYPE_BLOCK) { css_end_inline_layout(il); il->last_ymargin = 0; } il->marker_box = NULL; /* the marker was already positionned correctly */ if (props->width == CSS_AUTO) { int w; w = props->padding.x1 + props->padding.x2 + props->border.x1 + props->border.x2; if (props->margin.x1 != CSS_AUTO) w += props->margin.x1; if (props->margin.x2 != CSS_AUTO) w += props->margin.x2; box->width = il->total_width - w; } else { box->width = props->width; } /* position the box before so that we can pass x_parent and y_parent */ if (props->margin.x1 == CSS_AUTO && props->margin.x2 == CSS_AUTO) { int w; w = props->border.x1 + props->padding.x1 + box->width + props->padding.x2 + props->border.x2; box->x = (il->total_width - w) / 2; } else if (props->direction == CSS_DIRECTION_LTR) { box->x = props->margin.x1 + props->border.x1 + props->padding.x1; } else { box->x = il->total_width - (props->margin.x2 + props->border.x2 + props->padding.x2 + box->width); } /* XXX: compute y position there, but difficult to do because we do not have the complete margin info */ if (props->height == CSS_AUTO) { box->height = 0; /* will be extended later */ } else { box->height = props->height; } /* XXX: y_parent does not take into account margins ! */ if (css_layout_block_recurse(il->layout_state, &layout, box, il->x0 + box->x, il->y0 + il->y + props->border.y1 + props->padding.y1)) return -1; /* compute the margin */ if (il->is_first_box) { il->margin_top = max(il->margin_top, layout.margin_top); ymargin = 0; /* the margin is taken into account by the parent block */ } else { ymargin = max(il->last_ymargin, layout.margin_top); } il->last_ymargin = layout.margin_bottom; /* compute the box position */ box->y = il->y + ymargin + props->border.y1 + props->padding.y1; box->padding_top = 0; box->padding_bottom = 0; /* update position for the next box */ il->y = box->y + box->height + props->border.y2 + props->padding.y2; /* apply relative offset if specified */ if (props->position == CSS_POSITION_RELATIVE) { if (props->left != CSS_AUTO) box->x += props->left; else if (props->right != CSS_AUTO) box->x -= props->right; if (props->top != CSS_AUTO) box->y += props->top; else if (props->bottom != CSS_AUTO) box->y -= props->bottom; } break; case CSS_DISPLAY_MARKER: /* marker is put in the left margin of block_box */ { int min_w, w, offset; /* compute marker width */ if (props->width == CSS_AUTO) { css_layout_block_min_max(il->ctx, &min_w, &w, box); } else { w = props->width; } box->width = w; box->height = 0; if (css_layout_block(il->ctx, &layout, box)) return -1; /* put it in the margin */ offset = props->marker_offset; if (offset == CSS_AUTO) { if (il->ctx->media == CSS_MEDIA_TTY) offset = 1; else offset = 8; /* XXX: add constant */ } w = props->margin.x1 + props->border.x1 + props->padding.x1 + w + props->margin.x2 + props->border.x2 + props->padding.x2; box->x -= (w + offset); /* margin is also taken into account */ box->y = il->y + props->border.y1 + props->padding.y1 + layout.margin_top; /* note: the marker Y position can be modified if an inline context comes just after */ il->marker_box = box; il->marker_baseline = layout.baseline; } break; case CSS_DISPLAY_INLINE: case CSS_DISPLAY_INLINE_TABLE: case CSS_DISPLAY_INLINE_BLOCK: if (il->layout_type != LAYOUT_TYPE_INLINE) css_start_inline_layout(il); /* from this box, we are in an inline layout context */ if (props->display != CSS_DISPLAY_INLINE_TABLE && props->display != CSS_DISPLAY_INLINE_BLOCK && box->content_type == CSS_CONTENT_TYPE_CHILDS) { css_layout_block_iterate(il, box, baseline); } else { ret = css_layout_inline_box(il, box, baseline); if (ret) return ret; } break; default: dprintf("Unexpected display type : %d\n", props->display); break; case CSS_DISPLAY_NONE: break; } } il->is_first_box = 0; return 0; } /* layout the interior of box 'block_box'. 'block_box->width' and 'block_box->height' must have reasonnable values before calling this function. If height == 0, then the box is extended as needed. x_parent and y_parent are the absolute coordinates of block_box. They are needed only in case of floats blocks. */ static int css_layout_block_recurse(LayoutState *s, LayoutOutput *block_layout, CSSBox *block_box, int x_parent, int y_parent) { CSSBox *box; CSSState *block_props; InlineLayout inline_layout, *il = &inline_layout; int ret; block_props = block_box->props; #if 0 { printf("layout %s", css_ident_str(block_box->tag)); if (block_box->content_type == CSS_CONTENT_TYPE_BUFFER) printf(" offset=%ld", block_box->u.buffer.start); printf("\n"); } #endif /* handle simple cases here */ if (block_props->display == CSS_DISPLAY_TABLE || block_props->display == CSS_DISPLAY_INLINE_TABLE) { if (block_props->height == CSS_AUTO) { block_box->height = 0; /* will be extended later */ } else { block_box->height = block_props->height; } if (css_layout_table(s->ctx, block_layout, block_box, 0)) return -1; return 0; } if (block_box->content_type == CSS_CONTENT_TYPE_IMAGE) { /* nothing more to do */ return 0; } /* put initial values for margins */ block_layout->margin_top = block_props->margin.y1; block_layout->margin_bottom = block_props->margin.y2; box = block_box->u.child.first; if (!box) { /* empty block, cannot do more */ if (block_props->height != CSS_AUTO) block_box->height = block_props->height; return 0; } /* a subset of the inline layout is also used for the block layout */ il->ctx = s->ctx; il->compute_min_max = 0; il->layout_state = s; il->y = 0; il->x0 = x_parent; il->y0 = y_parent; il->total_width = block_box->width; il->last_ymargin = 0; /* not used */ il->is_first_box = 1; il->margin_top = block_layout->margin_top; il->marker_box = NULL; il->layout_type = LAYOUT_TYPE_BLOCK; il->first_line_baseline = 0; il->line_count = 0; ret = css_layout_block_iterate(il, block_box, 0); if (ret) return ret; /* start block layout to flush last line */ if (il->layout_type != LAYOUT_TYPE_BLOCK) css_end_inline_layout(il); /* update the bottom margin of the whole block */ block_layout->margin_top = il->margin_top; block_layout->margin_bottom = max(block_layout->margin_bottom, il->last_ymargin); block_layout->baseline = il->first_line_baseline; /* update the block height if necessary (XXX: incorrect if not auto) */ if (il->y > block_box->height) block_box->height = il->y; return 0; } /* layout the interior of box 'block_box'. 'block_box->width' and 'block_box->height' must have reasonnable values before calling this function. If height == 0, then the box is extended as needed. no floating boxes are initially registered. */ static int css_layout_block(CSSContext *s, LayoutOutput *block_layout, CSSBox *block_box) { LayoutState layout_state; int ret; layout_state.ctx = s; layout_state.first_float = NULL; ret = css_layout_block_recurse(&layout_state, block_layout, block_box, 0, 0); css_free_floats(layout_state.first_float); return ret; } /* min/max layout */ static int css_layout_box_min_max(InlineLayout *il, CSSBox *box) { CSSState *props = box->props; int w, min_w1, max_w1, ret; if (il->ctx->abort_func(il->ctx->abort_opaque)) return -1; if (props->position == CSS_POSITION_ABSOLUTE || props->position == CSS_POSITION_FIXED) { /* ignore this box */ } else if (props->block_float != CSS_FLOAT_NONE) { /* floating boxes : we ensure that we can put them on a line */ if (props->display != CSS_DISPLAY_NONE) { if (props->width != CSS_AUTO) { min_w1 = props->width; max_w1 = props->width; } else { css_layout_block_min_max(il->ctx, &min_w1, &max_w1, box); } il->min_width = max(il->min_width, min_w1); /* XXX: add to max width ? */ il->max_width = max(il->max_width, max_w1); } } else { switch(props->display) { case CSS_DISPLAY_LIST_ITEM: case CSS_DISPLAY_BLOCK: case CSS_DISPLAY_TABLE: if (il->layout_type != LAYOUT_TYPE_BLOCK) css_end_inline_layout(il); if (props->width != CSS_AUTO && props->display != CSS_DISPLAY_TABLE) { min_w1 = props->width; max_w1 = props->width; } else { /* look at the block inside */ css_layout_block_min_max(il->ctx, &min_w1, &max_w1, box); } w = props->padding.x1 + props->padding.x2 + props->border.x1 + props->border.x2; if (props->margin.x1 != CSS_AUTO) w += props->margin.x1; if (props->margin.x2 != CSS_AUTO) w += props->margin.x2; il->min_width = max(il->min_width, min_w1 + w); il->max_width = max(il->max_width, max_w1 + w); break; case CSS_DISPLAY_INLINE: case CSS_DISPLAY_INLINE_TABLE: case CSS_DISPLAY_INLINE_BLOCK: if (il->layout_type != LAYOUT_TYPE_INLINE) css_start_inline_layout(il); if (props->display != CSS_DISPLAY_INLINE_TABLE && props->display != CSS_DISPLAY_INLINE_BLOCK && box->content_type == CSS_CONTENT_TYPE_CHILDS) { CSSBox *box1, *box2; for(box1 = box->u.child.first; box1 != NULL; box1 = box2) { box2 = box1->next; /* need to do that first because boxes may be split */ ret = css_layout_box_min_max(il, box1); if (ret) return ret; } } else { ret = css_layout_inline_box(il, box, 0); if (ret) return ret; } break; } } return 0; } /* compute the minimum and maximum width of the content of a block */ static int css_layout_block_min_max(CSSContext *s, int *min_width_ptr, int *max_width_ptr, CSSBox *block_box) { CSSBox *box, *box1; LayoutOutput layout; InlineLayout inline_layout, *il = &inline_layout; int ret; if (block_box->props->display == CSS_DISPLAY_TABLE || block_box->props->display == CSS_DISPLAY_INLINE_TABLE) { if (css_layout_table(s, &layout, block_box, 1)) return -1; *min_width_ptr = layout.min_width; *max_width_ptr = layout.max_width; return 0; } il->ctx = s; il->compute_min_max = 1; il->min_width = 0; il->max_width = 0; il->layout_type = LAYOUT_TYPE_BLOCK; box = block_box->u.child.first; if (!box) { /* empty block, cannot do more */ if (block_box->props->width != CSS_AUTO) il->min_width = il->max_width = block_box->props->width; } else { for(;box != NULL; box = box1) { box1 = box->next; ret = css_layout_box_min_max(il, box); if (ret) return ret; } } /* start block layout to flush last line */ if (il->layout_type != LAYOUT_TYPE_BLOCK) css_end_inline_layout(il); *min_width_ptr = il->min_width; *max_width_ptr = il->max_width; return 0; } /* bounding box extraction. get document extends & global background infos. Also translate all relative coordinates into absolute ones. XXX: use absolute coordinates in the whole layout. */ static void css_compute_bbox_block(CSSContext *s, CSSBox *box, int x_parent, int y_parent) { CSSBox *tt; CSSState *props = box->props; int x0, y0; if (props->visibility == CSS_VISIBILITY_HIDDEN) { css_set_rect(&box->bbox, 0, 0, 0, 0); return; } x0 = box->x; y0 = box->y; /* convert to absolute position if needed */ if (!box->absolute_pos) { x0 += x_parent; y0 += y_parent; box->x = x0; box->y = y0; } /* update bounding box */ css_set_rect(&box->bbox, x0 - (props->padding.x1 + props->border.x1), y0 - (props->padding.y1 + box->padding_top + props->border.y1), x0 + box->width + props->padding.x2 + props->border.x2, y0 + box->height + (props->padding.y2 + box->padding_bottom + props->border.y2)); /* now display the content ! */ if (box->content_type == CSS_CONTENT_TYPE_CHILDS) { /* other boxes are inside: display them */ tt = box->u.child.first; while (tt) { css_compute_bbox_block(s, tt, x0, y0); css_union_rect(&box->bbox, &tt->bbox); tt = tt->next; } } } /* main css layout function. Return non zero if interrupted */ int css_layout(CSSContext *s, CSSBox *box, int width, CSSAbortFunc abort_func, void *abort_opaque) { LayoutOutput layout; int ret; s->abort_func = abort_func; s->abort_opaque = abort_opaque; /* bidi compute */ ret = css_layout_bidir_block(s, box); if (ret) return ret; /* layout */ box->width = width; ret = css_layout_block(s, &layout, box); if (ret) return ret; // css_dump(box); /* compute the bbox of all non hidden boxes */ css_compute_bbox_block(s, box, 0, 0); return 0; } /* display utils */ #define MAX_LINE_SIZE 256 int box_get_text(CSSContext *s, unsigned int *line_buf, int max_size, int *offsets, CSSBox *box) { /* final box with text inside */ unsigned long offset, offset0; unsigned int *q; int c, space_collapse, last_space, space; NextCharFunc nextc; CSSState *props = box->props; nextc = get_nextc(box); space_collapse = (props->white_space == CSS_WHITE_SPACE_NORMAL || props->white_space == CSS_WHITE_SPACE_NOWRAP); q = line_buf; offset = box->u.buffer.start; last_space = box->last_space; while (offset < box->u.buffer.end) { offset0 = offset; c = nextc(s->b, &offset); if (c == CSS_CONTENT_EOL) continue; space = css_is_space(c); if (space_collapse) { if (last_space && space) continue; last_space = space; } if (space) c = ' '; if ((q - line_buf) < max_size) { if (offsets) offsets[q - line_buf] = offset0; *q++ = c; } } return q - line_buf; } #define BFRAC 16 /* draw nice borders */ static void draw_borders(QEditScreen *scr, int x1, int y1, int x2, int y2, CSSState *props) { int dir, i, t1, t2, u1, v1, u2, v2, u1incr, u2incr, style, w, v; unsigned int color, color1, color2; for(dir=0;dir<4;dir++) { style = props->border_styles[dir]; if (style == CSS_BORDER_STYLE_NONE || style == CSS_BORDER_STYLE_HIDDEN) continue; color1 = props->border_colors[dir]; if (style == CSS_BORDER_STYLE_GROOVE || style == CSS_BORDER_STYLE_RIDGE || style == CSS_BORDER_STYLE_INSET || style == CSS_BORDER_STYLE_OUTSET) { /* XXX: better formula ? use luminance ? */ unsigned int r, g, b, a; a = (color1 >> 24) & 0xff; r = (color1 >> 16) & 0xff; g = (color1 >> 8) & 0xff; b = (color1) & 0xff; r = min(r + 128, 255); g = min(g + 128, 255); b = min(b + 128, 255); color2 = (a << 24) | (r << 16) | (g << 8) | b; if ((style == CSS_BORDER_STYLE_INSET && dir >= 2) || (style == CSS_BORDER_STYLE_OUTSET && dir < 2)) color1 = color2; } else { color2 = color1; } w = ((int *)&props->border)[dir]; if (w <= 0) continue; switch(dir) { case 2: case 0: u1 = y1; u2 = y2; v1 = y1 - props->border.y1; v2 = y2 + props->border.y2; break; default: u1 = x1; u2 = x2; v1 = x1 - props->border.x1; v2 = x2 + props->border.x2; break; } u1incr = ((v1 - u1) << BFRAC) / w; u2incr = ((v2 - u2) << BFRAC) / w; u1 = u1 << BFRAC; u2 = u2 << BFRAC; for(i=0;i= 2) v = 1 - v; if (w > 1 && ((i * 2) / w) == v) { color = color1; } else { color = color2; } break; default: continue; } t1 = u1 >> BFRAC; t2 = u2 >> BFRAC; switch(dir) { case 0: fill_rectangle(scr, x1 - i - 1, t1, 1, t2 - t1, color); break; case 1: fill_rectangle(scr, t1, y1 - i - 1, t2 - t1, 1, color); break; case 2: fill_rectangle(scr, x2 + i, t1, 1, t2 - t1, color); break; default: fill_rectangle(scr, t1, y2 + i, t2 - t1, 1, color); break; } } } } static void box_display_text(CSSContext *s, CSSBox *box, int x0, int y0) { CSSState *props = box->props; QEditScreen *scr = s->screen; unsigned int line_buf[MAX_LINE_SIZE]; unsigned int glyphs[MAX_LINE_SIZE]; int offsets[MAX_LINE_SIZE + 1], *offsets_ptr; unsigned int char_to_glyph_pos[MAX_LINE_SIZE], *ctg_ptr; int len, x, i, p, offset0, len1, w; QEFont *font; CSSColor color; if #if 0 (s->selection_end > s->selection_start && box->content_type == CSS_CONTENT_TYPE_BUFFER && !(box->u.buffer.end <= s->selection_start || box->u.buffer.start >= s->selection_end)) #else (box->content_type == CSS_CONTENT_TYPE_BUFFER) #endif { /* more complicated selection highlighting */ offsets_ptr = offsets; ctg_ptr = char_to_glyph_pos; } else { offsets_ptr = NULL; ctg_ptr = NULL; } len1 = box_get_text(s, line_buf, MAX_LINE_SIZE, offsets_ptr, box); len = unicode_to_glyphs(glyphs, ctg_ptr, MAX_LINE_SIZE, line_buf, len1, box->embedding_level & 1); if (len > 0) { font = css_select_font(scr, props); if (!offsets_ptr) { /* fast code */ draw_text(scr, font, x0, y0 + box->ascent, glyphs, len, props->color); } else { /* slower and more complicated code for selection handling */ offsets[len1] = box->u.buffer.end; x = x0; for(i=0;i= s->selection_start && offset0 < s->selection_end) { color = s->selection_bgcolor; fill_rectangle(scr, x, y0, w, font->ascent + font->descent, color); } x += w; } x = x0; for(i=0;i= s->selection_start && offset0 < s->selection_end) color = s->selection_fgcolor; else color = props->color; draw_text(scr, font, x, y0 + box->ascent, glyphs + i, 1, color); w = glyph_width(scr, font, glyphs[i]); x += w; } } } } /* display an image, currently, only show a border and the alt string */ #define ALT_TEXT_PADDING 3 static void box_display_image(CSSContext *s, CSSBox *box, int x0, int y0) { QEditScreen *scr = s->screen; CSSState *props = box->props; CSSState img_props; QEFont *font; int i, len; unsigned int ubuf[256]; if (s->media != CSS_MEDIA_TTY) { /* draw something inside the image content box, but only if the image is big enough (avoid displaying spacers) */ if (box->width > 2 && box->height > 2) { img_props.border.x1 = 1; img_props.border.y1 = 1; img_props.border.x2 = 1; img_props.border.y2 = 1; for(i=0;i<4;i++) { img_props.border_colors[i] = QERGB(0, 0, 0); img_props.border_styles[i] = CSS_BORDER_STYLE_INSET; } draw_borders(scr, x0 + 1, y0 + 1, x0 + box->width - 1, y0 + box->height - 1, &img_props); /* display the optional alt text */ if (box->u.image.content_alt) { font = css_select_font(scr, props); len = utf8_to_unicode(ubuf, sizeof(ubuf) / sizeof(ubuf[0]), box->u.image.content_alt); /* XXX: unicode, etc... */ draw_text(scr, font, x0 + ALT_TEXT_PADDING, y0 + font->ascent + ALT_TEXT_PADDING, ubuf, len, props->color); } } } } static void css_display_block(CSSContext *s, CSSBox *box, CSSState *props_parent, CSSRect *clip_box, int dx, int dy) { CSSState *props; int x0, y0, x1, y1, x2, y2; CSSBox *tt; QEditScreen *scr = s->screen; CSSRect old_clip; /* eval the properties for this box */ props = box->props; if (props->display == CSS_DISPLAY_NONE) return; /* XXX: if hidden, display childs ? */ if (props->visibility == CSS_VISIBILITY_HIDDEN) return; /* fast clip test */ if (!css_is_inter_rect(&box->bbox, clip_box)) return; x0 = box->x; y0 = box->y; x0 += dx; y0 += dy; x1 = x0 - props->padding.x1; y1 = y0 - (props->padding.y1 + box->padding_top); x2 = x0 + box->width + props->padding.x2; y2 = y0 + box->height + (props->padding.y2 + box->padding_bottom); /* background + padding */ /* XXX: hack to exclude HTML tag from these computations */ if (!s->bg_drawn && box->tag != CSS_ID_html) { int color; /* if no background drawn, we MUST draw one now with either the specified color or the default color */ color = props->bgcolor; if (color == COLOR_TRANSPARENT) color = s->default_bgcolor; fill_rectangle(scr, s->bg_rect.x1, s->bg_rect.y1, s->bg_rect.x2 - s->bg_rect.x1, s->bg_rect.y2 - s->bg_rect.y1, color); s->bg_drawn = 1; } else if (props->bgcolor != COLOR_TRANSPARENT) { fill_rectangle(scr, x1, y1, x2 - x1, y2 - y1, props->bgcolor); } /* borders */ if (s->media != CSS_MEDIA_TTY) draw_borders(scr, x1, y1, x2, y2, props); if (props->overflow == CSS_OVERFLOW_HIDDEN) { CSSRect r; r.x1 = x0; r.y1 = y0; r.x2 = x0 + box->width; r.y2 = y0 + box->height; push_clip_rectangle(scr, &old_clip, &r); } /* now display the content ! */ switch(box->content_type) { case CSS_CONTENT_TYPE_IMAGE: box_display_image(s, box, x0, y0); break; case CSS_CONTENT_TYPE_CHILDS: /* other boxes are inside: display them */ tt = box->u.child.first; while (tt) { css_display_block(s, tt, props, clip_box, dx, dy); tt = tt->next; } break; default: /* final box with text inside */ box_display_text(s, box, x0, y0); break; } if (props->overflow == CSS_OVERFLOW_HIDDEN) { set_clip_rectangle(scr, &old_clip); } #if 0 { CSSColor color = 0xffffff; fill_rectangle(scr, x1, y1, x2 - x1, 1, color); fill_rectangle(scr, x1, y2, x2 - x1, 1, color); fill_rectangle(scr, x1, y1, 1, y2 - y1, color); fill_rectangle(scr, x2, y1, 1, y2 - y1, color); } #endif } /* dx & dy are added to each coordinates to do scrolling. 'clip_box' is a hint to optimize drawing */ void css_display(CSSContext *s, CSSBox *box, CSSRect *clip_box, int dx, int dy) { CSSState props1, *default_props = &props1; CSSRect clip1; /* express the clip box in document coordinates because it is tested against the bounding boxes */ css_set_rect(&clip1, clip_box->x1 - dx, clip_box->y1 - dy, clip_box->x2 - dx, clip_box->y2 - dy); set_default_props(s, default_props); s->bg_rect = *clip_box; s->bg_drawn = 0; css_display_block(s, box, default_props, &clip1, dx, dy); /* if no background rectangle drawn, then draw it now with default color */ if (!s->bg_drawn) { fill_rectangle(s->screen, s->bg_rect.x1, s->bg_rect.y1, s->bg_rect.x2 - s->bg_rect.x1, s->bg_rect.y2 - s->bg_rect.y1, s->default_bgcolor); } } /* get the cursor position inside a box */ typedef struct CSSCursorState { CSSContext *ctx; CSSRect cursor_pos; CSSBox *box; int x0, y0; int dirc; int offset; } CSSCursorState; static int css_get_cursor_func(void *opaque, CSSBox *box, int x0, int y0) { CSSCursorState *s = opaque; unsigned int line_buf[MAX_LINE_SIZE]; unsigned int glyphs[MAX_LINE_SIZE]; int offsets[MAX_LINE_SIZE+1]; unsigned int char_to_glyph_pos[MAX_LINE_SIZE]; int posc, x, i, len, w, eol; QEditScreen *scr = s->ctx->screen; QEFont *font; CSSState *props; props = box->props; /* XXX: cannot put cursor in empty box */ if (box->height == 0 || box->content_type != CSS_CONTENT_TYPE_BUFFER) return 0; eol = (box->content_eol != 0); if (!(s->offset >= box->u.buffer.start && s->offset < (box->u.buffer.end + eol))) return 0; /* special case for eol */ if (s->offset == box->u.buffer.end) { /* get eol width */ font = css_select_font(s->ctx->screen, props); w = glyph_width(s->ctx->screen, font, '$'); if (box->embedding_level & 1) { /* right to left : cursor on the left */ x = -w; } else { x = box->width; } goto found; } /* get the text and get the cursor position */ len = box_get_text(s->ctx, line_buf, MAX_LINE_SIZE, offsets, box); offsets[len] = box->u.buffer.end; for(i=0;ioffset >= offsets[i] && s->offset < offsets[i + 1]) { posc = i; goto found1; } } /* should never happen */ return 0; found1: len = unicode_to_glyphs(glyphs, char_to_glyph_pos, MAX_LINE_SIZE, line_buf, len, box->embedding_level & 1); posc = char_to_glyph_pos[posc]; font = css_select_font(scr, props); x = 0; for(i=0;ibox = box; s->x0 = x0; s->y0 = y0; s->cursor_pos.x1 = x0 + x; s->cursor_pos.y1 = y0; s->cursor_pos.x2 = x0 + x + w; s->cursor_pos.y2 = y0 + box->height; s->dirc = box->embedding_level & 1; return 1; } int css_get_cursor_pos(CSSContext *s, CSSBox *box, CSSBox **box_ptr, int *x0_ptr, int *y0_ptr, CSSRect *cursor_ptr, int *dir_ptr, int offset) { CSSCursorState cursor_state; cursor_state.ctx = s; cursor_state.offset = offset; if (css_box_iterate(s, box, &cursor_state, css_get_cursor_func)) { *cursor_ptr = cursor_state.cursor_pos; *dir_ptr = cursor_state.dirc; if (box_ptr) *box_ptr = cursor_state.box; if (x0_ptr) *x0_ptr = cursor_state.x0; if (y0_ptr) *y0_ptr = cursor_state.y0; return 1; } else { return 0; } } int css_box_iterate(CSSContext *s, CSSBox *box, void *opaque, CSSIterateFunc iterate_func) { CSSBox *tt; if (box->content_type == CSS_CONTENT_TYPE_CHILDS) { tt = box->u.child.first; while (tt) { if (css_box_iterate(s, tt, opaque, iterate_func)) return 1; tt = tt->next; } } else if (box->content_type == CSS_CONTENT_TYPE_BUFFER) { return iterate_func(opaque, box, box->x, box->y); } return 0; } /* return the offset of the closest char of x position. */ int css_get_offset_pos(CSSContext *s, CSSBox *box, int xc, int dir) { unsigned int line_buf[MAX_LINE_SIZE]; unsigned int glyphs[MAX_LINE_SIZE]; int offsets[MAX_LINE_SIZE]; unsigned int char_to_glyph_pos[MAX_LINE_SIZE]; int x, d, dmin, posc, len, i, w; QEFont *font; CSSState *props; assert (box->content_type == CSS_CONTENT_TYPE_BUFFER); props = box->props; /* get the text and get the cursor position */ len = box_get_text(s, line_buf, MAX_LINE_SIZE, offsets, box); len = unicode_to_glyphs(glyphs, char_to_glyph_pos, MAX_LINE_SIZE, line_buf, len, box->embedding_level & 1); font = css_select_font(s->screen, props); dmin = MAXINT; x = 0; posc = -1; for(i=0;i 0 && x > xc) || (dir < 0 && x < xc)) { d = abs(x - xc); if (d < dmin) { dmin = d; posc = i; } } w = glyph_width(s->screen, font, glyphs[i]); x += w; } /* eol handling */ if (box->content_eol) { /* if right to left, take it into account */ if (box->embedding_level & 1) { w = glyph_width(s->screen, font, '$'); x = -w; } if (dir == 0 || (dir > 0 && x > xc) || (dir < 0 && x < xc)) { d = abs(x - xc); if (d < dmin) { return box->u.buffer.end; } } } /* no matching position found */ if (posc < 0) return -1; /* compute offset */ for(i=0;itag == CSS_ID_NIL) { tag = "anon"; } else { tag = css_ident_str(box->tag); } printf("<%s x=%d y=%d w=%d h=%d el=%d>\n", tag, box->x, box->y, box->width, box->height, box->embedding_level); switch(box->content_type) { case CSS_CONTENT_TYPE_CHILDS: b = box->u.child.first; while (b != NULL) { css_dump_box(b, level + 1); b = b->next; } break; case CSS_CONTENT_TYPE_BUFFER: for(i=0;iu.buffer.start, box->u.buffer.end); break; case CSS_CONTENT_TYPE_STRING: { unsigned long ptr; for(i=0;iu.buffer.start; ptr < box->u.buffer.end; ptr++) printf("%c", *(unsigned char *)ptr); printf("'\n"); } break; case CSS_CONTENT_TYPE_IMAGE: printf("[IMAGE]\n"); break; } for(i=0;i\n", tag); } void css_dump(CSSBox *box) { css_dump_box(box, 0); } #endif /* box handling API */ /* create a new box. WARNING: the tag and the attributes are freed when the box is deleted */ CSSBox *css_new_box(CSSIdent tag, CSSAttribute *attrs) { CSSBox *box; box = malloc(sizeof(CSSBox)); if (!box) return NULL; memset(box, 0, sizeof(CSSBox)); box->tag = tag; box->attrs = attrs; return box; } CSSBox *css_add_box(CSSBox *parent_box, CSSBox *box) { if (parent_box->content_type != CSS_CONTENT_TYPE_CHILDS) { dprintf("error: adding to a terminal box\n"); return NULL; } if (!parent_box->u.child.first) parent_box->u.child.first = box; else parent_box->u.child.last->next = box; parent_box->u.child.last = box; box->parent = parent_box; return box; } /* delete a box and all boxes after and inside */ /* XXX: free generated content ! */ void css_delete_box(CSSBox *box) { CSSBox *box1; CSSAttribute *a1, *a; CSSProperty *p1, *p; while (box != NULL) { switch(box->content_type) { case CSS_CONTENT_TYPE_CHILDS: css_delete_box(box->u.child.first); break; case CSS_CONTENT_TYPE_STRING: /* split boxes never own their content */ if (!box->split) free((void *)box->u.buffer.start); break; case CSS_CONTENT_TYPE_IMAGE: free(box->u.image.content_alt); break; } box1 = box->next; a = box->attrs; while (a != NULL) { a1 = a->next; free(a); a = a1; } p = box->properties; while (p != NULL) { p1 = p->next; free(p); p = p1; } free(box); box = box1; } } void css_set_text_buffer(CSSBox *box, int offset1, int offset2, int eol) { box->content_type = CSS_CONTENT_TYPE_BUFFER; box->content_eol = eol; box->u.buffer.start = offset1; box->u.buffer.end = offset2; } /* note: the string is reallocated */ void css_set_text_string(CSSBox *box, const char *string) { int len; char *str; box->content_type = CSS_CONTENT_TYPE_STRING; str = strdup(string); len = strlen(string); box->u.buffer.start = (unsigned long)str; box->u.buffer.end = (unsigned long)str + len; } void css_set_child_box(CSSBox *parent_box, CSSBox *box) { parent_box->content_type = CSS_CONTENT_TYPE_CHILDS; parent_box->u.child.first = box; parent_box->u.child.last = box; box->parent = parent_box; } /* if 'box' is not a box suitable to have child, transform it into a such a box */ void css_make_child_box(CSSBox *box) { CSSBox *box1; if (box->content_type != CSS_CONTENT_TYPE_CHILDS) { /* the box already contains text : we create a subbox cloning the parent box */ box1 = css_new_box(CSS_ID_NIL, NULL); box1->u.buffer.start = box->u.buffer.start; box1->u.buffer.end = box->u.buffer.end; box1->content_type = box->content_type; box->content_type = CSS_CONTENT_TYPE_CHILDS; box->u.child.first = box1; box->u.child.last = box1; box1->parent = box; } } CSSContext *css_new_document(QEditScreen *screen, EditBuffer *b) { CSSContext *s; s = malloc(sizeof(CSSContext)); if (!s) return NULL; memset(s, 0, sizeof(CSSContext)); s->style_sheet = NULL; s->screen = screen; s->b = b; s->media = screen->media; /* we copy it so that it can be changed if needed */ if (s->media == CSS_MEDIA_TTY) { s->px_size = (int)(CSS_TTY_PX_SIZE * CSS_LENGTH_FRAC_BASE); s->dots_per_inch = CSS_TTY_DPI; } else { s->px_size = (int)(CSS_SCREEN_PX_SIZE * CSS_LENGTH_FRAC_BASE); s->dots_per_inch = CSS_SCREEN_DPI; } return s; } void css_delete_document(CSSContext *s) { int i; CSSState *props, *props_next; for(i=0;ihash_props[i]; props != NULL; props = props_next) { props_next = props->hash_next; free_props(props); } } if (s->style_sheet) { css_free_style_sheet(s->style_sheet); } free(s); } /* must be called before using any css functions */ void css_init(void) { css_init_idents(); }