From 2a03ea30490892ac52b3da325ab78e1aa888f83e Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Thu, 10 May 2018 11:34:26 +0100 Subject: move html and text content handlers where they belong --- Makefile | 4 - content/handlers/Makefile | 11 + content/handlers/html/Makefile | 7 + content/handlers/html/box.c | 1241 +++++ content/handlers/html/box.h | 369 ++ content/handlers/html/box_construct.c | 3134 +++++++++++++ content/handlers/html/box_normalise.c | 1047 +++++ content/handlers/html/box_textarea.c | 350 ++ content/handlers/html/box_textarea.h | 54 + content/handlers/html/font.c | 163 + content/handlers/html/font.h | 43 + content/handlers/html/form.c | 1895 ++++++++ content/handlers/html/form_internal.h | 277 ++ content/handlers/html/html.c | 2468 ++++++++++ content/handlers/html/html.h | 187 + content/handlers/html/html_css.c | 714 +++ content/handlers/html/html_css_fetcher.c | 325 ++ content/handlers/html/html_forms.c | 579 +++ content/handlers/html/html_interaction.c | 1434 ++++++ content/handlers/html/html_internal.h | 409 ++ content/handlers/html/html_object.c | 730 +++ content/handlers/html/html_redraw.c | 1951 ++++++++ content/handlers/html/html_redraw_border.c | 928 ++++ content/handlers/html/html_script.c | 604 +++ content/handlers/html/imagemap.c | 804 ++++ content/handlers/html/imagemap.h | 42 + content/handlers/html/layout.c | 5432 ++++++++++++++++++++++ content/handlers/html/layout.h | 45 + content/handlers/html/search.c | 656 +++ content/handlers/html/search.h | 86 + content/handlers/html/table.c | 1080 +++++ content/handlers/html/table.h | 39 + content/handlers/javascript/duktape/Document.bnd | 2 +- content/handlers/javascript/duktape/Window.bnd | 4 +- content/handlers/text/Makefile | 3 + content/handlers/text/textplain.c | 1576 +++++++ content/handlers/text/textplain.h | 139 + desktop/browser.c | 6 +- desktop/frames.c | 4 +- desktop/netsurf.c | 4 +- desktop/print.c | 2 +- desktop/save_complete.c | 4 +- desktop/save_text.c | 4 +- desktop/selection.c | 8 +- desktop/textinput.c | 6 +- frontends/amiga/dt_sound.c | 2 +- render/Makefile | 10 - render/box.c | 1242 ----- render/box.h | 368 -- render/box_construct.c | 3137 ------------- render/box_normalise.c | 1046 ----- render/box_textarea.c | 350 -- render/box_textarea.h | 55 - render/font.c | 163 - render/font.h | 44 - render/form.c | 1895 -------- render/form_internal.h | 277 -- render/html.c | 2467 ---------- render/html.h | 197 - render/html_css.c | 713 --- render/html_css_fetcher.c | 321 -- render/html_forms.c | 575 --- render/html_interaction.c | 1434 ------ render/html_internal.h | 410 -- render/html_object.c | 729 --- render/html_redraw.c | 1951 -------- render/html_redraw_border.c | 928 ---- render/html_script.c | 603 --- render/imagemap.c | 801 ---- render/imagemap.h | 37 - render/layout.c | 5432 ---------------------- render/layout.h | 45 - render/search.c | 656 --- render/search.h | 81 - render/table.c | 1080 ----- render/table.h | 38 - render/textplain.c | 1351 ------ render/textplain.h | 139 - 78 files changed, 28845 insertions(+), 28602 deletions(-) create mode 100644 content/handlers/html/Makefile create mode 100644 content/handlers/html/box.c create mode 100644 content/handlers/html/box.h create mode 100644 content/handlers/html/box_construct.c create mode 100644 content/handlers/html/box_normalise.c create mode 100644 content/handlers/html/box_textarea.c create mode 100644 content/handlers/html/box_textarea.h create mode 100644 content/handlers/html/font.c create mode 100644 content/handlers/html/font.h create mode 100644 content/handlers/html/form.c create mode 100644 content/handlers/html/form_internal.h create mode 100644 content/handlers/html/html.c create mode 100644 content/handlers/html/html.h create mode 100644 content/handlers/html/html_css.c create mode 100644 content/handlers/html/html_css_fetcher.c create mode 100644 content/handlers/html/html_forms.c create mode 100644 content/handlers/html/html_interaction.c create mode 100644 content/handlers/html/html_internal.h create mode 100644 content/handlers/html/html_object.c create mode 100644 content/handlers/html/html_redraw.c create mode 100644 content/handlers/html/html_redraw_border.c create mode 100644 content/handlers/html/html_script.c create mode 100644 content/handlers/html/imagemap.c create mode 100644 content/handlers/html/imagemap.h create mode 100644 content/handlers/html/layout.c create mode 100644 content/handlers/html/layout.h create mode 100644 content/handlers/html/search.c create mode 100644 content/handlers/html/search.h create mode 100644 content/handlers/html/table.c create mode 100644 content/handlers/html/table.h create mode 100644 content/handlers/text/Makefile create mode 100644 content/handlers/text/textplain.c create mode 100644 content/handlers/text/textplain.h delete mode 100644 render/Makefile delete mode 100644 render/box.c delete mode 100644 render/box.h delete mode 100644 render/box_construct.c delete mode 100644 render/box_normalise.c delete mode 100644 render/box_textarea.c delete mode 100644 render/box_textarea.h delete mode 100644 render/font.c delete mode 100644 render/font.h delete mode 100644 render/form.c delete mode 100644 render/form_internal.h delete mode 100644 render/html.c delete mode 100644 render/html.h delete mode 100644 render/html_css.c delete mode 100644 render/html_css_fetcher.c delete mode 100644 render/html_forms.c delete mode 100644 render/html_interaction.c delete mode 100644 render/html_internal.h delete mode 100644 render/html_object.c delete mode 100644 render/html_redraw.c delete mode 100644 render/html_redraw_border.c delete mode 100644 render/html_script.c delete mode 100644 render/imagemap.c delete mode 100644 render/imagemap.h delete mode 100644 render/layout.c delete mode 100644 render/layout.h delete mode 100644 render/search.c delete mode 100644 render/search.h delete mode 100644 render/table.c delete mode 100644 render/table.h delete mode 100644 render/textplain.c delete mode 100644 render/textplain.h diff --git a/Makefile b/Makefile index 52f55d52a..3109a771c 100644 --- a/Makefile +++ b/Makefile @@ -617,9 +617,6 @@ include frontends/Makefile # Content sources include content/Makefile -# render sources -include render/Makefile - # utility sources include utils/Makefile @@ -636,7 +633,6 @@ include desktop/Makefile S_COMMON := \ $(S_CONTENT) \ $(S_FETCHERS) \ - $(S_RENDER) \ $(S_UTILS) \ $(S_HTTP) \ $(S_NSURL) \ diff --git a/content/handlers/Makefile b/content/handlers/Makefile index 2f2da3aed..ea9d0c84d 100644 --- a/content/handlers/Makefile +++ b/content/handlers/Makefile @@ -13,4 +13,15 @@ include content/handlers/javascript/Makefile S_CONTENT += $(addprefix handlers/javascript/,$(S_JAVASCRIPT)) +# HTML content handler sources +include content/handlers/html/Makefile + +S_CONTENT += $(addprefix handlers/html/,$(S_HTML)) + +# Text content handler sources +include content/handlers/text/Makefile + +S_CONTENT += $(addprefix handlers/text/,$(S_TEXT)) + +# extend the include search path INCLUDE_DIRS += content/handlers diff --git a/content/handlers/html/Makefile b/content/handlers/html/Makefile new file mode 100644 index 000000000..afefba27d --- /dev/null +++ b/content/handlers/html/Makefile @@ -0,0 +1,7 @@ +# HTML content handler sources + +S_HTML := box.c box_construct.c box_normalise.c box_textarea.c \ + font.c form.c imagemap.c layout.c search.c table.c \ + html.c html_css.c html_css_fetcher.c html_script.c \ + html_interaction.c html_redraw.c html_redraw_border.c \ + html_forms.c html_object.c diff --git a/content/handlers/html/box.c b/content/handlers/html/box.c new file mode 100644 index 000000000..52cf12413 --- /dev/null +++ b/content/handlers/html/box.c @@ -0,0 +1,1241 @@ +/* + * Copyright 2005-2007 James Bursa + * Copyright 2003 Phil Mellor + * Copyright 2005 John M Bell + * Copyright 2008 Michael Drake + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * implementation of box tree manipulation. + */ + +#include +#include +#include +#include +#include + +#include "utils/nsoption.h" +#include "utils/log.h" +#include "utils/talloc.h" +#include "netsurf/misc.h" +#include "netsurf/content.h" +#include "netsurf/mouse.h" +#include "css/utils.h" +#include "css/dump.h" +#include "desktop/scrollbar.h" +#include "desktop/gui_internal.h" + +#include "html/box.h" +#include "html/form_internal.h" +#include "html/html_internal.h" + +#define box_is_float(box) (box->type == BOX_FLOAT_LEFT || \ + box->type == BOX_FLOAT_RIGHT) + +/** + * Destructor for box nodes which own styles + * + * \param b The box being destroyed. + * \return 0 to allow talloc to continue destroying the tree. + */ +static int box_talloc_destructor(struct box *b) +{ + struct html_scrollbar_data *data; + + if ((b->flags & STYLE_OWNED) && b->style != NULL) { + css_computed_style_destroy(b->style); + b->style = NULL; + } + + if (b->styles != NULL) { + css_select_results_destroy(b->styles); + b->styles = NULL; + } + + if (b->href != NULL) + nsurl_unref(b->href); + + if (b->id != NULL) { + lwc_string_unref(b->id); + } + + if (b->node != NULL) { + dom_node_unref(b->node); + } + + if (b->scroll_x != NULL) { + data = scrollbar_get_data(b->scroll_x); + scrollbar_destroy(b->scroll_x); + free(data); + } + + if (b->scroll_y != NULL) { + data = scrollbar_get_data(b->scroll_y); + scrollbar_destroy(b->scroll_y); + free(data); + } + + return 0; +} + +/** + * Create a box tree node. + * + * \param styles selection results for the box, or NULL + * \param style computed style for the box (not copied), or 0 + * \param style_owned whether style is owned by this box + * \param href href for the box (copied), or 0 + * \param target target for the box (not copied), or 0 + * \param title title for the box (not copied), or 0 + * \param id id for the box (not copied), or 0 + * \param context context for allocations + * \return allocated and initialised box, or 0 on memory exhaustion + * + * styles is always owned by the box, if it is set. + * style is only owned by the box in the case of implied boxes. + */ + +struct box * box_create(css_select_results *styles, css_computed_style *style, + bool style_owned, nsurl *href, const char *target, + const char *title, lwc_string *id, void *context) +{ + unsigned int i; + struct box *box; + + box = talloc(context, struct box); + if (!box) { + return 0; + } + + talloc_set_destructor(box, box_talloc_destructor); + + box->type = BOX_INLINE; + box->flags = 0; + box->flags = style_owned ? (box->flags | STYLE_OWNED) : box->flags; + box->styles = styles; + box->style = style; + box->x = box->y = 0; + box->width = UNKNOWN_WIDTH; + box->height = 0; + box->descendant_x0 = box->descendant_y0 = 0; + box->descendant_x1 = box->descendant_y1 = 0; + for (i = 0; i != 4; i++) + box->margin[i] = box->padding[i] = box->border[i].width = 0; + box->scroll_x = box->scroll_y = NULL; + box->min_width = 0; + box->max_width = UNKNOWN_MAX_WIDTH; + box->byte_offset = 0; + box->text = NULL; + box->length = 0; + box->space = 0; + box->href = (href == NULL) ? NULL : nsurl_ref(href); + box->target = target; + box->title = title; + box->columns = 1; + box->rows = 1; + box->start_column = 0; + box->next = NULL; + box->prev = NULL; + box->children = NULL; + box->last = NULL; + box->parent = NULL; + box->inline_end = NULL; + box->float_children = NULL; + box->float_container = NULL; + box->next_float = NULL; + box->cached_place_below_level = 0; + box->list_marker = NULL; + box->col = NULL; + box->gadget = NULL; + box->usemap = NULL; + box->id = id; + box->background = NULL; + box->object = NULL; + box->object_params = NULL; + box->iframe = NULL; + box->node = NULL; + + return box; +} + +/** + * Add a child to a box tree node. + * + * \param parent box giving birth + * \param child box to link as last child of parent + */ + +void box_add_child(struct box *parent, struct box *child) +{ + assert(parent); + assert(child); + + if (parent->children != 0) { /* has children already */ + parent->last->next = child; + child->prev = parent->last; + } else { /* this is the first child */ + parent->children = child; + child->prev = 0; + } + + parent->last = child; + child->parent = parent; +} + + +/** + * Insert a new box as a sibling to a box in a tree. + * + * \param box box already in tree + * \param new_box box to link into tree as next sibling + */ + +void box_insert_sibling(struct box *box, struct box *new_box) +{ + new_box->parent = box->parent; + new_box->prev = box; + new_box->next = box->next; + box->next = new_box; + if (new_box->next) + new_box->next->prev = new_box; + else if (new_box->parent) + new_box->parent->last = new_box; +} + + +/** + * Unlink a box from the box tree and then free it recursively. + * + * \param box box to unlink and free recursively. + */ + +void box_unlink_and_free(struct box *box) +{ + struct box *parent = box->parent; + struct box *next = box->next; + struct box *prev = box->prev; + + if (parent) { + if (parent->children == box) + parent->children = next; + if (parent->last == box) + parent->last = next ? next : prev; + } + + if (prev) + prev->next = next; + if (next) + next->prev = prev; + + box_free(box); +} + + +/** + * Free a box tree recursively. + * + * \param box box to free recursively + * + * The box and all its children is freed. + */ + +void box_free(struct box *box) +{ + struct box *child, *next; + + /* free children first */ + for (child = box->children; child; child = next) { + next = child->next; + box_free(child); + } + + /* last this box */ + box_free_box(box); +} + + +/** + * Free the data in a single box structure. + * + * \param box box to free + */ + +void box_free_box(struct box *box) +{ + if (!(box->flags & CLONE)) { + if (box->gadget) + form_free_control(box->gadget); + if (box->scroll_x != NULL) + scrollbar_destroy(box->scroll_x); + if (box->scroll_y != NULL) + scrollbar_destroy(box->scroll_y); + if (box->styles != NULL) + css_select_results_destroy(box->styles); + } + + talloc_free(box); +} + + +/** + * Find the absolute coordinates of a box. + * + * \param box the box to calculate coordinates of + * \param x updated to x coordinate + * \param y updated to y coordinate + */ + +void box_coords(struct box *box, int *x, int *y) +{ + *x = box->x; + *y = box->y; + while (box->parent) { + if (box_is_float(box)) { + do { + box = box->parent; + } while (!box->float_children); + } else + box = box->parent; + *x += box->x - scrollbar_get_offset(box->scroll_x); + *y += box->y - scrollbar_get_offset(box->scroll_y); + } +} + + +/** + * Find the bounds of a box. + * + * \param box the box to calculate bounds of + * \param r receives bounds + */ + +void box_bounds(struct box *box, struct rect *r) +{ + int width, height; + + box_coords(box, &r->x0, &r->y0); + + width = box->padding[LEFT] + box->width + box->padding[RIGHT]; + height = box->padding[TOP] + box->height + box->padding[BOTTOM]; + + r->x1 = r->x0 + width; + r->y1 = r->y0 + height; +} + + +/** + * Determine if a point lies within a box. + * + * \param[in] len_ctx CSS length conversion context to use. + * \param[in] box Box to consider + * \param[in] x Coordinate relative to box + * \param[in] y Coordinate relative to box + * \param[out] physically If function returning true, physically is set true + * iff point is within the box's physical dimensions and + * false if the point is not within the box's physical + * dimensions but is in the area defined by the box's + * descendants. If function returns false, physically + * is undefined. + * \return true if the point is within the box or a descendant box + * + * This is a helper function for box_at_point(). + */ + +static bool box_contains_point( + const nscss_len_ctx *len_ctx, + const struct box *box, + int x, + int y, + bool *physically) +{ + css_computed_clip_rect css_rect; + + if (box->style != NULL && + css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE && + css_computed_clip(box->style, &css_rect) == + CSS_CLIP_RECT) { + /* We have an absolutly positioned box with a clip rect */ + struct rect r = { + .x0 = box->border[LEFT].width, + .y0 = box->border[TOP].width, + .x1 = box->padding[LEFT] + box->width + + box->border[RIGHT].width + + box->padding[RIGHT], + .y1 = box->padding[TOP] + box->height + + box->border[BOTTOM].width + + box->padding[BOTTOM] + }; + if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) + *physically = true; + else + *physically = false; + + /* Adjust rect to css clip region */ + if (css_rect.left_auto == false) { + r.x0 += FIXTOINT(nscss_len2px(len_ctx, + css_rect.left, css_rect.lunit, + box->style)); + } + if (css_rect.top_auto == false) { + r.y0 += FIXTOINT(nscss_len2px(len_ctx, + css_rect.top, css_rect.tunit, + box->style)); + } + if (css_rect.right_auto == false) { + r.x1 = box->border[LEFT].width + + FIXTOINT(nscss_len2px(len_ctx, + css_rect.right, + css_rect.runit, + box->style)); + } + if (css_rect.bottom_auto == false) { + r.y1 = box->border[TOP].width + + FIXTOINT(nscss_len2px(len_ctx, + css_rect.bottom, + css_rect.bunit, + box->style)); + } + + /* Test if point is in clipped box */ + if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) { + /* inside clip area */ + return true; + } + + /* Not inside clip area */ + return false; + } + if (x >= -box->border[LEFT].width && + x < box->padding[LEFT] + box->width + + box->padding[RIGHT] + box->border[RIGHT].width && + y >= -box->border[TOP].width && + y < box->padding[TOP] + box->height + + box->padding[BOTTOM] + box->border[BOTTOM].width) { + *physically = true; + return true; + } + if (box->list_marker && box->list_marker->x - box->x <= x + + box->list_marker->border[LEFT].width && + x < box->list_marker->x - box->x + + box->list_marker->padding[LEFT] + + box->list_marker->width + + box->list_marker->border[RIGHT].width + + box->list_marker->padding[RIGHT] && + box->list_marker->y - box->y <= y + + box->list_marker->border[TOP].width && + y < box->list_marker->y - box->y + + box->list_marker->padding[TOP] + + box->list_marker->height + + box->list_marker->border[BOTTOM].width + + box->list_marker->padding[BOTTOM]) { + *physically = true; + return true; + } + if ((box->style && css_computed_overflow_x(box->style) == + CSS_OVERFLOW_VISIBLE) || !box->style) { + if (box->descendant_x0 <= x && + x < box->descendant_x1) { + *physically = false; + return true; + } + } + if ((box->style && css_computed_overflow_y(box->style) == + CSS_OVERFLOW_VISIBLE) || !box->style) { + if (box->descendant_y0 <= y && + y < box->descendant_y1) { + *physically = false; + return true; + } + } + return false; +} + + +/** Direction to move in a box-tree walk */ +enum box_walk_dir { + BOX_WALK_CHILDREN, + BOX_WALK_PARENT, + BOX_WALK_NEXT_SIBLING, + BOX_WALK_FLOAT_CHILDREN, + BOX_WALK_NEXT_FLOAT_SIBLING, + BOX_WALK_FLOAT_CONTAINER +}; + + +/** + * Move from box to next box in given direction, adjusting for box coord change + * + * \param b box to move from from + * \param dir direction to move in + * \param x box's global x-coord, updated to position of next box + * \param y box's global y-coord, updated to position of next box + * + * If no box can be found in given direction, NULL is returned. + */ +static inline struct box *box_move_xy(struct box *b, enum box_walk_dir dir, + int *x, int *y) +{ + struct box *rb = NULL; + + switch (dir) { + case BOX_WALK_CHILDREN: + b = b->children; + if (b == NULL) + break; + *x += b->x; + *y += b->y; + if (!box_is_float(b)) { + rb = b; + break; + } + /* Fall through */ + + case BOX_WALK_NEXT_SIBLING: + do { + *x -= b->x; + *y -= b->y; + b = b->next; + if (b == NULL) + break; + *x += b->x; + *y += b->y; + } while (box_is_float(b)); + rb = b; + break; + + case BOX_WALK_PARENT: + *x -= b->x; + *y -= b->y; + rb = b->parent; + break; + + case BOX_WALK_FLOAT_CHILDREN: + b = b->float_children; + if (b == NULL) + break; + *x += b->x; + *y += b->y; + rb = b; + break; + + case BOX_WALK_NEXT_FLOAT_SIBLING: + *x -= b->x; + *y -= b->y; + b = b->next_float; + if (b == NULL) + break; + *x += b->x; + *y += b->y; + rb = b; + break; + + case BOX_WALK_FLOAT_CONTAINER: + *x -= b->x; + *y -= b->y; + rb = b->float_container; + break; + + default: + assert(0 && "Bad box walk type."); + } + + return rb; +} + + +/** + * Itterator for walking to next box in interaction order + * + * \param b box to find next box from + * \param x box's global x-coord, updated to position of next box + * \param y box's global y-coord, updated to position of next box + * \param skip_children whether to skip box's children + * + * This walks to a boxes float children before its children. When walking + * children, floating boxes are skipped. + */ +static inline struct box *box_next_xy(struct box *b, int *x, int *y, + bool skip_children) +{ + struct box *n; + int tx, ty; + + assert(b != NULL); + + if (skip_children) { + /* Caller is not interested in any kind of children */ + goto skip_children; + } + + tx = *x; ty = *y; + n = box_move_xy(b, BOX_WALK_FLOAT_CHILDREN, &tx, &ty); + if (n) { + /* Next node is float child */ + *x = tx; + *y = ty; + return n; + } +done_float_children: + + tx = *x; ty = *y; + n = box_move_xy(b, BOX_WALK_CHILDREN, &tx, &ty); + if (n) { + /* Next node is child */ + *x = tx; + *y = ty; + return n; + } + +skip_children: + tx = *x; ty = *y; + n = box_move_xy(b, BOX_WALK_NEXT_FLOAT_SIBLING, &tx, &ty); + if (n) { + /* Go to next float sibling */ + *x = tx; + *y = ty; + return n; + } + + if (box_is_float(b)) { + /* Done floats, but the float container may have children, + * or siblings, or ansestors with siblings. Change to + * float container and move past handling its float children. + */ + b = box_move_xy(b, BOX_WALK_FLOAT_CONTAINER, x, y); + goto done_float_children; + } + + /* Go to next sibling, or nearest ancestor with next sibling. */ + while (b) { + while (!b->next && b->parent) { + b = box_move_xy(b, BOX_WALK_PARENT, x, y); + if (box_is_float(b)) { + /* Go on to next float, if there is one */ + goto skip_children; + } + } + if (!b->next) { + /* No more boxes */ + return NULL; + } + + tx = *x; ty = *y; + n = box_move_xy(b, BOX_WALK_NEXT_SIBLING, &tx, &ty); + if (n) { + /* Go to non-float (ancestor) sibling */ + *x = tx; + *y = ty; + return n; + + } else if (b->parent) { + b = box_move_xy(b, BOX_WALK_PARENT, x, y); + if (box_is_float(b)) { + /* Go on to next float, if there is one */ + goto skip_children; + } + + } else { + /* No more boxes */ + return NULL; + } + } + + assert(b != NULL); + return NULL; +} + + + +/** + * Find the boxes at a point. + * + * \param len_ctx CSS length conversion context for document. + * \param box box to search children of + * \param x point to find, in global document coordinates + * \param y point to find, in global document coordinates + * \param box_x position of box, in global document coordinates, updated + * to position of returned box, if any + * \param box_y position of box, in global document coordinates, updated + * to position of returned box, if any + * \return box at given point, or 0 if none found + * + * To find all the boxes in the hierarchy at a certain point, use code like + * this: + * \code + * struct box *box = top_of_document_to_search; + * int box_x = 0, box_y = 0; + * + * while ((box = box_at_point(len_ctx, box, x, y, &box_x, &box_y))) { + * // process box + * } + * \endcode + */ + +struct box *box_at_point(const nscss_len_ctx *len_ctx, + struct box *box, const int x, const int y, + int *box_x, int *box_y) +{ + bool skip_children; + bool physically; + + assert(box); + + skip_children = false; + while ((box = box_next_xy(box, box_x, box_y, skip_children))) { + if (box_contains_point(len_ctx, box, x - *box_x, y - *box_y, + &physically)) { + *box_x -= scrollbar_get_offset(box->scroll_x); + *box_y -= scrollbar_get_offset(box->scroll_y); + + if (physically) + return box; + + skip_children = false; + } else { + skip_children = true; + } + } + + return NULL; +} + + +/** + * Check whether box is nearer mouse coordinates than current nearest box + * + * \param box box to test + * \param bx position of box, in global document coordinates + * \param by position of box, in global document coordinates + * \param x mouse point, in global document coordinates + * \param y mouse point, in global document coordinates + * \param dir direction in which to search (-1 = above-left, + * +1 = below-right) + * \param nearest nearest text box found, or NULL if none + * updated if box is nearer than existing nearest + * \param tx position of text_box, in global document coordinates + * updated if box is nearer than existing nearest + * \param ty position of text_box, in global document coordinates + * updated if box is nearer than existing nearest + * \param nr_xd distance to nearest text box found + * updated if box is nearer than existing nearest + * \param nr_yd distance to nearest text box found + * updated if box is nearer than existing nearest + * \return true if mouse point is inside box + */ + +static bool box_nearer_text_box(struct box *box, int bx, int by, + int x, int y, int dir, struct box **nearest, int *tx, int *ty, + int *nr_xd, int *nr_yd) +{ + int w = box->padding[LEFT] + box->width + box->padding[RIGHT]; + int h = box->padding[TOP] + box->height + box->padding[BOTTOM]; + int y1 = by + h; + int x1 = bx + w; + int yd = INT_MAX; + int xd = INT_MAX; + + if (x >= bx && x1 > x && y >= by && y1 > y) { + *nearest = box; + *tx = bx; + *ty = by; + return true; + } + + if (box->parent->list_marker != box) { + if (dir < 0) { + /* consider only those children (partly) above-left */ + if (by <= y && bx < x) { + yd = y <= y1 ? 0 : y - y1; + xd = x <= x1 ? 0 : x - x1; + } + } else { + /* consider only those children (partly) below-right */ + if (y1 > y && x1 > x) { + yd = y > by ? 0 : by - y; + xd = x > bx ? 0 : bx - x; + } + } + + /* give y displacement precedence over x */ + if (yd < *nr_yd || (yd == *nr_yd && xd <= *nr_xd)) { + *nr_yd = yd; + *nr_xd = xd; + *nearest = box; + *tx = bx; + *ty = by; + } + } + return false; +} + + +/** + * Pick the text box child of 'box' that is closest to and above-left + * (dir -ve) or below-right (dir +ve) of the point 'x,y' + * + * \param box parent box + * \param bx position of box, in global document coordinates + * \param by position of box, in global document coordinates + * \param fx position of float parent, in global document coordinates + * \param fy position of float parent, in global document coordinates + * \param x mouse point, in global document coordinates + * \param y mouse point, in global document coordinates + * \param dir direction in which to search (-1 = above-left, + * +1 = below-right) + * \param nearest nearest text box found, or NULL if none + * updated if a descendant of box is nearer than old nearest + * \param tx position of nearest, in global document coordinates + * updated if a descendant of box is nearer than old nearest + * \param ty position of nearest, in global document coordinates + * updated if a descendant of box is nearer than old nearest + * \param nr_xd distance to nearest text box found + * updated if a descendant of box is nearer than old nearest + * \param nr_yd distance to nearest text box found + * updated if a descendant of box is nearer than old nearest + * \return true if mouse point is inside text_box + */ + +static bool box_nearest_text_box(struct box *box, int bx, int by, + int fx, int fy, int x, int y, int dir, struct box **nearest, + int *tx, int *ty, int *nr_xd, int *nr_yd) +{ + struct box *child = box->children; + int c_bx, c_by; + int c_fx, c_fy; + bool in_box = false; + + if (*nearest == NULL) { + *nr_xd = INT_MAX / 2; /* displacement of 'nearest so far' */ + *nr_yd = INT_MAX / 2; + } + if (box->type == BOX_INLINE_CONTAINER) { + int bw = box->padding[LEFT] + box->width + box->padding[RIGHT]; + int bh = box->padding[TOP] + box->height + box->padding[BOTTOM]; + int b_y1 = by + bh; + int b_x1 = bx + bw; + if (x >= bx && b_x1 > x && y >= by && b_y1 > y) { + in_box = true; + } + } + + while (child) { + if (child->type == BOX_FLOAT_LEFT || + child->type == BOX_FLOAT_RIGHT) { + c_bx = fx + child->x - + scrollbar_get_offset(child->scroll_x); + c_by = fy + child->y - + scrollbar_get_offset(child->scroll_y); + } else { + c_bx = bx + child->x - + scrollbar_get_offset(child->scroll_x); + c_by = by + child->y - + scrollbar_get_offset(child->scroll_y); + } + if (child->float_children) { + c_fx = c_bx; + c_fy = c_by; + } else { + c_fx = fx; + c_fy = fy; + } + if (in_box && child->text && !child->object) { + if (box_nearer_text_box(child, + c_bx, c_by, x, y, dir, nearest, + tx, ty, nr_xd, nr_yd)) + return true; + } else { + if (child->list_marker) { + if (box_nearer_text_box( + child->list_marker, + c_bx + child->list_marker->x, + c_by + child->list_marker->y, + x, y, dir, nearest, + tx, ty, nr_xd, nr_yd)) + return true; + } + if (box_nearest_text_box(child, c_bx, c_by, + c_fx, c_fy, x, y, dir, nearest, tx, ty, + nr_xd, nr_yd)) + return true; + } + child = child->next; + } + + return false; +} + + +/** + * Peform pick text on browser window contents to locate the box under + * the mouse pointer, or nearest in the given direction if the pointer is + * not over a text box. + * + * \param html an HTML content + * \param x coordinate of mouse + * \param y coordinate of mouse + * \param dir direction to search (-1 = above-left, +1 = below-right) + * \param dx receives x ordinate of mouse relative to text box + * \param dy receives y ordinate of mouse relative to text box + */ + +struct box *box_pick_text_box(struct html_content *html, + int x, int y, int dir, int *dx, int *dy) +{ + struct box *text_box = NULL; + struct box *box; + int nr_xd, nr_yd; + int bx, by; + int fx, fy; + int tx, ty; + + if (html == NULL) + return NULL; + + box = html->layout; + bx = box->margin[LEFT]; + by = box->margin[TOP]; + fx = bx; + fy = by; + + if (!box_nearest_text_box(box, bx, by, fx, fy, x, y, + dir, &text_box, &tx, &ty, &nr_xd, &nr_yd)) { + if (text_box && text_box->text && !text_box->object) { + int w = (text_box->padding[LEFT] + + text_box->width + + text_box->padding[RIGHT]); + int h = (text_box->padding[TOP] + + text_box->height + + text_box->padding[BOTTOM]); + int x1, y1; + + y1 = ty + h; + x1 = tx + w; + + /* ensure point lies within the text box */ + if (x < tx) x = tx; + if (y < ty) y = ty; + if (y > y1) y = y1; + if (x > x1) x = x1; + } + } + + /* return coordinates relative to box */ + *dx = x - tx; + *dy = y - ty; + + return text_box; +} + + +/** + * Find a box based upon its id attribute. + * + * \param box box tree to search + * \param id id to look for + * \return the box or 0 if not found + */ + +struct box *box_find_by_id(struct box *box, lwc_string *id) +{ + struct box *a, *b; + bool m; + + if (box->id != NULL && + lwc_string_isequal(id, box->id, &m) == lwc_error_ok && + m == true) + return box; + + for (a = box->children; a; a = a->next) { + if ((b = box_find_by_id(a, id)) != NULL) + return b; + } + + return NULL; +} + + +/** + * Determine if a box is visible when the tree is rendered. + * + * \param box box to check + * \return true iff the box is rendered + */ + +bool box_visible(struct box *box) +{ + /* visibility: hidden */ + if (box->style && css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN) + return false; + + return true; +} + + +/** + * Print a box tree to a file. + */ + +void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style) +{ + unsigned int i; + struct box *c, *prev; + + for (i = 0; i != depth; i++) + fprintf(stream, " "); + + fprintf(stream, "%p ", box); + fprintf(stream, "x%i y%i w%i h%i ", box->x, box->y, + box->width, box->height); + if (box->max_width != UNKNOWN_MAX_WIDTH) + fprintf(stream, "min%i max%i ", box->min_width, box->max_width); + fprintf(stream, "(%i %i %i %i) ", + box->descendant_x0, box->descendant_y0, + box->descendant_x1, box->descendant_y1); + + fprintf(stream, "m(%i %i %i %i) ", + box->margin[TOP], box->margin[LEFT], + box->margin[BOTTOM], box->margin[RIGHT]); + + switch (box->type) { + case BOX_BLOCK: fprintf(stream, "BLOCK "); break; + case BOX_INLINE_CONTAINER: fprintf(stream, "INLINE_CONTAINER "); break; + case BOX_INLINE: fprintf(stream, "INLINE "); break; + case BOX_INLINE_END: fprintf(stream, "INLINE_END "); break; + case BOX_INLINE_BLOCK: fprintf(stream, "INLINE_BLOCK "); break; + case BOX_TABLE: fprintf(stream, "TABLE [columns %i] ", + box->columns); break; + case BOX_TABLE_ROW: fprintf(stream, "TABLE_ROW "); break; + case BOX_TABLE_CELL: fprintf(stream, "TABLE_CELL [columns %i, " + "start %i, rows %i] ", box->columns, + box->start_column, box->rows); break; + case BOX_TABLE_ROW_GROUP: fprintf(stream, "TABLE_ROW_GROUP "); break; + case BOX_FLOAT_LEFT: fprintf(stream, "FLOAT_LEFT "); break; + case BOX_FLOAT_RIGHT: fprintf(stream, "FLOAT_RIGHT "); break; + case BOX_BR: fprintf(stream, "BR "); break; + case BOX_TEXT: fprintf(stream, "TEXT "); break; + default: fprintf(stream, "Unknown box type "); + } + + if (box->text) + fprintf(stream, "%li '%.*s' ", (unsigned long) box->byte_offset, + (int) box->length, box->text); + if (box->space) + fprintf(stream, "space "); + if (box->object) { + fprintf(stream, "(object '%s') ", + nsurl_access(hlcache_handle_get_url(box->object))); + } + if (box->iframe) { + fprintf(stream, "(iframe) "); + } + if (box->gadget) + fprintf(stream, "(gadget) "); + if (style && box->style) + nscss_dump_computed_style(stream, box->style); + if (box->href) + fprintf(stream, " -> '%s'", nsurl_access(box->href)); + if (box->target) + fprintf(stream, " |%s|", box->target); + if (box->title) + fprintf(stream, " [%s]", box->title); + if (box->id) + fprintf(stream, " ID:%s", lwc_string_data(box->id)); + if (box->type == BOX_INLINE || box->type == BOX_INLINE_END) + fprintf(stream, " inline_end %p", box->inline_end); + if (box->float_children) + fprintf(stream, " float_children %p", box->float_children); + if (box->next_float) + fprintf(stream, " next_float %p", box->next_float); + if (box->float_container) + fprintf(stream, " float_container %p", box->float_container); + if (box->col) { + fprintf(stream, " (columns"); + for (i = 0; i != box->columns; i++) + fprintf(stream, " (%s %s %i %i %i)", + ((const char *[]) {"UNKNOWN", "FIXED", + "AUTO", "PERCENT", "RELATIVE"}) + [box->col[i].type], + ((const char *[]) {"normal", + "positioned"}) + [box->col[i].positioned], + box->col[i].width, + box->col[i].min, box->col[i].max); + fprintf(stream, ")"); + } + if (box->node != NULL) { + dom_string *name; + if (dom_node_get_node_name(box->node, &name) == DOM_NO_ERR) { + fprintf(stream, " <%s>", dom_string_data(name)); + dom_string_unref(name); + } + } + fprintf(stream, "\n"); + + if (box->list_marker) { + for (i = 0; i != depth; i++) + fprintf(stream, " "); + fprintf(stream, "list_marker:\n"); + box_dump(stream, box->list_marker, depth + 1, style); + } + + for (c = box->children; c && c->next; c = c->next) + ; + if (box->last != c) + fprintf(stream, "warning: box->last %p (should be %p) " + "(box %p)\n", box->last, c, box); + for (prev = 0, c = box->children; c; prev = c, c = c->next) { + if (c->parent != box) + fprintf(stream, "warning: box->parent %p (should be " + "%p) (box on next line)\n", + c->parent, box); + if (c->prev != prev) + fprintf(stream, "warning: box->prev %p (should be " + "%p) (box on next line)\n", + c->prev, prev); + box_dump(stream, c, depth + 1, style); + } +} + +/** + * Applies the given scroll setup to a box. This includes scroll + * creation/deletion as well as scroll dimension updates. + * + * \param c content in which the box is located + * \param box the box to handle the scrolls for + * \param bottom whether the horizontal scrollbar should be present + * \param right whether the vertical scrollbar should be present + * \return true on success false otherwise + */ +bool box_handle_scrollbars(struct content *c, struct box *box, + bool bottom, bool right) +{ + struct html_scrollbar_data *data; + int visible_width, visible_height; + int full_width, full_height; + + if (!bottom && box->scroll_x != NULL) { + data = scrollbar_get_data(box->scroll_x); + scrollbar_destroy(box->scroll_x); + free(data); + box->scroll_x = NULL; + } + + if (!right && box->scroll_y != NULL) { + data = scrollbar_get_data(box->scroll_y); + scrollbar_destroy(box->scroll_y); + free(data); + box->scroll_y = NULL; + } + + if (!bottom && !right) + return true; + + visible_width = box->width + box->padding[RIGHT] + box->padding[LEFT]; + visible_height = box->height + box->padding[TOP] + box->padding[BOTTOM]; + + full_width = ((box->descendant_x1 - box->border[RIGHT].width) > + visible_width) ? + box->descendant_x1 + box->padding[RIGHT] : + visible_width; + full_height = ((box->descendant_y1 - box->border[BOTTOM].width) > + visible_height) ? + box->descendant_y1 + box->padding[BOTTOM] : + visible_height; + + if (right) { + if (box->scroll_y == NULL) { + data = malloc(sizeof(struct html_scrollbar_data)); + if (data == NULL) { + NSLOG(netsurf, INFO, "malloc failed"); + guit->misc->warning("NoMemory", 0); + return false; + } + data->c = c; + data->box = box; + if (scrollbar_create(false, visible_height, + full_height, visible_height, + data, html_overflow_scroll_callback, + &(box->scroll_y)) != NSERROR_OK) { + return false; + } + } else { + scrollbar_set_extents(box->scroll_y, visible_height, + visible_height, full_height); + } + } + if (bottom) { + if (box->scroll_x == NULL) { + data = malloc(sizeof(struct html_scrollbar_data)); + if (data == NULL) { + NSLOG(netsurf, INFO, "malloc failed"); + guit->misc->warning("NoMemory", 0); + return false; + } + data->c = c; + data->box = box; + if (scrollbar_create(true, + visible_width - + (right ? SCROLLBAR_WIDTH : 0), + full_width, visible_width, + data, html_overflow_scroll_callback, + &box->scroll_x) != NSERROR_OK) { + return false; + } + } else { + scrollbar_set_extents(box->scroll_x, + visible_width - + (right ? SCROLLBAR_WIDTH : 0), + visible_width, full_width); + } + } + + if (right && bottom) + scrollbar_make_pair(box->scroll_x, box->scroll_y); + + return true; +} + +/** + * Determine if a box has a vertical scrollbar. + * + * \param box scrolling box + * \return the box has a vertical scrollbar + */ + +bool box_vscrollbar_present(const struct box * const box) +{ + return box->padding[TOP] + box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width < box->descendant_y1; +} + + +/** + * Determine if a box has a horizontal scrollbar. + * + * \param box scrolling box + * \return the box has a horizontal scrollbar + */ + +bool box_hscrollbar_present(const struct box * const box) +{ + return box->padding[LEFT] + box->width + box->padding[RIGHT] + + box->border[RIGHT].width < box->descendant_x1; +} diff --git a/content/handlers/html/box.h b/content/handlers/html/box.h new file mode 100644 index 000000000..f096b6714 --- /dev/null +++ b/content/handlers/html/box.h @@ -0,0 +1,369 @@ +/* + * Copyright 2005 James Bursa + * Copyright 2003 Phil Mellor + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Box tree construction and manipulation (interface). + * + * This stage of rendering converts a tree of dom_nodes (produced by libdom) + * to a tree of struct box. The box tree represents the structure of the + * document as given by the CSS display and float properties. + * + * For example, consider the following HTML: + * \code + *

Example Heading

+ *

Example paragraph with emphasised text etc.

\endcode + * + * This would produce approximately the following box tree with default CSS + * rules: + * \code + * BOX_BLOCK (corresponds to h1) + * BOX_INLINE_CONTAINER + * BOX_INLINE "Example Heading" + * BOX_BLOCK (p) + * BOX_INLINE_CONTAINER + * BOX_INLINE "Example paragraph " + * BOX_INLINE "with emphasised text" (em) + * BOX_INLINE "etc." \endcode + * + * Note that the em has been collapsed into the INLINE_CONTAINER. + * + * If these CSS rules were applied: + * \code + * h1 { display: table-cell } + * p { display: table-cell } + * em { float: left; width: 5em } \endcode + * + * then the box tree would instead look like this: + * \code + * BOX_TABLE + * BOX_TABLE_ROW_GROUP + * BOX_TABLE_ROW + * BOX_TABLE_CELL (h1) + * BOX_INLINE_CONTAINER + * BOX_INLINE "Example Heading" + * BOX_TABLE_CELL (p) + * BOX_INLINE_CONTAINER + * BOX_INLINE "Example paragraph " + * BOX_FLOAT_LEFT (em) + * BOX_BLOCK + * BOX_INLINE_CONTAINER + * BOX_INLINE "with emphasised text" + * BOX_INLINE "etc." \endcode + * + * Here implied boxes have been added and a float is present. + * + * A box tree is "normalized" if the following is satisfied: + * \code + * parent permitted child nodes + * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE + * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT, + * INLINE_END + * INLINE none + * TABLE at least 1 TABLE_ROW_GROUP + * TABLE_ROW_GROUP at least 1 TABLE_ROW + * TABLE_ROW at least 1 TABLE_CELL + * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK) + * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE + * \endcode + */ + +#ifndef NETSURF_HTML_BOX_H +#define NETSURF_HTML_BOX_H + +#include +#include +#include +#include + +#include "content/handlers/css/utils.h" + +struct content; +struct box; +struct browser_window; +struct column; +struct object_params; +struct object_param; +struct html_content; +struct nsurl; +struct dom_node; +struct dom_string; +struct rect; + +#define UNKNOWN_WIDTH INT_MAX +#define UNKNOWN_MAX_WIDTH INT_MAX + +typedef void (*box_construct_complete_cb)(struct html_content *c, bool success); + +/** Type of a struct box. */ +typedef enum { + BOX_BLOCK, BOX_INLINE_CONTAINER, BOX_INLINE, + BOX_TABLE, BOX_TABLE_ROW, BOX_TABLE_CELL, + BOX_TABLE_ROW_GROUP, + BOX_FLOAT_LEFT, BOX_FLOAT_RIGHT, + BOX_INLINE_BLOCK, BOX_BR, BOX_TEXT, + BOX_INLINE_END, BOX_NONE +} box_type; + + +/** Flags for a struct box. */ +typedef enum { + NEW_LINE = 1 << 0, /* first inline on a new line */ + STYLE_OWNED = 1 << 1, /* style is owned by this box */ + PRINTED = 1 << 2, /* box has already been printed */ + PRE_STRIP = 1 << 3, /* PRE tag needing leading newline stripped */ + CLONE = 1 << 4, /* continuation of previous box from wrapping */ + MEASURED = 1 << 5, /* text box width has been measured */ + HAS_HEIGHT = 1 << 6, /* box has height (perhaps due to children) */ + MAKE_HEIGHT = 1 << 7, /* box causes its own height */ + NEED_MIN = 1 << 8, /* minimum width is required for layout */ + REPLACE_DIM = 1 << 9, /* replaced element has given dimensions */ + IFRAME = 1 << 10, /* box contains an iframe */ + CONVERT_CHILDREN = 1 << 11, /* wanted children converting */ + IS_REPLACED = 1 << 12 /* box is a replaced element */ +} box_flags; + +/* Sides of a box */ +enum box_side { TOP, RIGHT, BOTTOM, LEFT }; + +/** + * Container for box border details + */ +struct box_border { + enum css_border_style_e style; /**< border-style */ + css_color c; /**< border-color value */ + int width; /**< border-width (pixels) */ +}; + +/** Node in box tree. All dimensions are in pixels. */ +struct box { + /** Type of box. */ + box_type type; + + /** Box flags */ + box_flags flags; + + /** Computed styles for elements and their pseudo elements. NULL on + * non-element boxes. */ + css_select_results *styles; + + /** Style for this box. 0 for INLINE_CONTAINER and FLOAT_*. Pointer into + * a box's 'styles' select results, except for implied boxes, where it + * is a pointer to an owned computed style. */ + css_computed_style *style; + + /** Coordinate of left padding edge relative to parent box, or relative + * to ancestor that contains this box in float_children for FLOAT_. */ + int x; + /** Coordinate of top padding edge, relative as for x. */ + int y; + + int width; /**< Width of content box (excluding padding etc.). */ + int height; /**< Height of content box (excluding padding etc.). */ + + /* These four variables determine the maximum extent of a box's + * descendants. They are relative to the x,y coordinates of the box. + * + * Their use depends on the overflow CSS property: + * + * Overflow: Usage: + * visible The content of the box is displayed within these + * dimensions. + * hidden These are ignored. Content is plotted within the box + * dimensions. + * scroll These are used to determine the extent of the + * scrollable area. + * auto As "scroll". + */ + int descendant_x0; /**< left edge of descendants */ + int descendant_y0; /**< top edge of descendants */ + int descendant_x1; /**< right edge of descendants */ + int descendant_y1; /**< bottom edge of descendants */ + + int margin[4]; /**< Margin: TOP, RIGHT, BOTTOM, LEFT. */ + int padding[4]; /**< Padding: TOP, RIGHT, BOTTOM, LEFT. */ + struct box_border border[4]; /**< Border: TOP, RIGHT, BOTTOM, LEFT. */ + + struct scrollbar *scroll_x; /**< Horizontal scroll. */ + struct scrollbar *scroll_y; /**< Vertical scroll. */ + + /** Width of box taking all line breaks (including margins etc). Must + * be non-negative. */ + int min_width; + /** Width that would be taken with no line breaks. Must be + * non-negative. */ + int max_width; + + /**< Byte offset within a textual representation of this content. */ + size_t byte_offset; + + char *text; /**< Text, or 0 if none. Unterminated. */ + size_t length; /**< Length of text. */ + + /** Width of space after current text (depends on font and size). */ + int space; + + struct nsurl *href; /**< Link, or 0. */ + const char *target; /**< Link target, or 0. */ + const char *title; /**< Title, or 0. */ + + unsigned int columns; /**< Number of columns for TABLE / TABLE_CELL. */ + unsigned int rows; /**< Number of rows for TABLE only. */ + unsigned int start_column; /**< Start column for TABLE_CELL only. */ + + struct box *next; /**< Next sibling box, or 0. */ + struct box *prev; /**< Previous sibling box, or 0. */ + struct box *children; /**< First child box, or 0. */ + struct box *last; /**< Last child box, or 0. */ + struct box *parent; /**< Parent box, or 0. */ + /** INLINE_END box corresponding to this INLINE box, or INLINE box + * corresponding to this INLINE_END box. */ + struct box *inline_end; + + /** First float child box, or 0. Float boxes are in the tree twice, in + * this list for the block box which defines the area for floats, and + * also in the standard tree given by children, next, prev, etc. */ + struct box *float_children; + /** Next sibling float box. */ + struct box *next_float; + /** If box is a float, points to box's containing block */ + struct box *float_container; + /** Level below which subsequent floats must be cleared. + * This is used only for boxes with float_children */ + int clear_level; + + /* Level below which floats have been placed. */ + int cached_place_below_level; + + /** List marker box if this is a list-item, or 0. */ + struct box *list_marker; + + struct column *col; /**< Array of table column data for TABLE only. */ + + /** Form control data, or 0 if not a form control. */ + struct form_control* gadget; + + char *usemap; /** (Image)map to use with this object, or 0 if none */ + lwc_string *id; /**< value of id attribute (or name for anchors) */ + + /** Background image for this box, or 0 if none */ + struct hlcache_handle *background; + + /** Object in this box (usually an image), or 0 if none. */ + struct hlcache_handle* object; + /** Parameters for the object, or 0. */ + struct object_params *object_params; + + /** Iframe's browser_window, or NULL if none */ + struct browser_window *iframe; + + struct dom_node *node; /**< DOM node that generated this box or NULL */ +}; + +/** Table column data. */ +struct column { + /** Type of column. */ + enum { COLUMN_WIDTH_UNKNOWN, COLUMN_WIDTH_FIXED, + COLUMN_WIDTH_AUTO, COLUMN_WIDTH_PERCENT, + COLUMN_WIDTH_RELATIVE } type; + /** Preferred width of column. Pixels for FIXED, percentage for PERCENT, + * relative units for RELATIVE, unused for AUTO. */ + int width; + /** Minimum width of content. */ + int min; + /** Maximum width of content. */ + int max; + /** Whether all of column's cells are css positioned. */ + bool positioned; +}; + +/** Parameters for object element and similar elements. */ +struct object_params { + struct nsurl *data; + char *type; + char *codetype; + struct nsurl *codebase; + struct nsurl *classid; + struct object_param *params; +}; + +/** Linked list of object element parameters. */ +struct object_param { + char *name; + char *value; + char *type; + char *valuetype; + struct object_param *next; +}; + +/** Frame target names (constant pointers to save duplicating the strings many + * times). We convert _blank to _top for user-friendliness. */ +extern const char *TARGET_SELF; +extern const char *TARGET_PARENT; +extern const char *TARGET_TOP; +extern const char *TARGET_BLANK; + + + +struct box * box_create(css_select_results *styles, css_computed_style *style, + bool style_owned, struct nsurl *href, const char *target, + const char *title, lwc_string *id, void *context); +void box_add_child(struct box *parent, struct box *child); +void box_insert_sibling(struct box *box, struct box *new_box); +void box_unlink_and_free(struct box *box); +void box_free(struct box *box); +void box_free_box(struct box *box); +void box_bounds(struct box *box, struct rect *r); +void box_coords(struct box *box, int *x, int *y); +struct box *box_at_point( + const nscss_len_ctx *len_ctx, + struct box *box, const int x, const int y, + int *box_x, int *box_y); +struct box *box_pick_text_box(struct html_content *html, + int x, int y, int dir, int *dx, int *dy); +struct box *box_find_by_id(struct box *box, lwc_string *id); +bool box_visible(struct box *box); +void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style); + +/** + * Extract a URL from a relative link, handling junk like whitespace and + * attempting to read a real URL from "javascript:" links. + * + * \param content html content + * \param dsrel relative URL text taken from page + * \param base base for relative URLs + * \param result updated to target URL on heap, unchanged if extract failed + * \return true on success, false on memory exhaustion + */ +bool box_extract_link(const struct html_content *content, const struct dom_string *dsrel, struct nsurl *base, struct nsurl **result); + +bool box_handle_scrollbars(struct content *c, struct box *box, + bool bottom, bool right); +bool box_vscrollbar_present(const struct box *box); +bool box_hscrollbar_present(const struct box *box); + +nserror dom_to_box(struct dom_node *n, struct html_content *c, + box_construct_complete_cb cb); + +bool box_normalise_block( + struct box *block, + const struct box *root, + struct html_content *c); + +#endif diff --git a/content/handlers/html/box_construct.c b/content/handlers/html/box_construct.c new file mode 100644 index 000000000..9c19391de --- /dev/null +++ b/content/handlers/html/box_construct.c @@ -0,0 +1,3134 @@ +/* + * Copyright 2005 James Bursa + * Copyright 2003 Phil Mellor + * Copyright 2005 John M Bell + * Copyright 2006 Richard Wilson + * Copyright 2008 Michael Drake + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Implementation of conversion from DOM tree to box tree. + */ + +#include +#include +#include +#include +#include +#include + +#include "utils/config.h" +#include "utils/nsoption.h" +#include "utils/corestrings.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/talloc.h" +#include "utils/string.h" +#include "utils/ascii.h" +#include "netsurf/css.h" +#include "netsurf/misc.h" +#include "netsurf/plot_style.h" +#include "content/content_protected.h" +#include "css/hints.h" +#include "css/select.h" +#include "css/utils.h" +#include "desktop/gui_internal.h" + +#include "html/box.h" +#include "html/box_textarea.h" +#include "html/form_internal.h" +#include "html/html_internal.h" + +/** + * Context for box tree construction + */ +struct box_construct_ctx { + html_content *content; /**< Content we're constructing for */ + + dom_node *n; /**< Current node to process */ + + struct box *root_box; /**< Root box in the tree */ + + box_construct_complete_cb cb; /**< Callback to invoke on completion */ + + int *bctx; /**< talloc context */ +}; + +/** + * Transient properties for construction of current node + */ +struct box_construct_props { + /** Style from which to inherit, or NULL if none */ + const css_computed_style *parent_style; + /** Current link target, or NULL if none */ + nsurl *href; + /** Current frame target, or NULL if none */ + const char *target; + /** Current title attribute, or NULL if none */ + const char *title; + /** Identity of the current block-level container */ + struct box *containing_block; + /** Current container for inlines, or NULL if none + * \note If non-NULL, will be the last child of containing_block */ + struct box *inline_container; + /** Whether the current node is the root of the DOM tree */ + bool node_is_root; +}; + +static const content_type image_types = CONTENT_IMAGE; + +/* the strings are not important, since we just compare the pointers */ +const char *TARGET_SELF = "_self"; +const char *TARGET_PARENT = "_parent"; +const char *TARGET_TOP = "_top"; +const char *TARGET_BLANK = "_blank"; + +static void convert_xml_to_box(struct box_construct_ctx *ctx); +static bool box_construct_element(struct box_construct_ctx *ctx, + bool *convert_children); +static void box_construct_element_after(dom_node *n, html_content *content); +static bool box_construct_text(struct box_construct_ctx *ctx); +static css_select_results * box_get_style(html_content *c, + const css_computed_style *parent_style, + const css_computed_style *root_style, dom_node *n); +static void box_text_transform(char *s, unsigned int len, + enum css_text_transform_e tt); +#define BOX_SPECIAL_PARAMS dom_node *n, html_content *content, \ + struct box *box, bool *convert_children +static bool box_a(BOX_SPECIAL_PARAMS); +static bool box_body(BOX_SPECIAL_PARAMS); +static bool box_br(BOX_SPECIAL_PARAMS); +static bool box_image(BOX_SPECIAL_PARAMS); +static bool box_textarea(BOX_SPECIAL_PARAMS); +static bool box_select(BOX_SPECIAL_PARAMS); +static bool box_input(BOX_SPECIAL_PARAMS); +static bool box_button(BOX_SPECIAL_PARAMS); +static bool box_frameset(BOX_SPECIAL_PARAMS); +static bool box_create_frameset(struct content_html_frames *f, dom_node *n, + html_content *content); +static bool box_select_add_option(struct form_control *control, dom_node *n); +static bool box_noscript(BOX_SPECIAL_PARAMS); +static bool box_object(BOX_SPECIAL_PARAMS); +static bool box_embed(BOX_SPECIAL_PARAMS); +static bool box_pre(BOX_SPECIAL_PARAMS); +static bool box_iframe(BOX_SPECIAL_PARAMS); +static bool box_get_attribute(dom_node *n, const char *attribute, + void *context, char **value); + +/* element_table must be sorted by name */ +struct element_entry { + char name[10]; /* element type */ + bool (*convert)(BOX_SPECIAL_PARAMS); +}; +static const struct element_entry element_table[] = { + {"a", box_a}, + {"body", box_body}, + {"br", box_br}, + {"button", box_button}, + {"embed", box_embed}, + {"frameset", box_frameset}, + {"iframe", box_iframe}, + {"image", box_image}, + {"img", box_image}, + {"input", box_input}, + {"noscript", box_noscript}, + {"object", box_object}, + {"pre", box_pre}, + {"select", box_select}, + {"textarea", box_textarea} +}; +#define ELEMENT_TABLE_COUNT (sizeof(element_table) / sizeof(element_table[0])) + +/** + * Construct a box tree from an xml tree and stylesheets. + * + * \param n xml tree + * \param c content of type CONTENT_HTML to construct box tree in + * \param cb callback to report conversion completion + * \return netsurf error code indicating status of call + */ + +nserror dom_to_box(dom_node *n, html_content *c, box_construct_complete_cb cb) +{ + struct box_construct_ctx *ctx; + + if (c->bctx == NULL) { + /* create a context allocation for this box tree */ + c->bctx = talloc_zero(0, int); + if (c->bctx == NULL) { + return NSERROR_NOMEM; + } + } + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) { + return NSERROR_NOMEM; + } + + ctx->content = c; + ctx->n = dom_node_ref(n); + ctx->root_box = NULL; + ctx->cb = cb; + ctx->bctx = c->bctx; + + return guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); +} + +/* mapping from CSS display to box type + * this table must be in sync with libcss' css_display enum */ +static const box_type box_map[] = { + 0, /*CSS_DISPLAY_INHERIT,*/ + BOX_INLINE, /*CSS_DISPLAY_INLINE,*/ + BOX_BLOCK, /*CSS_DISPLAY_BLOCK,*/ + BOX_BLOCK, /*CSS_DISPLAY_LIST_ITEM,*/ + BOX_INLINE, /*CSS_DISPLAY_RUN_IN,*/ + BOX_INLINE_BLOCK, /*CSS_DISPLAY_INLINE_BLOCK,*/ + BOX_TABLE, /*CSS_DISPLAY_TABLE,*/ + BOX_TABLE, /*CSS_DISPLAY_INLINE_TABLE,*/ + BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_ROW_GROUP,*/ + BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_HEADER_GROUP,*/ + BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_FOOTER_GROUP,*/ + BOX_TABLE_ROW, /*CSS_DISPLAY_TABLE_ROW,*/ + BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN_GROUP,*/ + BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN,*/ + BOX_TABLE_CELL, /*CSS_DISPLAY_TABLE_CELL,*/ + BOX_INLINE, /*CSS_DISPLAY_TABLE_CAPTION,*/ + BOX_NONE /*CSS_DISPLAY_NONE*/ +}; + +static inline struct box *box_for_node(dom_node *n) +{ + struct box *box = NULL; + dom_exception err; + + err = dom_node_get_user_data(n, corestring_dom___ns_key_box_node_data, + (void *) &box); + if (err != DOM_NO_ERR) + return NULL; + + return box; +} + +static inline bool box_is_root(dom_node *n) +{ + dom_node *parent; + dom_node_type type; + dom_exception err; + + err = dom_node_get_parent_node(n, &parent); + if (err != DOM_NO_ERR) + return false; + + if (parent != NULL) { + err = dom_node_get_node_type(parent, &type); + + dom_node_unref(parent); + + if (err != DOM_NO_ERR) + return false; + + if (type != DOM_DOCUMENT_NODE) + return false; + } + + return true; +} + +/** + * Find the next node in the DOM tree, completing + * element construction where appropriate. + * + * \param n Current node + * \param content Containing content + * \param convert_children Whether to consider children of \a n + * \return Next node to process, or NULL if complete + * + * \note \a n will be unreferenced + */ +static dom_node *next_node(dom_node *n, html_content *content, + bool convert_children) +{ + dom_node *next = NULL; + bool has_children; + dom_exception err; + + err = dom_node_has_child_nodes(n, &has_children); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return NULL; + } + + if (convert_children && has_children) { + err = dom_node_get_first_child(n, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return NULL; + } + dom_node_unref(n); + } else { + err = dom_node_get_next_sibling(n, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return NULL; + } + + if (next != NULL) { + if (box_for_node(n) != NULL) + box_construct_element_after(n, content); + dom_node_unref(n); + } else { + if (box_for_node(n) != NULL) + box_construct_element_after(n, content); + + while (box_is_root(n) == false) { + dom_node *parent = NULL; + dom_node *parent_next = NULL; + + err = dom_node_get_parent_node(n, &parent); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return NULL; + } + + assert(parent != NULL); + + err = dom_node_get_next_sibling(parent, + &parent_next); + if (err != DOM_NO_ERR) { + dom_node_unref(parent); + dom_node_unref(n); + return NULL; + } + + if (parent_next != NULL) { + dom_node_unref(parent_next); + dom_node_unref(parent); + break; + } + + dom_node_unref(n); + n = parent; + parent = NULL; + + if (box_for_node(n) != NULL) { + box_construct_element_after( + n, content); + } + } + + if (box_is_root(n) == false) { + dom_node *parent = NULL; + + err = dom_node_get_parent_node(n, &parent); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return NULL; + } + + assert(parent != NULL); + + err = dom_node_get_next_sibling(parent, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(parent); + dom_node_unref(n); + return NULL; + } + + if (box_for_node(parent) != NULL) { + box_construct_element_after(parent, + content); + } + + dom_node_unref(parent); + } + + dom_node_unref(n); + } + } + + return next; +} + +/** + * Convert an ELEMENT node to a box tree fragment, + * then schedule conversion of the next ELEMENT node + */ +void convert_xml_to_box(struct box_construct_ctx *ctx) +{ + dom_node *next; + bool convert_children; + uint32_t num_processed = 0; + const uint32_t max_processed_before_yield = 10; + + do { + convert_children = true; + + assert(ctx->n != NULL); + + if (box_construct_element(ctx, &convert_children) == false) { + ctx->cb(ctx->content, false); + dom_node_unref(ctx->n); + free(ctx); + return; + } + + /* Find next element to process, converting text nodes as we go */ + next = next_node(ctx->n, ctx->content, convert_children); + while (next != NULL) { + dom_node_type type; + dom_exception err; + + err = dom_node_get_node_type(next, &type); + if (err != DOM_NO_ERR) { + ctx->cb(ctx->content, false); + dom_node_unref(next); + free(ctx); + return; + } + + if (type == DOM_ELEMENT_NODE) + break; + + if (type == DOM_TEXT_NODE) { + ctx->n = next; + if (box_construct_text(ctx) == false) { + ctx->cb(ctx->content, false); + dom_node_unref(ctx->n); + free(ctx); + return; + } + } + + next = next_node(next, ctx->content, true); + } + + ctx->n = next; + + if (next == NULL) { + /* Conversion complete */ + struct box root; + + memset(&root, 0, sizeof(root)); + + root.type = BOX_BLOCK; + root.children = root.last = ctx->root_box; + root.children->parent = &root; + + /** \todo Remove box_normalise_block */ + if (box_normalise_block(&root, ctx->root_box, + ctx->content) == false) { + ctx->cb(ctx->content, false); + } else { + ctx->content->layout = root.children; + ctx->content->layout->parent = NULL; + + ctx->cb(ctx->content, true); + } + + assert(ctx->n == NULL); + + free(ctx); + return; + } + } while (++num_processed < max_processed_before_yield); + + /* More work to do: schedule a continuation */ + guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); +} + +/** + * Construct a list marker box + * + * \param box Box to attach marker to + * \param title Current title attribute + * \param ctx Box construction context + * \param parent Current block-level container + * \return true on success, false on memory exhaustion + */ +static bool box_construct_marker(struct box *box, const char *title, + struct box_construct_ctx *ctx, struct box *parent) +{ + lwc_string *image_uri; + struct box *marker; + + marker = box_create(NULL, box->style, false, NULL, NULL, title, + NULL, ctx->bctx); + if (marker == false) + return false; + + marker->type = BOX_BLOCK; + + /** \todo marker content (list-style-type) */ + switch (css_computed_list_style_type(box->style)) { + case CSS_LIST_STYLE_TYPE_DISC: + /* 2022 BULLET */ + marker->text = (char *) "\342\200\242"; + marker->length = 3; + break; + case CSS_LIST_STYLE_TYPE_CIRCLE: + /* 25CB WHITE CIRCLE */ + marker->text = (char *) "\342\227\213"; + marker->length = 3; + break; + case CSS_LIST_STYLE_TYPE_SQUARE: + /* 25AA BLACK SMALL SQUARE */ + marker->text = (char *) "\342\226\252"; + marker->length = 3; + break; + case CSS_LIST_STYLE_TYPE_DECIMAL: + case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: + case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: + case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: + case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: + default: + if (parent->last) { + struct box *last = parent->last; + + /* Drill down into last child of parent + * to find the list marker (if any) + * + * Floated list boxes end up as: + * + * parent + * BOX_INLINE_CONTAINER + * BOX_FLOAT_{LEFT,RIGHT} + * BOX_BLOCK <-- list box + * ... + */ + while (last != NULL && last->list_marker == NULL) { + struct box *last_inner = last; + + while (last_inner != NULL) { + if (last_inner->list_marker != NULL) + break; + if (last_inner->type == + BOX_INLINE_CONTAINER || + last_inner->type == + BOX_FLOAT_LEFT || + last_inner->type == + BOX_FLOAT_RIGHT) { + last_inner = last_inner->last; + } else { + last_inner = NULL; + } + } + if (last_inner != NULL) { + last = last_inner; + } else { + last = last->prev; + } + } + + if (last && last->list_marker) { + marker->rows = last->list_marker->rows + 1; + } + } + + marker->text = talloc_array(ctx->bctx, char, 20); + if (marker->text == NULL) + return false; + + snprintf(marker->text, 20, "%u.", marker->rows); + marker->length = strlen(marker->text); + break; + case CSS_LIST_STYLE_TYPE_NONE: + marker->text = 0; + marker->length = 0; + break; + } + + if (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI && + (image_uri != NULL) && + (nsoption_bool(foreground_images) == true)) { + nsurl *url; + nserror error; + + /* TODO: we get a url out of libcss as a lwc string, but + * earlier we already had it as a nsurl after we + * nsurl_joined it. Can this be improved? + * For now, just making another nsurl. */ + error = nsurl_create(lwc_string_data(image_uri), &url); + if (error != NSERROR_OK) + return false; + + if (html_fetch_object(ctx->content, url, marker, image_types, + ctx->content->base.available_width, 1000, false) == + false) { + nsurl_unref(url); + return false; + } + nsurl_unref(url); + } + + box->list_marker = marker; + marker->parent = box; + + return true; +} + +/** + * Construct the box required for a generated element. + * + * \param n XML node of type XML_ELEMENT_NODE + * \param content Content of type CONTENT_HTML that is being processed + * \param box Box which may have generated content + * \param style Complete computed style for pseudo element, or NULL + * + * TODO: + * This is currently incomplete. It just does enough to support the clearfix + * hack. ( http://www.positioniseverything.net/easyclearing.html ) + */ +static void box_construct_generate(dom_node *n, html_content *content, + struct box *box, const css_computed_style *style) +{ + struct box *gen = NULL; + enum css_display_e computed_display; + const css_computed_content_item *c_item; + + /* Nothing to generate if the parent box is not a block */ + if (box->type != BOX_BLOCK) + return; + + /* To determine if an element has a pseudo element, we select + * for it and test to see if the returned style's content + * property is set to normal. */ + if (style == NULL || + css_computed_content(style, &c_item) == + CSS_CONTENT_NORMAL) { + /* No pseudo element */ + return; + } + + /* create box for this element */ + computed_display = ns_computed_display(style, box_is_root(n)); + if (computed_display == CSS_DISPLAY_BLOCK || + computed_display == CSS_DISPLAY_TABLE) { + /* currently only support block level boxes */ + + /** \todo Not wise to drop const from the computed style */ + gen = box_create(NULL, (css_computed_style *) style, + false, NULL, NULL, NULL, NULL, content->bctx); + if (gen == NULL) { + return; + } + + /* set box type from computed display */ + gen->type = box_map[ns_computed_display( + style, box_is_root(n))]; + + box_add_child(box, gen); + } +} + +/** + * Extract transient construction properties + * + * \param n Current DOM node to convert + * \param props Property object to populate + */ +static void box_extract_properties(dom_node *n, + struct box_construct_props *props) +{ + memset(props, 0, sizeof(*props)); + + props->node_is_root = box_is_root(n); + + /* Extract properties from containing DOM node */ + if (props->node_is_root == false) { + dom_node *current_node = n; + dom_node *parent_node = NULL; + struct box *parent_box; + dom_exception err; + + /* Find ancestor node containing parent box */ + while (true) { + err = dom_node_get_parent_node(current_node, + &parent_node); + if (err != DOM_NO_ERR || parent_node == NULL) + break; + + parent_box = box_for_node(parent_node); + + if (parent_box != NULL) { + props->parent_style = parent_box->style; + props->href = parent_box->href; + props->target = parent_box->target; + props->title = parent_box->title; + + dom_node_unref(parent_node); + break; + } else { + if (current_node != n) + dom_node_unref(current_node); + current_node = parent_node; + parent_node = NULL; + } + } + + /* Find containing block (may be parent) */ + while (true) { + struct box *b; + + err = dom_node_get_parent_node(current_node, + &parent_node); + if (err != DOM_NO_ERR || parent_node == NULL) { + if (current_node != n) + dom_node_unref(current_node); + break; + } + + if (current_node != n) + dom_node_unref(current_node); + + b = box_for_node(parent_node); + + /* Children of nodes that created an inline box + * will generate boxes which are attached as + * _siblings_ of the box generated for their + * parent node. Note, however, that we'll still + * use the parent node's styling as the parent + * style, above. */ + if (b != NULL && b->type != BOX_INLINE && + b->type != BOX_BR) { + props->containing_block = b; + + dom_node_unref(parent_node); + break; + } else { + current_node = parent_node; + parent_node = NULL; + } + } + } + + /* Compute current inline container, if any */ + if (props->containing_block != NULL && + props->containing_block->last != NULL && + props->containing_block->last->type == + BOX_INLINE_CONTAINER) + props->inline_container = props->containing_block->last; +} + +/** + * Construct the box tree for an XML element. + * + * \param ctx Tree construction context + * \param convert_children Whether to convert children + * \return true on success, false on memory exhaustion + */ + +bool box_construct_element(struct box_construct_ctx *ctx, + bool *convert_children) +{ + dom_string *title0, *s; + lwc_string *id = NULL; + struct box *box = NULL, *old_box; + css_select_results *styles = NULL; + struct element_entry *element; + lwc_string *bgimage_uri; + dom_exception err; + struct box_construct_props props; + const css_computed_style *root_style = NULL; + + assert(ctx->n != NULL); + + box_extract_properties(ctx->n, &props); + + if (props.containing_block != NULL) { + /* In case the containing block is a pre block, we clear + * the PRE_STRIP flag since it is not used if we follow + * the pre with a tag */ + props.containing_block->flags &= ~PRE_STRIP; + } + + if (props.node_is_root == false) { + root_style = ctx->root_box->style; + } + + styles = box_get_style(ctx->content, props.parent_style, root_style, + ctx->n); + if (styles == NULL) + return false; + + /* Extract title attribute, if present */ + err = dom_element_get_attribute(ctx->n, corestring_dom_title, &title0); + if (err != DOM_NO_ERR) + return false; + + if (title0 != NULL) { + char *t = squash_whitespace(dom_string_data(title0)); + + dom_string_unref(title0); + + if (t == NULL) + return false; + + props.title = talloc_strdup(ctx->bctx, t); + + free(t); + + if (props.title == NULL) + return false; + } + + /* Extract id attribute, if present */ + err = dom_element_get_attribute(ctx->n, corestring_dom_id, &s); + if (err != DOM_NO_ERR) + return false; + + if (s != NULL) { + err = dom_string_intern(s, &id); + if (err != DOM_NO_ERR) + id = NULL; + + dom_string_unref(s); + } + + box = box_create(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false, + props.href, props.target, props.title, id, + ctx->bctx); + if (box == NULL) + return false; + + /* If this is the root box, add it to the context */ + if (props.node_is_root) + ctx->root_box = box; + + /* Deal with colspan/rowspan */ + err = dom_element_get_attribute(ctx->n, corestring_dom_colspan, &s); + if (err != DOM_NO_ERR) + return false; + + if (s != NULL) { + const char *val = dom_string_data(s); + + if ('0' <= val[0] && val[0] <= '9') + box->columns = strtol(val, NULL, 10); + + dom_string_unref(s); + } + + err = dom_element_get_attribute(ctx->n, corestring_dom_rowspan, &s); + if (err != DOM_NO_ERR) + return false; + + if (s != NULL) { + const char *val = dom_string_data(s); + + if ('0' <= val[0] && val[0] <= '9') + box->rows = strtol(val, NULL, 10); + + dom_string_unref(s); + } + + /* Set box type from computed display */ + if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE || + css_computed_position(box->style) == + CSS_POSITION_FIXED) && + (ns_computed_display_static(box->style) == + CSS_DISPLAY_INLINE || + ns_computed_display_static(box->style) == + CSS_DISPLAY_INLINE_BLOCK || + ns_computed_display_static(box->style) == + CSS_DISPLAY_INLINE_TABLE)) { + /* Special case for absolute positioning: make absolute inlines + * into inline block so that the boxes are constructed in an + * inline container as if they were not absolutely positioned. + * Layout expects and handles this. */ + box->type = box_map[CSS_DISPLAY_INLINE_BLOCK]; + } else if (props.node_is_root) { + /* Special case for root element: force it to BLOCK, or the + * rest of the layout will break. */ + box->type = BOX_BLOCK; + } else { + /* Normal mapping */ + box->type = box_map[ns_computed_display(box->style, + props.node_is_root)]; + } + + err = dom_node_get_node_name(ctx->n, &s); + if (err != DOM_NO_ERR || s == NULL) + return false; + + /* Special elements */ + element = bsearch(dom_string_data(s), element_table, + ELEMENT_TABLE_COUNT, sizeof(element_table[0]), + (int (*)(const void *, const void *)) strcasecmp); + + dom_string_unref(s); + + if (element != NULL) { + /* A special convert function exists for this element */ + if (element->convert(ctx->n, ctx->content, box, + convert_children) == false) + return false; + } + + /* Handle the :before pseudo element */ + if (!(box->flags & IS_REPLACED)) { + box_construct_generate(ctx->n, ctx->content, box, + box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]); + } + + if (box->type == BOX_NONE || (ns_computed_display(box->style, + props.node_is_root) == CSS_DISPLAY_NONE && + props.node_is_root == false)) { + css_select_results_destroy(styles); + box->styles = NULL; + box->style = NULL; + + /* Invalidate associated gadget, if any */ + if (box->gadget != NULL) { + box->gadget->box = NULL; + box->gadget = NULL; + } + + /* Can't do this, because the lifetimes of boxes and gadgets + * are inextricably linked. Fortunately, talloc will save us + * (for now) */ + /* box_free_box(box); */ + + *convert_children = false; + + return true; + } + + /* Attach DOM node to box */ + err = dom_node_set_user_data(ctx->n, + corestring_dom___ns_key_box_node_data, box, NULL, + (void *) &old_box); + if (err != DOM_NO_ERR) + return false; + + /* Attach box to DOM node */ + box->node = dom_node_ref(ctx->n); + + if (props.inline_container == NULL && + (box->type == BOX_INLINE || + box->type == BOX_BR || + box->type == BOX_INLINE_BLOCK || + css_computed_float(box->style) == CSS_FLOAT_LEFT || + css_computed_float(box->style) == CSS_FLOAT_RIGHT) && + props.node_is_root == false) { + /* Found an inline child of a block without a current container + * (i.e. this box is the first child of its parent, or was + * preceded by block-level siblings) */ + assert(props.containing_block != NULL && + "Box must have containing block."); + + props.inline_container = box_create(NULL, NULL, false, NULL, + NULL, NULL, NULL, ctx->bctx); + if (props.inline_container == NULL) + return false; + + props.inline_container->type = BOX_INLINE_CONTAINER; + + box_add_child(props.containing_block, props.inline_container); + } + + /* Kick off fetch for any background image */ + if (css_computed_background_image(box->style, &bgimage_uri) == + CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL && + nsoption_bool(background_images) == true) { + nsurl *url; + nserror error; + + /* TODO: we get a url out of libcss as a lwc string, but + * earlier we already had it as a nsurl after we + * nsurl_joined it. Can this be improved? + * For now, just making another nsurl. */ + error = nsurl_create(lwc_string_data(bgimage_uri), &url); + if (error == NSERROR_OK) { + /* Fetch image if we got a valid URL */ + if (html_fetch_object(ctx->content, url, box, + image_types, + ctx->content->base.available_width, + 1000, true) == false) { + nsurl_unref(url); + return false; + } + nsurl_unref(url); + } + } + + if (*convert_children) + box->flags |= CONVERT_CHILDREN; + + if (box->type == BOX_INLINE || box->type == BOX_BR || + box->type == BOX_INLINE_BLOCK) { + /* Inline container must exist, as we'll have + * created it above if it didn't */ + assert(props.inline_container != NULL); + + box_add_child(props.inline_container, box); + } else { + if (ns_computed_display(box->style, props.node_is_root) == + CSS_DISPLAY_LIST_ITEM) { + /* List item: compute marker */ + if (box_construct_marker(box, props.title, ctx, + props.containing_block) == false) + return false; + } + + if (props.node_is_root == false && + (css_computed_float(box->style) == + CSS_FLOAT_LEFT || + css_computed_float(box->style) == + CSS_FLOAT_RIGHT)) { + /* Float: insert a float between the parent and box. */ + struct box *flt = box_create(NULL, NULL, false, + props.href, props.target, props.title, + NULL, ctx->bctx); + if (flt == NULL) + return false; + + if (css_computed_float(box->style) == CSS_FLOAT_LEFT) + flt->type = BOX_FLOAT_LEFT; + else + flt->type = BOX_FLOAT_RIGHT; + + box_add_child(props.inline_container, flt); + box_add_child(flt, box); + } else { + /* Non-floated block-level box: add to containing block + * if there is one. If we're the root box, then there + * won't be. */ + if (props.containing_block != NULL) + box_add_child(props.containing_block, box); + } + } + + return true; +} + +/** + * Complete construction of the box tree for an element. + * + * \param n DOM node to construct for + * \param content Containing document + * + * This will be called after all children of an element have been processed + */ +void box_construct_element_after(dom_node *n, html_content *content) +{ + struct box_construct_props props; + struct box *box = box_for_node(n); + + assert(box != NULL); + + box_extract_properties(n, &props); + + if (box->type == BOX_INLINE || box->type == BOX_BR) { + /* Insert INLINE_END into containing block */ + struct box *inline_end; + bool has_children; + dom_exception err; + + err = dom_node_has_child_nodes(n, &has_children); + if (err != DOM_NO_ERR) + return; + + if (has_children == false || + (box->flags & CONVERT_CHILDREN) == 0) { + /* No children, or didn't want children converted */ + return; + } + + if (props.inline_container == NULL) { + /* Create inline container if we don't have one */ + props.inline_container = box_create(NULL, NULL, false, + NULL, NULL, NULL, NULL, content->bctx); + if (props.inline_container == NULL) + return; + + props.inline_container->type = BOX_INLINE_CONTAINER; + + box_add_child(props.containing_block, + props.inline_container); + } + + inline_end = box_create(NULL, box->style, false, + box->href, box->target, box->title, + box->id == NULL ? NULL : + lwc_string_ref(box->id), content->bctx); + if (inline_end != NULL) { + inline_end->type = BOX_INLINE_END; + + assert(props.inline_container != NULL); + + box_add_child(props.inline_container, inline_end); + + box->inline_end = inline_end; + inline_end->inline_end = box; + } + } else if (!(box->flags & IS_REPLACED)) { + /* Handle the :after pseudo element */ + box_construct_generate(n, content, box, + box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]); + } +} + +/** + * Construct the box tree for an XML text node. + * + * \param ctx Tree construction context + * \return true on success, false on memory exhaustion + */ + +bool box_construct_text(struct box_construct_ctx *ctx) +{ + struct box_construct_props props; + struct box *box = NULL; + dom_string *content; + dom_exception err; + + assert(ctx->n != NULL); + + box_extract_properties(ctx->n, &props); + + assert(props.containing_block != NULL); + + err = dom_characterdata_get_data(ctx->n, &content); + if (err != DOM_NO_ERR || content == NULL) + return false; + + if (css_computed_white_space(props.parent_style) == + CSS_WHITE_SPACE_NORMAL || + css_computed_white_space(props.parent_style) == + CSS_WHITE_SPACE_NOWRAP) { + char *text; + + text = squash_whitespace(dom_string_data(content)); + + dom_string_unref(content); + + if (text == NULL) + return false; + + /* if the text is just a space, combine it with the preceding + * text node, if any */ + if (text[0] == ' ' && text[1] == 0) { + if (props.inline_container != NULL) { + assert(props.inline_container->last != NULL); + + props.inline_container->last->space = + UNKNOWN_WIDTH; + } + + free(text); + + return true; + } + + if (props.inline_container == NULL) { + /* Child of a block without a current container + * (i.e. this box is the first child of its parent, or + * was preceded by block-level siblings) */ + props.inline_container = box_create(NULL, NULL, false, + NULL, NULL, NULL, NULL, ctx->bctx); + if (props.inline_container == NULL) { + free(text); + return false; + } + + props.inline_container->type = BOX_INLINE_CONTAINER; + + box_add_child(props.containing_block, + props.inline_container); + } + + /** \todo Dropping const here is not clever */ + box = box_create(NULL, + (css_computed_style *) props.parent_style, + false, props.href, props.target, props.title, + NULL, ctx->bctx); + if (box == NULL) { + free(text); + return false; + } + + box->type = BOX_TEXT; + + box->text = talloc_strdup(ctx->bctx, text); + free(text); + if (box->text == NULL) + return false; + + box->length = strlen(box->text); + + /* strip ending space char off */ + if (box->length > 1 && box->text[box->length - 1] == ' ') { + box->space = UNKNOWN_WIDTH; + box->length--; + } + + if (css_computed_text_transform(props.parent_style) != + CSS_TEXT_TRANSFORM_NONE) + box_text_transform(box->text, box->length, + css_computed_text_transform( + props.parent_style)); + + box_add_child(props.inline_container, box); + + if (box->text[0] == ' ') { + box->length--; + + memmove(box->text, &box->text[1], box->length); + + if (box->prev != NULL) + box->prev->space = UNKNOWN_WIDTH; + } + } else { + /* white-space: pre */ + char *text; + size_t text_len = dom_string_byte_length(content); + size_t i; + char *current; + enum css_white_space_e white_space = + css_computed_white_space(props.parent_style); + + /* note: pre-wrap/pre-line are unimplemented */ + assert(white_space == CSS_WHITE_SPACE_PRE || + white_space == CSS_WHITE_SPACE_PRE_LINE || + white_space == CSS_WHITE_SPACE_PRE_WRAP); + + text = malloc(text_len + 1); + dom_string_unref(content); + + if (text == NULL) + return false; + + memcpy(text, dom_string_data(content), text_len); + text[text_len] = '\0'; + + /* TODO: Handle tabs properly */ + for (i = 0; i < text_len; i++) + if (text[i] == '\t') + text[i] = ' '; + + if (css_computed_text_transform(props.parent_style) != + CSS_TEXT_TRANSFORM_NONE) + box_text_transform(text, strlen(text), + css_computed_text_transform( + props.parent_style)); + + current = text; + + /* swallow a single leading new line */ + if (props.containing_block->flags & PRE_STRIP) { + switch (*current) { + case '\n': + current++; + break; + case '\r': + current++; + if (*current == '\n') + current++; + break; + } + props.containing_block->flags &= ~PRE_STRIP; + } + + do { + size_t len = strcspn(current, "\r\n"); + + char old = current[len]; + + current[len] = 0; + + if (props.inline_container == NULL) { + /* Child of a block without a current container + * (i.e. this box is the first child of its + * parent, or was preceded by block-level + * siblings) */ + props.inline_container = box_create(NULL, NULL, + false, NULL, NULL, NULL, NULL, + ctx->bctx); + if (props.inline_container == NULL) { + free(text); + return false; + } + + props.inline_container->type = + BOX_INLINE_CONTAINER; + + box_add_child(props.containing_block, + props.inline_container); + } + + /** \todo Dropping const isn't clever */ + box = box_create(NULL, + (css_computed_style *) props.parent_style, + false, props.href, props.target, props.title, + NULL, ctx->bctx); + if (box == NULL) { + free(text); + return false; + } + + box->type = BOX_TEXT; + + box->text = talloc_strdup(ctx->bctx, current); + if (box->text == NULL) { + free(text); + return false; + } + + box->length = strlen(box->text); + + box_add_child(props.inline_container, box); + + current[len] = old; + + current += len; + + if (current[0] != '\0') { + /* Linebreak: create new inline container */ + props.inline_container = box_create(NULL, NULL, + false, NULL, NULL, NULL, NULL, + ctx->bctx); + if (props.inline_container == NULL) { + free(text); + return false; + } + + props.inline_container->type = + BOX_INLINE_CONTAINER; + + box_add_child(props.containing_block, + props.inline_container); + + if (current[0] == '\r' && current[1] == '\n') + current += 2; + else + current++; + } + } while (*current); + + free(text); + } + + return true; +} + +/** + * Get the style for an element. + * + * \param c content of type CONTENT_HTML that is being processed + * \param parent_style style at this point in xml tree, or NULL for root + * \param root_style root node's style, or NULL for root + * \param n node in xml tree + * \return the new style, or NULL on memory exhaustion + */ +css_select_results *box_get_style(html_content *c, + const css_computed_style *parent_style, + const css_computed_style *root_style, dom_node *n) +{ + dom_string *s; + dom_exception err; + css_stylesheet *inline_style = NULL; + css_select_results *styles; + nscss_select_ctx ctx; + + /* Firstly, construct inline stylesheet, if any */ + err = dom_element_get_attribute(n, corestring_dom_style, &s); + if (err != DOM_NO_ERR) + return NULL; + + if (s != NULL) { + inline_style = nscss_create_inline_style( + (const uint8_t *) dom_string_data(s), + dom_string_byte_length(s), + c->encoding, + nsurl_access(c->base_url), + c->quirks != DOM_DOCUMENT_QUIRKS_MODE_NONE); + + dom_string_unref(s); + + if (inline_style == NULL) + return NULL; + } + + /* Populate selection context */ + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + ctx.root_style = root_style; + ctx.parent_style = parent_style; + + /* Select style for element */ + styles = nscss_get_style(&ctx, n, CSS_MEDIA_SCREEN, inline_style); + + /* No longer need inline style */ + if (inline_style != NULL) + css_stylesheet_destroy(inline_style); + + return styles; +} + + +/** + * Apply the CSS text-transform property to given text for its ASCII chars. + * + * \param s string to transform + * \param len length of s + * \param tt transform type + */ + +void box_text_transform(char *s, unsigned int len, enum css_text_transform_e tt) +{ + unsigned int i; + if (len == 0) + return; + switch (tt) { + case CSS_TEXT_TRANSFORM_UPPERCASE: + for (i = 0; i < len; ++i) + if ((unsigned char) s[i] < 0x80) + s[i] = toupper(s[i]); + break; + case CSS_TEXT_TRANSFORM_LOWERCASE: + for (i = 0; i < len; ++i) + if ((unsigned char) s[i] < 0x80) + s[i] = tolower(s[i]); + break; + case CSS_TEXT_TRANSFORM_CAPITALIZE: + if ((unsigned char) s[0] < 0x80) + s[0] = toupper(s[0]); + for (i = 1; i < len; ++i) + if ((unsigned char) s[i] < 0x80 && + isspace(s[i - 1])) + s[i] = toupper(s[i]); + break; + default: + break; + } +} + + +/** + * \name Special case element handlers + * + * These functions are called by box_construct_element() when an element is + * being converted, according to the entries in element_table. + * + * The parameters are the xmlNode, the content for the document, and a partly + * filled in box structure for the element. + * + * Return true on success, false on memory exhaustion. Set *convert_children + * to false if children of this element in the XML tree should be skipped (for + * example, if they have been processed in some special way already). + * + * Elements ordered as in the HTML 4.01 specification. Section numbers in + * brackets [] refer to the spec. + * + * \{ + */ + +/** + * Document body [7.5.1]. + */ + +bool box_body(BOX_SPECIAL_PARAMS) +{ + css_color color; + + css_computed_background_color(box->style, &color); + if (nscss_color_is_transparent(color)) + content->background_colour = NS_TRANSPARENT; + else + content->background_colour = nscss_color_to_ns(color); + + return true; +} + + +/** + * Forced line break [9.3.2]. + */ + +bool box_br(BOX_SPECIAL_PARAMS) +{ + box->type = BOX_BR; + return true; +} + +/** + * Preformatted text [9.3.4]. + */ + +bool box_pre(BOX_SPECIAL_PARAMS) +{ + box->flags |= PRE_STRIP; + return true; +} + +/** + * Anchor [12.2]. + */ + +bool box_a(BOX_SPECIAL_PARAMS) +{ + bool ok; + nsurl *url; + dom_string *s; + dom_exception err; + + err = dom_element_get_attribute(n, corestring_dom_href, &s); + if (err == DOM_NO_ERR && s != NULL) { + ok = box_extract_link(content, s, content->base_url, &url); + dom_string_unref(s); + if (!ok) + return false; + if (url) { + if (box->href != NULL) + nsurl_unref(box->href); + box->href = url; + } + } + + /* name and id share the same namespace */ + err = dom_element_get_attribute(n, corestring_dom_name, &s); + if (err == DOM_NO_ERR && s != NULL) { + lwc_string *lwc_name; + + err = dom_string_intern(s, &lwc_name); + + dom_string_unref(s); + + if (err == DOM_NO_ERR) { + /* name replaces existing id + * TODO: really? */ + if (box->id != NULL) + lwc_string_unref(box->id); + + box->id = lwc_name; + } + } + + /* target frame [16.3] */ + err = dom_element_get_attribute(n, corestring_dom_target, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__blank)) + box->target = TARGET_BLANK; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__top)) + box->target = TARGET_TOP; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__parent)) + box->target = TARGET_PARENT; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc__self)) + /* the default may have been overridden by a + * , so this is different to 0 */ + box->target = TARGET_SELF; + else { + /* 6.16 says that frame names must begin with [a-zA-Z] + * This doesn't match reality, so just take anything */ + box->target = talloc_strdup(content->bctx, + dom_string_data(s)); + if (!box->target) { + dom_string_unref(s); + return false; + } + } + dom_string_unref(s); + } + + return true; +} + + +/** + * Embedded image [13.2]. + */ + +bool box_image(BOX_SPECIAL_PARAMS) +{ + bool ok; + dom_string *s; + dom_exception err; + nsurl *url; + enum css_width_e wtype; + enum css_height_e htype; + css_fixed value = 0; + css_unit wunit = CSS_UNIT_PX; + css_unit hunit = CSS_UNIT_PX; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + /* handle alt text */ + err = dom_element_get_attribute(n, corestring_dom_alt, &s); + if (err == DOM_NO_ERR && s != NULL) { + char *alt = squash_whitespace(dom_string_data(s)); + dom_string_unref(s); + if (alt == NULL) + return false; + box->text = talloc_strdup(content->bctx, alt); + free(alt); + if (box->text == NULL) + return false; + box->length = strlen(box->text); + } + + if (nsoption_bool(foreground_images) == false) { + return true; + } + + /* imagemap associated with this image */ + if (!box_get_attribute(n, "usemap", content->bctx, &box->usemap)) + return false; + if (box->usemap && box->usemap[0] == '#') + box->usemap++; + + /* get image URL */ + err = dom_element_get_attribute(n, corestring_dom_src, &s); + if (err != DOM_NO_ERR || s == NULL) + return true; + + if (box_extract_link(content, s, content->base_url, &url) == false) { + dom_string_unref(s); + return false; + } + + dom_string_unref(s); + + if (url == NULL) + return true; + + /* start fetch */ + box->flags |= IS_REPLACED; + ok = html_fetch_object(content, url, box, image_types, + content->base.available_width, 1000, false); + nsurl_unref(url); + + wtype = css_computed_width(box->style, &value, &wunit); + htype = css_computed_height(box->style, &value, &hunit); + + if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT && + htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT) { + /* We know the dimensions the image will be shown at before it's + * fetched. */ + box->flags |= REPLACE_DIM; + } + + return ok; +} + + +/** + * Noscript element + */ + +bool box_noscript(BOX_SPECIAL_PARAMS) +{ + /* If scripting is enabled, do not display the contents of noscript */ + if (content->enable_scripting) + *convert_children = false; + + return true; +} + + +/** + * Destructor for object_params, for <object> elements + * + * \param o The object params being destroyed. + * \return 0 to allow talloc to continue destroying the tree. + */ +static int box_object_talloc_destructor(struct object_params *o) +{ + if (o->codebase != NULL) + nsurl_unref(o->codebase); + if (o->classid != NULL) + nsurl_unref(o->classid); + if (o->data != NULL) + nsurl_unref(o->data); + + return 0; +} + +/** + * Generic embedded object [13.3]. + */ + +bool box_object(BOX_SPECIAL_PARAMS) +{ + struct object_params *params; + struct object_param *param; + dom_string *codebase, *classid, *data; + dom_node *c; + dom_exception err; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) == + false) + return false; + if (box->usemap && box->usemap[0] == '#') + box->usemap++; + + params = talloc(content->bctx, struct object_params); + if (params == NULL) + return false; + + talloc_set_destructor(params, box_object_talloc_destructor); + + params->data = NULL; + params->type = NULL; + params->codetype = NULL; + params->codebase = NULL; + params->classid = NULL; + params->params = NULL; + + /* codebase, classid, and data are URLs + * (codebase is the base for the other two) */ + err = dom_element_get_attribute(n, corestring_dom_codebase, &codebase); + if (err == DOM_NO_ERR && codebase != NULL) { + if (box_extract_link(content, codebase, content->base_url, + ¶ms->codebase) == false) { + dom_string_unref(codebase); + return false; + } + dom_string_unref(codebase); + } + if (params->codebase == NULL) + params->codebase = nsurl_ref(content->base_url); + + err = dom_element_get_attribute(n, corestring_dom_classid, &classid); + if (err == DOM_NO_ERR && classid != NULL) { + if (box_extract_link(content, classid, + params->codebase, ¶ms->classid) == false) { + dom_string_unref(classid); + return false; + } + dom_string_unref(classid); + } + + err = dom_element_get_attribute(n, corestring_dom_data, &data); + if (err == DOM_NO_ERR && data != NULL) { + if (box_extract_link(content, data, + params->codebase, ¶ms->data) == false) { + dom_string_unref(data); + return false; + } + dom_string_unref(data); + } + + if (params->classid == NULL && params->data == NULL) + /* nothing to embed; ignore */ + return true; + + /* Don't include ourself */ + if (params->classid != NULL && nsurl_compare(content->base_url, + params->classid, NSURL_COMPLETE)) + return true; + + if (params->data != NULL && nsurl_compare(content->base_url, + params->data, NSURL_COMPLETE)) + return true; + + /* codetype and type are MIME types */ + if (box_get_attribute(n, "codetype", params, + ¶ms->codetype) == false) + return false; + if (box_get_attribute(n, "type", params, ¶ms->type) == false) + return false; + + /* classid && !data => classid is used (consult codetype) + * (classid || !classid) && data => data is used (consult type) + * !classid && !data => invalid; ignored */ + + if (params->classid != NULL && params->data == NULL && + params->codetype != NULL) { + lwc_string *icodetype; + lwc_error lerror; + + lerror = lwc_intern_string(params->codetype, + strlen(params->codetype), &icodetype); + if (lerror != lwc_error_ok) + return false; + + if (content_factory_type_from_mime_type(icodetype) == + CONTENT_NONE) { + /* can't handle this MIME type */ + lwc_string_unref(icodetype); + return true; + } + + lwc_string_unref(icodetype); + } + + if (params->data != NULL && params->type != NULL) { + lwc_string *itype; + lwc_error lerror; + + lerror = lwc_intern_string(params->type, strlen(params->type), + &itype); + if (lerror != lwc_error_ok) + return false; + + if (content_factory_type_from_mime_type(itype) == + CONTENT_NONE) { + /* can't handle this MIME type */ + lwc_string_unref(itype); + return true; + } + + lwc_string_unref(itype); + } + + /* add parameters to linked list */ + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) + return false; + + while (c != NULL) { + dom_node *next; + dom_node_type type; + + err = dom_node_get_node_type(c, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (type == DOM_ELEMENT_NODE) { + dom_string *name; + + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (!dom_string_caseless_lwc_isequal(name, + corestring_lwc_param)) { + /* The first non-param child is the start of + * the alt html. Therefore, we should break + * out of this loop. */ + dom_node_unref(c); + break; + } + + param = talloc(params, struct object_param); + if (param == NULL) { + dom_node_unref(c); + return false; + } + param->name = NULL; + param->value = NULL; + param->type = NULL; + param->valuetype = NULL; + param->next = NULL; + + if (box_get_attribute(c, "name", param, + ¶m->name) == false) { + dom_node_unref(c); + return false; + } + + if (box_get_attribute(c, "value", param, + ¶m->value) == false) { + dom_node_unref(c); + return false; + } + + if (box_get_attribute(c, "type", param, + ¶m->type) == false) { + dom_node_unref(c); + return false; + } + + if (box_get_attribute(c, "valuetype", param, + ¶m->valuetype) == false) { + dom_node_unref(c); + return false; + } + + if (param->valuetype == NULL) { + param->valuetype = talloc_strdup(param, "data"); + if (param->valuetype == NULL) { + dom_node_unref(c); + return false; + } + } + + param->next = params->params; + params->params = param; + } + + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + dom_node_unref(c); + c = next; + } + + box->object_params = params; + + /* start fetch (MIME type is ok or not specified) */ + box->flags |= IS_REPLACED; + if (!html_fetch_object(content, + params->data ? params->data : params->classid, + box, CONTENT_ANY, content->base.available_width, 1000, + false)) + return false; + + *convert_children = false; + return true; +} + + +/** + * Window subdivision [16.2.1]. + */ + +bool box_frameset(BOX_SPECIAL_PARAMS) +{ + bool ok; + + if (content->frameset) { + NSLOG(netsurf, INFO, "Error: multiple framesets in document."); + /* Don't convert children */ + if (convert_children) + *convert_children = false; + /* And ignore this spurious frameset */ + box->type = BOX_NONE; + return true; + } + + content->frameset = talloc_zero(content->bctx, struct content_html_frames); + if (!content->frameset) + return false; + + ok = box_create_frameset(content->frameset, n, content); + if (ok) + box->type = BOX_NONE; + + if (convert_children) + *convert_children = false; + return ok; +} + + +/** + * Destructor for content_html_frames, for frame elements + * + * \param f The frame params being destroyed. + * \return 0 to allow talloc to continue destroying the tree. + */ +static int box_frames_talloc_destructor(struct content_html_frames *f) +{ + if (f->url != NULL) { + nsurl_unref(f->url); + f->url = NULL; + } + + return 0; +} + + +/** + * Parse a multi-length-list, as defined by HTML 4.01. + * + * \param ds dom string to parse + * \param count updated to number of entries + * \return array of struct box_multi_length, or 0 on memory exhaustion + */ +static struct frame_dimension * +box_parse_multi_lengths(const dom_string *ds, unsigned int *count) +{ + char *end; + unsigned int i, n; + struct frame_dimension *length; + const char *s; + + s = dom_string_data(ds); + + for (i = 0, n = 1; s[i]; i++) + if (s[i] == ',') + n++; + + length = calloc(n, sizeof(struct frame_dimension)); + if (!length) + return NULL; + + for (i = 0; i != n; i++) { + while (ascii_is_space(*s)) { + s++; + } + length[i].value = strtof(s, &end); + if (length[i].value <= 0) { + length[i].value = 1; + } + s = end; + switch (*s) { + case '%': + length[i].unit = FRAME_DIMENSION_PERCENT; + break; + case '*': + length[i].unit = FRAME_DIMENSION_RELATIVE; + break; + default: + length[i].unit = FRAME_DIMENSION_PIXELS; + break; + } + while (*s && *s != ',') { + s++; + } + if (*s == ',') { + s++; + } + } + + *count = n; + return length; +} + + +bool box_create_frameset(struct content_html_frames *f, dom_node *n, + html_content *content) { + unsigned int row, col, index, i; + unsigned int rows = 1, cols = 1; + dom_string *s; + dom_exception err; + nsurl *url; + struct frame_dimension *row_height = 0, *col_width = 0; + dom_node *c, *next; + struct content_html_frames *frame; + bool default_border = true; + colour default_border_colour = 0x000000; + + /* parse rows and columns */ + err = dom_element_get_attribute(n, corestring_dom_rows, &s); + if (err == DOM_NO_ERR && s != NULL) { + row_height = box_parse_multi_lengths(s, &rows); + dom_string_unref(s); + if (row_height == NULL) + return false; + } else { + row_height = calloc(1, sizeof(struct frame_dimension)); + if (row_height == NULL) + return false; + row_height->value = 100; + row_height->unit = FRAME_DIMENSION_PERCENT; + } + + err = dom_element_get_attribute(n, corestring_dom_cols, &s); + if (err == DOM_NO_ERR && s != NULL) { + col_width = box_parse_multi_lengths(s, &cols); + dom_string_unref(s); + if (col_width == NULL) { + free(row_height); + return false; + } + } else { + col_width = calloc(1, sizeof(struct frame_dimension)); + if (col_width == NULL) { + free(row_height); + return false; + } + col_width->value = 100; + col_width->unit = FRAME_DIMENSION_PERCENT; + } + + /* common extension: border="0|1" to control all children */ + err = dom_element_get_attribute(n, corestring_dom_border, &s); + if (err == DOM_NO_ERR && s != NULL) { + if ((dom_string_data(s)[0] == '0') && + (dom_string_data(s)[1] == '\0')) + default_border = false; + dom_string_unref(s); + } + + /* common extension: frameborder="yes|no" to control all children */ + err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no) == 0) + default_border = false; + dom_string_unref(s); + } + + /* common extension: bordercolor="#RRGGBB|" to control + *all children */ + err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), &color)) + default_border_colour = nscss_color_to_ns(color); + + dom_string_unref(s); + } + + /* update frameset and create default children */ + f->cols = cols; + f->rows = rows; + f->scrolling = BW_SCROLLING_NO; + f->children = talloc_array(content->bctx, struct content_html_frames, + (rows * cols)); + + talloc_set_destructor(f->children, box_frames_talloc_destructor); + + for (row = 0; row < rows; row++) { + for (col = 0; col < cols; col++) { + index = (row * cols) + col; + frame = &f->children[index]; + frame->cols = 0; + frame->rows = 0; + frame->width = col_width[col]; + frame->height = row_height[row]; + frame->margin_width = 0; + frame->margin_height = 0; + frame->name = NULL; + frame->url = NULL; + frame->no_resize = false; + frame->scrolling = BW_SCROLLING_AUTO; + frame->border = default_border; + frame->border_colour = default_border_colour; + frame->children = NULL; + } + } + free(col_width); + free(row_height); + + /* create the frameset windows */ + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) + return false; + + for (row = 0; c != NULL && row < rows; row++) { + for (col = 0; c != NULL && col < cols; col++) { + while (c != NULL) { + dom_node_type type; + dom_string *name; + + err = dom_node_get_node_type(c, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (type != DOM_ELEMENT_NODE || + (!dom_string_caseless_lwc_isequal( + name, + corestring_lwc_frame) && + !dom_string_caseless_lwc_isequal( + name, + corestring_lwc_frameset + ))) { + err = dom_node_get_next_sibling(c, + &next); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + dom_node_unref(c); + return false; + } + + dom_string_unref(name); + dom_node_unref(c); + c = next; + } else { + /* Got a FRAME or FRAMESET element */ + dom_string_unref(name); + break; + } + } + + if (c == NULL) + break; + + /* get current frame */ + index = (row * cols) + col; + frame = &f->children[index]; + + /* nest framesets */ + err = dom_node_get_node_name(c, &s); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_frameset)) { + dom_string_unref(s); + frame->border = 0; + if (box_create_frameset(frame, c, + content) == false) { + dom_node_unref(c); + return false; + } + + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + dom_node_unref(c); + c = next; + continue; + } + + dom_string_unref(s); + + /* get frame URL (not required) */ + url = NULL; + err = dom_element_get_attribute(c, corestring_dom_src, &s); + if (err == DOM_NO_ERR && s != NULL) { + box_extract_link(content, s, content->base_url, + &url); + dom_string_unref(s); + } + + /* copy url */ + if (url != NULL) { + /* no self-references */ + if (nsurl_compare(content->base_url, url, + NSURL_COMPLETE) == false) + frame->url = url; + url = NULL; + } + + /* fill in specified values */ + err = dom_element_get_attribute(c, corestring_dom_name, &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->name = talloc_strdup(content->bctx, + dom_string_data(s)); + dom_string_unref(s); + } + + dom_element_has_attribute(c, corestring_dom_noresize, + &frame->no_resize); + + err = dom_element_get_attribute(c, corestring_dom_frameborder, + &s); + if (err == DOM_NO_ERR && s != NULL) { + i = atoi(dom_string_data(s)); + frame->border = (i != 0); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_scrolling, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_yes)) + frame->scrolling = BW_SCROLLING_YES; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no)) + frame->scrolling = BW_SCROLLING_NO; + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_marginwidth, + &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->margin_width = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_marginheight, + &s); + if (err == DOM_NO_ERR && s != NULL) { + frame->margin_height = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(c, corestring_dom_bordercolor, + &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), + &color)) + frame->border_colour = + nscss_color_to_ns(color); + + dom_string_unref(s); + } + + /* advance */ + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + return false; + } + + dom_node_unref(c); + c = next; + } + } + + /* If the last child wasn't a frame, we still need to unref it */ + if (c != NULL) { + dom_node_unref(c); + } + + return true; +} + + +/** + * Destructor for content_html_iframe, for <iframe> elements + * + * \param f The iframe params being destroyed. + * \return 0 to allow talloc to continue destroying the tree. + */ +static int box_iframes_talloc_destructor(struct content_html_iframe *f) +{ + if (f->url != NULL) { + nsurl_unref(f->url); + f->url = NULL; + } + + return 0; +} + + +/** + * Inline subwindow [16.5]. + */ + +bool box_iframe(BOX_SPECIAL_PARAMS) +{ + nsurl *url; + dom_string *s; + dom_exception err; + struct content_html_iframe *iframe; + int i; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + if (box->style && css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN) + /* Don't create iframe discriptors for invisible iframes + * TODO: handle hidden iframes at browser_window generation + * time instead? */ + return true; + + /* get frame URL */ + err = dom_element_get_attribute(n, corestring_dom_src, &s); + if (err != DOM_NO_ERR || s == NULL) + return true; + if (box_extract_link(content, s, content->base_url, &url) == false) { + dom_string_unref(s); + return false; + } + dom_string_unref(s); + if (url == NULL) + return true; + + /* don't include ourself */ + if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) { + nsurl_unref(url); + return true; + } + + /* create a new iframe */ + iframe = talloc(content->bctx, struct content_html_iframe); + if (iframe == NULL) { + nsurl_unref(url); + return false; + } + + talloc_set_destructor(iframe, box_iframes_talloc_destructor); + + iframe->box = box; + iframe->margin_width = 0; + iframe->margin_height = 0; + iframe->name = NULL; + iframe->url = url; + iframe->scrolling = BW_SCROLLING_AUTO; + iframe->border = true; + + /* Add this iframe to the linked list of iframes */ + iframe->next = content->iframe; + content->iframe = iframe; + + /* fill in specified values */ + err = dom_element_get_attribute(n, corestring_dom_name, &s); + if (err == DOM_NO_ERR && s != NULL) { + iframe->name = talloc_strdup(content->bctx, dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); + if (err == DOM_NO_ERR && s != NULL) { + i = atoi(dom_string_data(s)); + iframe->border = (i != 0); + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); + if (err == DOM_NO_ERR && s != NULL) { + css_color color; + + if (nscss_parse_colour(dom_string_data(s), &color)) + iframe->border_colour = nscss_color_to_ns(color); + + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_scrolling, &s); + if (err == DOM_NO_ERR && s != NULL) { + if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_yes)) + iframe->scrolling = BW_SCROLLING_YES; + else if (dom_string_caseless_lwc_isequal(s, + corestring_lwc_no)) + iframe->scrolling = BW_SCROLLING_NO; + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_marginwidth, &s); + if (err == DOM_NO_ERR && s != NULL) { + iframe->margin_width = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + err = dom_element_get_attribute(n, corestring_dom_marginheight, &s); + if (err == DOM_NO_ERR && s != NULL) { + iframe->margin_height = atoi(dom_string_data(s)); + dom_string_unref(s); + } + + /* box */ + assert(box->style); + box->flags |= IFRAME; + box->flags |= IS_REPLACED; + + /* Showing iframe, so don't show alternate content */ + if (convert_children) + *convert_children = false; + return true; +} + + +/** + * Helper function for adding textarea widget to box. + * + * This is a load of hacks to ensure boxes replaced with textareas + * can be handled by the layout code. + */ + +static bool box_input_text(html_content *html, struct box *box, + struct dom_node *node) +{ + struct box *inline_container, *inline_box; + + box->type = BOX_INLINE_BLOCK; + + inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, html->bctx); + if (!inline_container) + return false; + inline_container->type = BOX_INLINE_CONTAINER; + inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, + html->bctx); + if (!inline_box) + return false; + inline_box->type = BOX_TEXT; + inline_box->text = talloc_strdup(html->bctx, ""); + + box_add_child(inline_container, inline_box); + box_add_child(box, inline_container); + + return box_textarea_create_textarea(html, box, node); +} + + +/** + * Form control [17.4]. + */ + +bool box_input(BOX_SPECIAL_PARAMS) +{ + struct form_control *gadget = NULL; + dom_string *type = NULL; + dom_exception err; + nsurl *url; + nserror error; + + dom_element_get_attribute(n, corestring_dom_type, &type); + + gadget = html_forms_get_control_for_node(content->forms, n); + if (gadget == NULL) + goto no_memory; + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; + gadget->html = content; + + if (type && dom_string_caseless_lwc_isequal(type, + corestring_lwc_password)) { + if (box_input_text(content, box, n) == false) + goto no_memory; + + } else if (type && dom_string_caseless_lwc_isequal(type, + corestring_lwc_file)) { + box->type = BOX_INLINE_BLOCK; + + } else if (type && dom_string_caseless_lwc_isequal(type, + corestring_lwc_hidden)) { + /* no box for hidden inputs */ + box->type = BOX_NONE; + + } else if (type && + (dom_string_caseless_lwc_isequal(type, + corestring_lwc_checkbox) || + dom_string_caseless_lwc_isequal(type, + corestring_lwc_radio))) { + + } else if (type && + (dom_string_caseless_lwc_isequal(type, + corestring_lwc_submit) || + dom_string_caseless_lwc_isequal(type, + corestring_lwc_reset) || + dom_string_caseless_lwc_isequal(type, + corestring_lwc_button))) { + struct box *inline_container, *inline_box; + + if (box_button(n, content, box, 0) == false) + goto no_memory; + + inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, + content->bctx); + if (inline_container == NULL) + goto no_memory; + + inline_container->type = BOX_INLINE_CONTAINER; + + inline_box = box_create(NULL, box->style, false, 0, 0, + box->title, 0, content->bctx); + if (inline_box == NULL) + goto no_memory; + + inline_box->type = BOX_TEXT; + + if (box->gadget->value != NULL) + inline_box->text = talloc_strdup(content->bctx, + box->gadget->value); + else if (box->gadget->type == GADGET_SUBMIT) + inline_box->text = talloc_strdup(content->bctx, + messages_get("Form_Submit")); + else if (box->gadget->type == GADGET_RESET) + inline_box->text = talloc_strdup(content->bctx, + messages_get("Form_Reset")); + else + inline_box->text = talloc_strdup(content->bctx, + "Button"); + + if (inline_box->text == NULL) + goto no_memory; + + inline_box->length = strlen(inline_box->text); + + box_add_child(inline_container, inline_box); + + box_add_child(box, inline_container); + + } else if (type && dom_string_caseless_lwc_isequal(type, + corestring_lwc_image)) { + gadget->type = GADGET_IMAGE; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) != CSS_DISPLAY_NONE && + nsoption_bool(foreground_images) == true) { + dom_string *s; + + err = dom_element_get_attribute(n, corestring_dom_src, &s); + if (err == DOM_NO_ERR && s != NULL) { + error = nsurl_join(content->base_url, + dom_string_data(s), &url); + dom_string_unref(s); + if (error != NSERROR_OK) + goto no_memory; + + /* if url is equivalent to the parent's url, + * we've got infinite inclusion. stop it here + */ + if (nsurl_compare(url, content->base_url, + NSURL_COMPLETE) == false) { + if (!html_fetch_object(content, url, + box, image_types, + content->base. + available_width, + 1000, false)) { + nsurl_unref(url); + goto no_memory; + } + } + nsurl_unref(url); + } + } + } else { + /* the default type is "text" */ + if (box_input_text(content, box, n) == false) + goto no_memory; + } + + if (type) + dom_string_unref(type); + + *convert_children = false; + return true; + +no_memory: + if (type) + dom_string_unref(type); + + return false; +} + + +/** + * Push button [17.5]. + */ + +bool box_button(BOX_SPECIAL_PARAMS) +{ + struct form_control *gadget; + + gadget = html_forms_get_control_for_node(content->forms, n); + if (!gadget) + return false; + + gadget->html = content; + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; + + box->type = BOX_INLINE_BLOCK; + + /* Just render the contents */ + + return true; +} + + +/** + * Option selector [17.6]. + */ + +bool box_select(BOX_SPECIAL_PARAMS) +{ + struct box *inline_container; + struct box *inline_box; + struct form_control *gadget; + dom_node *c, *c2; + dom_node *next, *next2; + dom_exception err; + + gadget = html_forms_get_control_for_node(content->forms, n); + if (gadget == NULL) + return false; + + gadget->html = content; + err = dom_node_get_first_child(n, &c); + if (err != DOM_NO_ERR) { + form_free_control(gadget); + return false; + } + + while (c != NULL) { + dom_string *name; + + err = dom_node_get_node_name(c, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } + + if (dom_string_caseless_lwc_isequal(name, + corestring_lwc_option)) { + dom_string_unref(name); + + if (box_select_add_option(gadget, c) == false) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } + } else if (dom_string_caseless_lwc_isequal(name, + corestring_lwc_optgroup)) { + dom_string_unref(name); + + err = dom_node_get_first_child(c, &c2); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } + + while (c2 != NULL) { + dom_string *c2_name; + + err = dom_node_get_node_name(c2, &c2_name); + if (err != DOM_NO_ERR) { + dom_node_unref(c2); + dom_node_unref(c); + form_free_control(gadget); + return false; + } + + if (dom_string_caseless_lwc_isequal(c2_name, + corestring_lwc_option)) { + dom_string_unref(c2_name); + + if (box_select_add_option(gadget, + c2) == false) { + dom_node_unref(c2); + dom_node_unref(c); + return false; + } + } else { + dom_string_unref(c2_name); + } + + err = dom_node_get_next_sibling(c2, &next2); + if (err != DOM_NO_ERR) { + dom_node_unref(c2); + dom_node_unref(c); + return false; + } + + dom_node_unref(c2); + c2 = next2; + } + } else { + dom_string_unref(name); + } + + err = dom_node_get_next_sibling(c, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(c); + form_free_control(gadget); + return false; + } + + dom_node_unref(c); + c = next; + } + + if (gadget->data.select.num_items == 0) { + /* no options: ignore entire select */ + form_free_control(gadget); + return true; + } + + box->type = BOX_INLINE_BLOCK; + box->gadget = gadget; + box->flags |= IS_REPLACED; + gadget->box = box; + + inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx); + if (inline_container == NULL) + goto no_memory; + inline_container->type = BOX_INLINE_CONTAINER; + inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, + content->bctx); + if (inline_box == NULL) + goto no_memory; + inline_box->type = BOX_TEXT; + box_add_child(inline_container, inline_box); + box_add_child(box, inline_container); + + if (gadget->data.select.multiple == false && + gadget->data.select.num_selected == 0) { + gadget->data.select.current = gadget->data.select.items; + gadget->data.select.current->initial_selected = + gadget->data.select.current->selected = true; + gadget->data.select.num_selected = 1; + dom_html_option_element_set_selected( + gadget->data.select.current->node, true); + } + + if (gadget->data.select.num_selected == 0) + inline_box->text = talloc_strdup(content->bctx, + messages_get("Form_None")); + else if (gadget->data.select.num_selected == 1) + inline_box->text = talloc_strdup(content->bctx, + gadget->data.select.current->text); + else + inline_box->text = talloc_strdup(content->bctx, + messages_get("Form_Many")); + if (inline_box->text == NULL) + goto no_memory; + + inline_box->length = strlen(inline_box->text); + + *convert_children = false; + return true; + +no_memory: + return false; +} + + +/** + * Add an option to a form select control (helper function for box_select()). + * + * \param control select containing the <option> + * \param n xml element node for <option> + * \return true on success, false on memory exhaustion + */ + +bool box_select_add_option(struct form_control *control, dom_node *n) +{ + char *value = NULL; + char *text = NULL; + char *text_nowrap = NULL; + bool selected; + dom_string *content, *s; + dom_exception err; + + err = dom_node_get_text_content(n, &content); + if (err != DOM_NO_ERR) + return false; + + if (content != NULL) { + text = squash_whitespace(dom_string_data(content)); + dom_string_unref(content); + } else { + text = strdup(""); + } + + if (text == NULL) + goto no_memory; + + err = dom_element_get_attribute(n, corestring_dom_value, &s); + if (err == DOM_NO_ERR && s != NULL) { + value = strdup(dom_string_data(s)); + dom_string_unref(s); + } else { + value = strdup(text); + } + + if (value == NULL) + goto no_memory; + + dom_element_has_attribute(n, corestring_dom_selected, &selected); + + /* replace spaces/TABs with hard spaces to prevent line wrapping */ + text_nowrap = cnv_space2nbsp(text); + if (text_nowrap == NULL) + goto no_memory; + + if (form_add_option(control, value, text_nowrap, selected, n) == false) + goto no_memory; + + free(text); + + return true; + +no_memory: + free(value); + free(text); + free(text_nowrap); + return false; +} + + +/** + * Multi-line text field [17.7]. + */ + +bool box_textarea(BOX_SPECIAL_PARAMS) +{ + /* Get the form_control for the DOM node */ + box->gadget = html_forms_get_control_for_node(content->forms, n); + if (box->gadget == NULL) + return false; + + box->flags |= IS_REPLACED; + box->gadget->html = content; + box->gadget->box = box; + + if (!box_input_text(content, box, n)) + return false; + + *convert_children = false; + return true; +} + + +/** + * Embedded object (not in any HTML specification: + * see http://wp.netscape.com/assist/net_sites/new_html3_prop.html ) + */ + +bool box_embed(BOX_SPECIAL_PARAMS) +{ + struct object_params *params; + struct object_param *param; + dom_namednodemap *attrs; + unsigned long idx; + uint32_t num_attrs; + dom_string *src; + dom_exception err; + + if (box->style && ns_computed_display(box->style, + box_is_root(n)) == CSS_DISPLAY_NONE) + return true; + + params = talloc(content->bctx, struct object_params); + if (params == NULL) + return false; + + talloc_set_destructor(params, box_object_talloc_destructor); + + params->data = NULL; + params->type = NULL; + params->codetype = NULL; + params->codebase = NULL; + params->classid = NULL; + params->params = NULL; + + /* src is a URL */ + err = dom_element_get_attribute(n, corestring_dom_src, &src); + if (err != DOM_NO_ERR || src == NULL) + return true; + if (box_extract_link(content, src, content->base_url, + ¶ms->data) == false) { + dom_string_unref(src); + return false; + } + + dom_string_unref(src); + + if (params->data == NULL) + return true; + + /* Don't include ourself */ + if (nsurl_compare(content->base_url, params->data, NSURL_COMPLETE)) + return true; + + /* add attributes as parameters to linked list */ + err = dom_node_get_attributes(n, &attrs); + if (err != DOM_NO_ERR) + return false; + + err = dom_namednodemap_get_length(attrs, &num_attrs); + if (err != DOM_NO_ERR) { + dom_namednodemap_unref(attrs); + return false; + } + + for (idx = 0; idx < num_attrs; idx++) { + dom_attr *attr; + dom_string *name, *value; + + err = dom_namednodemap_item(attrs, idx, (void *) &attr); + if (err != DOM_NO_ERR) { + dom_namednodemap_unref(attrs); + return false; + } + + err = dom_attr_get_name(attr, &name); + if (err != DOM_NO_ERR) { + dom_namednodemap_unref(attrs); + return false; + } + + if (dom_string_caseless_lwc_isequal(name, corestring_lwc_src)) { + dom_string_unref(name); + continue; + } + + err = dom_attr_get_value(attr, &value); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + dom_namednodemap_unref(attrs); + return false; + } + + param = talloc(content->bctx, struct object_param); + if (param == NULL) { + dom_string_unref(value); + dom_string_unref(name); + dom_namednodemap_unref(attrs); + return false; + } + + param->name = talloc_strdup(content->bctx, dom_string_data(name)); + param->value = talloc_strdup(content->bctx, dom_string_data(value)); + param->type = NULL; + param->valuetype = talloc_strdup(content->bctx, "data"); + param->next = NULL; + + dom_string_unref(value); + dom_string_unref(name); + + if (param->name == NULL || param->value == NULL || + param->valuetype == NULL) { + dom_namednodemap_unref(attrs); + return false; + } + + param->next = params->params; + params->params = param; + } + + dom_namednodemap_unref(attrs); + + box->object_params = params; + + /* start fetch */ + box->flags |= IS_REPLACED; + return html_fetch_object(content, params->data, box, CONTENT_ANY, + content->base.available_width, 1000, false); +} + +/** + * \} + */ + + +/** + * Get the value of an XML element's attribute. + * + * \param n xmlNode, of type XML_ELEMENT_NODE + * \param attribute name of attribute + * \param context talloc context for result buffer + * \param value updated to value, if the attribute is present + * \return true on success, false if attribute present but memory exhausted + * + * Note that returning true does not imply that the attribute was found. If the + * attribute was not found, *value will be unchanged. + */ + +bool box_get_attribute(dom_node *n, const char *attribute, + void *context, char **value) +{ + char *result; + dom_string *attr, *attr_name; + dom_exception err; + + err = dom_string_create_interned((const uint8_t *) attribute, + strlen(attribute), &attr_name); + if (err != DOM_NO_ERR) + return false; + + err = dom_element_get_attribute(n, attr_name, &attr); + if (err != DOM_NO_ERR) { + dom_string_unref(attr_name); + return false; + } + + dom_string_unref(attr_name); + + if (attr != NULL) { + result = talloc_strdup(context, dom_string_data(attr)); + + dom_string_unref(attr); + + if (result == NULL) + return false; + + *value = result; + } + + return true; +} + + +/* exported function documented in html/box.h */ +bool +box_extract_link(const html_content *content, + const dom_string *dsrel, + nsurl *base, + nsurl **result) +{ + char *s, *s1, *apos0 = 0, *apos1 = 0, *quot0 = 0, *quot1 = 0; + unsigned int i, j, end; + nserror error; + const char *rel; + + rel = dom_string_data(dsrel); + + s1 = s = malloc(3 * strlen(rel) + 1); + if (!s) + return false; + + /* copy to s, removing white space and control characters */ + for (i = 0; rel[i] && ascii_is_space(rel[i]); i++) + ; + for (end = strlen(rel); + (end != i) && ascii_is_space(rel[end - 1]); + end--) + ; + for (j = 0; i != end; i++) { + if ((unsigned char) rel[i] < 0x20) { + ; /* skip control characters */ + } else if (rel[i] == ' ') { + s[j++] = '%'; + s[j++] = '2'; + s[j++] = '0'; + } else { + s[j++] = rel[i]; + } + } + s[j] = 0; + + if (content->enable_scripting == false) { + /* extract first quoted string out of "javascript:" link */ + if (strncmp(s, "javascript:", 11) == 0) { + apos0 = strchr(s, '\''); + if (apos0) + apos1 = strchr(apos0 + 1, '\''); + quot0 = strchr(s, '"'); + if (quot0) + quot1 = strchr(quot0 + 1, '"'); + if (apos0 && apos1 && + (!quot0 || !quot1 || apos0 < quot0)) { + *apos1 = 0; + s1 = apos0 + 1; + } else if (quot0 && quot1) { + *quot1 = 0; + s1 = quot0 + 1; + } + } + } + + /* construct absolute URL */ + error = nsurl_join(base, s1, result); + free(s); + if (error != NSERROR_OK) { + *result = NULL; + return false; + } + + return true; +} diff --git a/content/handlers/html/box_normalise.c b/content/handlers/html/box_normalise.c new file mode 100644 index 000000000..7155cb722 --- /dev/null +++ b/content/handlers/html/box_normalise.c @@ -0,0 +1,1047 @@ +/* + * Copyright 2005 James Bursa + * Copyright 2003 Phil Mellor + * Copyright 2005 John M Bell + * Copyright 2004 Kevin Bagust + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Box tree normalisation (implementation). + */ + +#include +#include +#include + +#include "utils/log.h" +#include "utils/errors.h" +#include "css/select.h" + +#include "html/box.h" +#include "html/html_internal.h" +#include "html/table.h" + +/* Define to enable box normalise debug */ +#undef BOX_NORMALISE_DEBUG + +/** + * Row spanning information for a cell + */ +struct span_info { + /** Number of rows this cell spans */ + unsigned int row_span; + /** Row group of cell */ + struct box *rg; + /** The cell in this column spans all rows until the end of the table */ + bool auto_row; +}; + +/** + * Column record for a table + */ +struct columns { + /** Current column index */ + unsigned int current_column; + /** Number of columns in main part of table 1..max columns */ + unsigned int num_columns; + /** Information about columns in main table, array [0, num_columns) */ + struct span_info *spans; + /** Number of rows in table */ + unsigned int num_rows; +}; + + +static bool box_normalise_table( + struct box *table, + const struct box *root, + html_content *c); +static bool box_normalise_table_spans( + struct box *table, + const struct box *root, + struct span_info *spans, + html_content *c); +static bool box_normalise_table_row_group( + struct box *row_group, + const struct box *root, + struct columns *col_info, + html_content *c); +static bool box_normalise_table_row( + struct box *row, + const struct box *root, + struct columns *col_info, + html_content *c); +static bool calculate_table_row(struct columns *col_info, + unsigned int col_span, unsigned int row_span, + unsigned int *start_column, struct box *cell); +static bool box_normalise_inline_container( + struct box *cont, + const struct box *root, + html_content *c); + +/** + * Ensure the box tree is correctly nested by adding and removing nodes. + * + * \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL + * \param root root box of document + * \param c content of boxes + * \return true on success, false on memory exhaustion + * + * The tree is modified to satisfy the following: + * \code + * parent permitted child nodes + * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE + * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT + * INLINE, TEXT none + * TABLE at least 1 TABLE_ROW_GROUP + * TABLE_ROW_GROUP at least 1 TABLE_ROW + * TABLE_ROW at least 1 TABLE_CELL + * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK) + * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE + * \endcode + */ + +bool box_normalise_block( + struct box *block, + const struct box *root, + html_content *c) +{ + struct box *child; + struct box *next_child; + struct box *table; + css_computed_style *style; + nscss_select_ctx ctx; + + assert(block != NULL); + assert(root != NULL); + + ctx.root_style = root->style; + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "block %p, block->type %u", block, block->type); +#endif + + assert(block->type == BOX_BLOCK || block->type == BOX_INLINE_BLOCK || + block->type == BOX_TABLE_CELL); + + for (child = block->children; child != NULL; child = next_child) { +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "child %p, child->type = %d", child, + child->type); +#endif + + next_child = child->next; /* child may be destroyed */ + + switch (child->type) { + case BOX_BLOCK: + /* ok */ + if (box_normalise_block(child, root, c) == false) + return false; + break; + case BOX_INLINE_CONTAINER: + if (box_normalise_inline_container(child, root, c) == false) + return false; + break; + case BOX_TABLE: + if (box_normalise_table(child, root, c) == false) + return false; + break; + case BOX_INLINE: + case BOX_INLINE_END: + case BOX_INLINE_BLOCK: + case BOX_FLOAT_LEFT: + case BOX_FLOAT_RIGHT: + case BOX_BR: + case BOX_TEXT: + /* should have been wrapped in inline + container by convert_xml_to_box() */ + assert(0); + break; + case BOX_TABLE_ROW_GROUP: + case BOX_TABLE_ROW: + case BOX_TABLE_CELL: + /* insert implied table */ + assert(block->style != NULL); + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, block->style); + if (style == NULL) + return false; + + table = box_create(NULL, style, true, block->href, + block->target, NULL, NULL, c->bctx); + if (table == NULL) { + css_computed_style_destroy(style); + return false; + } + table->type = BOX_TABLE; + + if (child->prev == NULL) + block->children = table; + else + child->prev->next = table; + + table->prev = child->prev; + + while (child != NULL && ( + child->type == BOX_TABLE_ROW_GROUP || + child->type == BOX_TABLE_ROW || + child->type == BOX_TABLE_CELL)) { + box_add_child(table, child); + + next_child = child->next; + child->next = NULL; + child = next_child; + } + + table->last->next = NULL; + table->next = next_child = child; + if (table->next != NULL) + table->next->prev = table; + else + block->last = table; + table->parent = block; + + if (box_normalise_table(table, root, c) == false) + return false; + break; + default: + assert(0); + } + } + + return true; +} + + +bool box_normalise_table( + struct box *table, + const struct box *root, + html_content * c) +{ + struct box *child; + struct box *next_child; + struct box *row_group; + css_computed_style *style; + struct columns col_info; + nscss_select_ctx ctx; + + assert(table != NULL); + assert(table->type == BOX_TABLE); + + ctx.root_style = root->style; + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "table %p", table); +#endif + + col_info.num_columns = 1; + col_info.current_column = 0; + col_info.spans = malloc(2 * sizeof *col_info.spans); + if (col_info.spans == NULL) + return false; + + col_info.spans[0].row_span = col_info.spans[1].row_span = 0; + col_info.spans[0].auto_row = false; + col_info.spans[1].auto_row = false; + col_info.num_rows = 0; + + for (child = table->children; child != NULL; child = next_child) { + next_child = child->next; + switch (child->type) { + case BOX_TABLE_ROW_GROUP: + /* ok */ + if (box_normalise_table_row_group(child, root, + &col_info, c) == false) { + free(col_info.spans); + return false; + } + break; + case BOX_BLOCK: + case BOX_INLINE_CONTAINER: + case BOX_TABLE: + case BOX_TABLE_ROW: + case BOX_TABLE_CELL: + /* insert implied table row group */ + assert(table->style != NULL); + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, table->style); + if (style == NULL) { + free(col_info.spans); + return false; + } + + row_group = box_create(NULL, style, true, table->href, + table->target, NULL, NULL, c->bctx); + if (row_group == NULL) { + css_computed_style_destroy(style); + free(col_info.spans); + return false; + } + + row_group->type = BOX_TABLE_ROW_GROUP; + + if (child->prev == NULL) + table->children = row_group; + else + child->prev->next = row_group; + + row_group->prev = child->prev; + + while (child != NULL && ( + child->type == BOX_BLOCK || + child->type == BOX_INLINE_CONTAINER || + child->type == BOX_TABLE || + child->type == BOX_TABLE_ROW || + child->type == BOX_TABLE_CELL)) { + box_add_child(row_group, child); + + next_child = child->next; + child->next = NULL; + child = next_child; + } + + assert(row_group->last != NULL); + + row_group->last->next = NULL; + row_group->next = next_child = child; + if (row_group->next != NULL) + row_group->next->prev = row_group; + else + table->last = row_group; + row_group->parent = table; + + if (box_normalise_table_row_group(row_group, root, + &col_info, c) == false) { + free(col_info.spans); + return false; + } + break; + case BOX_INLINE: + case BOX_INLINE_END: + case BOX_INLINE_BLOCK: + case BOX_FLOAT_LEFT: + case BOX_FLOAT_RIGHT: + case BOX_BR: + case BOX_TEXT: + /* should have been wrapped in inline + container by convert_xml_to_box() */ + assert(0); + break; + default: + fprintf(stderr, "%i\n", child->type); + assert(0); + } + } + + table->columns = col_info.num_columns; + table->rows = col_info.num_rows; + + if (table->children == NULL) { + struct box *row; + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, + "table->children == 0, creating implied row"); +#endif + + assert(table->style != NULL); + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, table->style); + if (style == NULL) { + free(col_info.spans); + return false; + } + + row_group = box_create(NULL, style, true, table->href, + table->target, NULL, NULL, c->bctx); + if (row_group == NULL) { + css_computed_style_destroy(style); + free(col_info.spans); + return false; + } + row_group->type = BOX_TABLE_ROW_GROUP; + + style = nscss_get_blank_style(&ctx, row_group->style); + if (style == NULL) { + box_free(row_group); + free(col_info.spans); + return false; + } + + row = box_create(NULL, style, true, row_group->href, + row_group->target, NULL, NULL, c->bctx); + if (row == NULL) { + css_computed_style_destroy(style); + box_free(row_group); + free(col_info.spans); + return false; + } + row->type = BOX_TABLE_ROW; + + row->parent = row_group; + row_group->children = row_group->last = row; + + row_group->parent = table; + table->children = table->last = row_group; + + table->rows = 1; + } + + if (box_normalise_table_spans(table, root, col_info.spans, c) == false) { + free(col_info.spans); + return false; + } + + free(col_info.spans); + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "table %p done", table); +#endif + + return true; +} + + +/** + * Normalise table cell column/row counts for colspan/rowspan = 0. + * Additionally, generate empty cells. + * + * \param table Table to process + * \param root root box of document + * \param spans Array of length table->columns for use in empty cell detection + * \param c Content containing table + * \return True on success, false on memory exhaustion. + */ + +bool box_normalise_table_spans( + struct box *table, + const struct box *root, + struct span_info *spans, + html_content *c) +{ + struct box *table_row_group; + struct box *table_row; + struct box *table_cell; + unsigned int rows_left = table->rows; + unsigned int group_rows_left; + unsigned int col; + nscss_select_ctx ctx; + + ctx.root_style = root->style; + + /* Clear span data */ + memset(spans, 0, table->columns * sizeof(struct span_info)); + + /* Scan table, filling in width and height of table cells with + * colspan = 0 and rowspan = 0. Also generate empty cells */ + for (table_row_group = table->children; + table_row_group != NULL; + table_row_group = table_row_group->next) { + + group_rows_left = table_row_group->rows; + + for (table_row = table_row_group->children; + table_row != NULL; + table_row = table_row->next) { + + for (table_cell = table_row->children; + table_cell != NULL; + table_cell = table_cell->next) { + + /* colspan = 0 -> colspan = 1 */ + if (table_cell->columns == 0) { + table_cell->columns = 1; + } + + /* if rowspan is 0 it is expanded to + * the number of rows left in the row + * group + */ + if (table_cell->rows == 0) { + table_cell->rows = group_rows_left; + } + + /* limit rowspans within group */ + if (table_cell->rows > group_rows_left) { + table_cell->rows = group_rows_left; + } + + /* Record span information */ + for (col = table_cell->start_column; + col < table_cell->start_column + + table_cell->columns; col++) { + spans[col].row_span = table_cell->rows; + } + } + + /* Reduce span count of each column */ + for (col = 0; col < table->columns; col++) { + if (spans[col].row_span == 0) { + unsigned int start = col; + css_computed_style *style; + struct box *cell, *prev; + + /* If it's already zero, then we need + * to generate an empty cell for the + * gap in the row that spans as many + * columns as remain blank. + */ + assert(table_row->style != NULL); + + /* Find width of gap */ + while (col < table->columns && + spans[col].row_span == + 0) { + col++; + } + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == + DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, + table_row->style); + if (style == NULL) + return false; + + cell = box_create(NULL, style, true, + table_row->href, + table_row->target, + NULL, NULL, c->bctx); + if (cell == NULL) { + css_computed_style_destroy( + style); + return false; + } + cell->type = BOX_TABLE_CELL; + + cell->rows = 1; + cell->columns = col - start; + cell->start_column = start; + + /* Find place to insert cell */ + for (prev = table_row->children; + prev != NULL; + prev = prev->next) { + if (prev->start_column + + prev->columns == + start) + break; + if (prev->next == NULL) + break; + } + + /* Insert it */ + if (prev == NULL) { + if (table_row->children != NULL) + table_row->children-> + prev = cell; + else + table_row->last = cell; + + cell->next = + table_row->children; + table_row->children = cell; + } else { + if (prev->next != NULL) + prev->next->prev = cell; + else + table_row->last = cell; + + cell->next = prev->next; + prev->next = cell; + cell->prev = prev; + } + cell->parent = table_row; + } else { + spans[col].row_span--; + } + } + + assert(rows_left > 0); + + rows_left--; + } + + group_rows_left--; + } + + return true; +} + + +bool box_normalise_table_row_group( + struct box *row_group, + const struct box *root, + struct columns *col_info, + html_content * c) +{ + struct box *child; + struct box *next_child; + struct box *row; + css_computed_style *style; + nscss_select_ctx ctx; + unsigned int group_row_count = 0; + + assert(row_group != 0); + assert(row_group->type == BOX_TABLE_ROW_GROUP); + + ctx.root_style = root->style; + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "row_group %p", row_group); +#endif + + for (child = row_group->children; child != NULL; child = next_child) { + next_child = child->next; + + switch (child->type) { + case BOX_TABLE_ROW: + /* ok */ + group_row_count++; + if (box_normalise_table_row(child, root, col_info, + c) == false) + return false; + break; + case BOX_BLOCK: + case BOX_INLINE_CONTAINER: + case BOX_TABLE: + case BOX_TABLE_ROW_GROUP: + case BOX_TABLE_CELL: + /* insert implied table row */ + assert(row_group->style != NULL); + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, row_group->style); + if (style == NULL) + return false; + + row = box_create(NULL, style, true, row_group->href, + row_group->target, NULL, NULL, c->bctx); + if (row == NULL) { + css_computed_style_destroy(style); + return false; + } + row->type = BOX_TABLE_ROW; + + if (child->prev == NULL) + row_group->children = row; + else + child->prev->next = row; + + row->prev = child->prev; + + while (child != NULL && ( + child->type == BOX_BLOCK || + child->type == BOX_INLINE_CONTAINER || + child->type == BOX_TABLE || + child->type == BOX_TABLE_ROW_GROUP || + child->type == BOX_TABLE_CELL)) { + box_add_child(row, child); + + next_child = child->next; + child->next = NULL; + child = next_child; + } + + assert(row->last != NULL); + + row->last->next = NULL; + row->next = next_child = child; + if (row->next != NULL) + row->next->prev = row; + else + row_group->last = row; + row->parent = row_group; + + group_row_count++; + if (box_normalise_table_row(row, root, col_info, + c) == false) + return false; + break; + case BOX_INLINE: + case BOX_INLINE_END: + case BOX_INLINE_BLOCK: + case BOX_FLOAT_LEFT: + case BOX_FLOAT_RIGHT: + case BOX_BR: + case BOX_TEXT: + /* should have been wrapped in inline + container by convert_xml_to_box() */ + assert(0); + break; + default: + assert(0); + } + } + + if (row_group->children == NULL) { +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, + "row_group->children == 0, inserting implied row"); +#endif + + assert(row_group->style != NULL); + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, row_group->style); + if (style == NULL) { + return false; + } + + row = box_create(NULL, style, true, row_group->href, + row_group->target, NULL, NULL, c->bctx); + if (row == NULL) { + css_computed_style_destroy(style); + return false; + } + row->type = BOX_TABLE_ROW; + + row->parent = row_group; + row_group->children = row_group->last = row; + + group_row_count = 1; + + /* Keep table's row count in sync */ + col_info->num_rows++; + } + + row_group->rows = group_row_count; + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "row_group %p done", row_group); +#endif + + return true; +} + + +bool box_normalise_table_row( + struct box *row, + const struct box *root, + struct columns *col_info, + html_content * c) +{ + struct box *child; + struct box *next_child; + struct box *cell = NULL; + css_computed_style *style; + unsigned int i; + nscss_select_ctx ctx; + + assert(row != NULL); + assert(row->type == BOX_TABLE_ROW); + + ctx.root_style = root->style; + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "row %p", row); +#endif + + for (child = row->children; child != NULL; child = next_child) { + next_child = child->next; + + switch (child->type) { + case BOX_TABLE_CELL: + /* ok */ + if (box_normalise_block(child, root, c) == false) + return false; + cell = child; + break; + case BOX_BLOCK: + case BOX_INLINE_CONTAINER: + case BOX_TABLE: + case BOX_TABLE_ROW_GROUP: + case BOX_TABLE_ROW: + /* insert implied table cell */ + assert(row->style != NULL); + + ctx.ctx = c->select_ctx; + ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); + ctx.base_url = c->base_url; + ctx.universal = c->universal; + + style = nscss_get_blank_style(&ctx, row->style); + if (style == NULL) + return false; + + cell = box_create(NULL, style, true, row->href, + row->target, NULL, NULL, c->bctx); + if (cell == NULL) { + css_computed_style_destroy(style); + return false; + } + cell->type = BOX_TABLE_CELL; + + if (child->prev == NULL) + row->children = cell; + else + child->prev->next = cell; + + cell->prev = child->prev; + + while (child != NULL && ( + child->type == BOX_BLOCK || + child->type == BOX_INLINE_CONTAINER || + child->type == BOX_TABLE || + child->type == BOX_TABLE_ROW_GROUP || + child->type == BOX_TABLE_ROW)) { + box_add_child(cell, child); + + next_child = child->next; + child->next = NULL; + child = next_child; + } + + assert(cell->last != NULL); + + cell->last->next = NULL; + cell->next = next_child = child; + if (cell->next != NULL) + cell->next->prev = cell; + else + row->last = cell; + cell->parent = row; + + if (box_normalise_block(cell, root, c) == false) + return false; + break; + case BOX_INLINE: + case BOX_INLINE_END: + case BOX_INLINE_BLOCK: + case BOX_FLOAT_LEFT: + case BOX_FLOAT_RIGHT: + case BOX_BR: + case BOX_TEXT: + /* should have been wrapped in inline + container by convert_xml_to_box() */ + assert(0); + break; + default: + assert(0); + } + + if (calculate_table_row(col_info, cell->columns, cell->rows, + &cell->start_column, cell) == false) + return false; + } + + + /* Update row spanning details for all columns */ + for (i = 0; i < col_info->num_columns; i++) { + if (col_info->spans[i].row_span != 0 && + col_info->spans[i].auto_row == false) { + /* This cell spans rows, and is not an auto row. + * Reduce number of rows left to span */ + col_info->spans[i].row_span--; + } + } + + /* Reset current column for next row */ + col_info->current_column = 0; + + /* Increment row counter */ + col_info->num_rows++; + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "row %p done", row); +#endif + + return true; +} + + +/** + * Compute the column index at which the current cell begins. + * Additionally, update the column record to reflect row spanning. + * + * \param col_info Column record + * \param col_span Number of columns that current cell spans + * \param row_span Number of rows that current cell spans + * \param start_column Pointer to location to receive column index + * \param cell Box for current table cell + * \return true on success, false on memory exhaustion + */ + +bool calculate_table_row(struct columns *col_info, + unsigned int col_span, unsigned int row_span, + unsigned int *start_column, struct box *cell) +{ + unsigned int cell_start_col = col_info->current_column; + unsigned int cell_end_col; + unsigned int i; + struct span_info *spans; + struct box *rg = cell->parent->parent; /* Cell's row group */ + + /* Skip columns with cells spanning from above */ + /* TODO: Need to ignore cells spanning from above that belong to + * different row group. We don't have that info here. */ + while (col_info->spans[cell_start_col].row_span != 0 && + col_info->spans[cell_start_col].rg == rg) { + cell_start_col++; + } + + /* Update current column with calculated start */ + col_info->current_column = cell_start_col; + + /* If this cell has a colspan of 0, then assume 1. + * No other browser supports colspan=0, anyway. */ + if (col_span == 0) + col_span = 1; + + cell_end_col = cell_start_col + col_span; + + if (col_info->num_columns < cell_end_col) { + /* It appears that this row has more columns than + * the maximum recorded for the table so far. + * Allocate more span records. */ + spans = realloc(col_info->spans, + sizeof *spans * (cell_end_col + 1)); + if (spans == NULL) + return false; + + col_info->spans = spans; + col_info->num_columns = cell_end_col; + + /* Mark new final column as sentinel */ + col_info->spans[cell_end_col].row_span = 0; + col_info->spans[cell_end_col].auto_row = false; + } + + /* This cell may span multiple columns. If it also wants to span + * multiple rows, temporarily assume it spans 1 row only. This will + * be fixed up in box_normalise_table_spans() */ + for (i = cell_start_col; i < cell_end_col; i++) { + col_info->spans[i].row_span = (row_span == 0) ? 1 : row_span; + col_info->spans[i].auto_row = (row_span == 0); + col_info->spans[i].rg = rg; + } + + /* Update current column with calculated end. */ + col_info->current_column = cell_end_col; + + *start_column = cell_start_col; + + return true; +} + + +bool box_normalise_inline_container( + struct box *cont, + const struct box *root, + html_content * c) +{ + struct box *child; + struct box *next_child; + + assert(cont != NULL); + assert(cont->type == BOX_INLINE_CONTAINER); + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "cont %p", cont); +#endif + + for (child = cont->children; child != NULL; child = next_child) { + next_child = child->next; + switch (child->type) { + case BOX_INLINE: + case BOX_INLINE_END: + case BOX_BR: + case BOX_TEXT: + /* ok */ + break; + case BOX_INLINE_BLOCK: + /* ok */ + if (box_normalise_block(child, root, c) == false) + return false; + break; + case BOX_FLOAT_LEFT: + case BOX_FLOAT_RIGHT: + /* ok */ + assert(child->children != NULL); + + switch (child->children->type) { + case BOX_BLOCK: + if (box_normalise_block(child->children, root, + c) == false) + return false; + break; + case BOX_TABLE: + if (box_normalise_table(child->children, root, + c) == false) + return false; + break; + default: + assert(0); + } + + if (child->children == NULL) { + /* the child has destroyed itself: remove float */ + if (child->prev == NULL) + child->parent->children = child->next; + else + child->prev->next = child->next; + if (child->next != NULL) + child->next->prev = child->prev; + else + child->parent->last = child->prev; + + box_free(child); + } + break; + case BOX_BLOCK: + case BOX_INLINE_CONTAINER: + case BOX_TABLE: + case BOX_TABLE_ROW_GROUP: + case BOX_TABLE_ROW: + case BOX_TABLE_CELL: + default: + assert(0); + } + } + +#ifdef BOX_NORMALISE_DEBUG + NSLOG(netsurf, INFO, "cont %p done", cont); +#endif + + return true; +} diff --git a/content/handlers/html/box_textarea.c b/content/handlers/html/box_textarea.c new file mode 100644 index 000000000..abd28fb86 --- /dev/null +++ b/content/handlers/html/box_textarea.c @@ -0,0 +1,350 @@ +/* + * Copyright 2013 Michael Drake + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Box tree treeview box replacement (implementation). + */ + +#include + +#include "utils/config.h" +#include "utils/log.h" +#include "netsurf/keypress.h" +#include "desktop/textarea.h" + +#include "html/box_textarea.h" +#include "html/font.h" +#include "html/form_internal.h" + + +bool box_textarea_keypress(html_content *html, struct box *box, uint32_t key) +{ + struct form_control *gadget = box->gadget; + struct textarea *ta = gadget->data.text.ta; + struct form* form = box->gadget->form; + struct content *c = (struct content *) html; + + assert(ta != NULL); + + if (gadget->type != GADGET_TEXTAREA) { + switch (key) { + case NS_KEY_NL: + case NS_KEY_CR: + if (form) + form_submit(content_get_url(c), html->bw, + form, 0); + return true; + + case NS_KEY_TAB: + { + struct form_control *next_input; + /* Find next text entry field that is actually + * displayed (i.e. has an associated box) */ + for (next_input = gadget->next; + next_input && + ((next_input->type != GADGET_TEXTBOX && + next_input->type != GADGET_TEXTAREA && + next_input->type != GADGET_PASSWORD) || + !next_input->box); + next_input = next_input->next) + ; + if (!next_input) + return true; + + textarea_set_caret(ta, -1); + textarea_set_caret(next_input->data.text.ta, 0); + } + return true; + + case NS_KEY_SHIFT_TAB: + { + struct form_control *prev_input; + /* Find previous text entry field that is actually + * displayed (i.e. has an associated box) */ + for (prev_input = gadget->prev; + prev_input && + ((prev_input->type != GADGET_TEXTBOX && + prev_input->type != GADGET_TEXTAREA && + prev_input->type != GADGET_PASSWORD) || + !prev_input->box); + prev_input = prev_input->prev) + ; + if (!prev_input) + return true; + + textarea_set_caret(ta, -1); + textarea_set_caret(prev_input->data.text.ta, 0); + } + return true; + + default: + /* Pass to textarea widget */ + break; + } + } + + return textarea_keypress(ta, key); +} + + +/** + * Callback for html form textareas. + */ +static void box_textarea_callback(void *data, struct textarea_msg *msg) +{ + struct form_textarea_data *d = data; + struct form_control *gadget = d->gadget; + struct html_content *html = d->gadget->html; + struct box *box = gadget->box; + + switch (msg->type) { + case TEXTAREA_MSG_DRAG_REPORT: + if (msg->data.drag == TEXTAREA_DRAG_NONE) { + /* Textarea drag finished */ + html_drag_type drag_type = HTML_DRAG_NONE; + union html_drag_owner drag_owner; + drag_owner.no_owner = true; + + html_set_drag_type(html, drag_type, drag_owner, + NULL); + } else { + /* Textarea drag started */ + struct rect rect = { + .x0 = INT_MIN, + .y0 = INT_MIN, + .x1 = INT_MAX, + .y1 = INT_MAX + }; + union html_drag_owner drag_owner; + drag_owner.textarea = box; + + switch (msg->data.drag) { + case TEXTAREA_DRAG_SCROLLBAR: + html_set_drag_type(html, + HTML_DRAG_TEXTAREA_SCROLLBAR, + drag_owner, + &rect); + break; + + case TEXTAREA_DRAG_SELECTION: + html_set_drag_type(html, + HTML_DRAG_TEXTAREA_SELECTION, + drag_owner, + &rect); + break; + + default: + NSLOG(netsurf, INFO, + "Drag type %d not handled.", + msg->data.drag); + /* This is a logic faliure in the + * front end code so abort. + */ + assert(0); + break; + } + } + break; + + case TEXTAREA_MSG_REDRAW_REQUEST: + { + /* Request redraw of the required textarea rectangle */ + int x, y; + + if (html->reflowing == true) { + /* Can't redraw during layout, and it will + * be redrawn after layout anyway. */ + break; + } + + box_coords(box, &x, &y); + + content__request_redraw((struct content *)html, + x + msg->data.redraw.x0, + y + msg->data.redraw.y0, + msg->data.redraw.x1 - msg->data.redraw.x0, + msg->data.redraw.y1 - msg->data.redraw.y0); + } + break; + + case TEXTAREA_MSG_SELECTION_REPORT: + if (msg->data.selection.have_selection) { + /* Textarea now has a selection */ + union html_selection_owner sel_owner; + sel_owner.textarea = box; + + html_set_selection(html, HTML_SELECTION_TEXTAREA, + sel_owner, + msg->data.selection.read_only); + } else { + /* The textarea now has no selection */ + union html_selection_owner sel_owner; + sel_owner.none = true; + + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); + } + break; + + case TEXTAREA_MSG_CARET_UPDATE: + if (html->bw == NULL) + break; + + if (msg->data.caret.type == TEXTAREA_CARET_HIDE) { + union html_focus_owner focus_owner; + focus_owner.textarea = box; + html_set_focus(html, HTML_FOCUS_TEXTAREA, + focus_owner, true, 0, 0, 0, NULL); + } else { + union html_focus_owner focus_owner; + focus_owner.textarea = box; + html_set_focus(html, HTML_FOCUS_TEXTAREA, + focus_owner, false, + msg->data.caret.pos.x, + msg->data.caret.pos.y, + msg->data.caret.pos.height, + msg->data.caret.pos.clip); + } + break; + + case TEXTAREA_MSG_TEXT_MODIFIED: + form_gadget_update_value(gadget, + strndup(msg->data.modified.text, + msg->data.modified.len)); + break; + } +} + + +/* Exported interface, documented in box_textarea.h */ +bool box_textarea_create_textarea(html_content *html, + struct box *box, struct dom_node *node) +{ + dom_string *dom_text = NULL; + dom_exception err; + textarea_setup ta_setup; + textarea_flags ta_flags; + plot_font_style_t fstyle = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 10 * FONT_SIZE_SCALE, + .weight = 400, + .flags = FONTF_NONE, + .background = 0, + .foreground = 0, + }; + bool read_only = false; + bool disabled = false; + struct form_control *gadget = box->gadget; + const char *text; + + assert(gadget != NULL); + assert(gadget->type == GADGET_TEXTAREA || + gadget->type == GADGET_TEXTBOX || + gadget->type == GADGET_PASSWORD); + + if (gadget->type == GADGET_TEXTAREA) { + dom_html_text_area_element *textarea = + (dom_html_text_area_element *) node; + ta_flags = TEXTAREA_MULTILINE; + + err = dom_html_text_area_element_get_read_only( + textarea, &read_only); + if (err != DOM_NO_ERR) + return false; + + err = dom_html_text_area_element_get_disabled( + textarea, &disabled); + if (err != DOM_NO_ERR) + return false; + + /* Get the textarea's initial content */ + err = dom_html_text_area_element_get_value(textarea, &dom_text); + if (err != DOM_NO_ERR) + return false; + + } else { + dom_html_input_element *input = (dom_html_input_element *) node; + + err = dom_html_input_element_get_read_only( + input, &read_only); + if (err != DOM_NO_ERR) + return false; + + err = dom_html_input_element_get_disabled( + input, &disabled); + if (err != DOM_NO_ERR) + return false; + + if (gadget->type == GADGET_PASSWORD) + ta_flags = TEXTAREA_PASSWORD; + else + ta_flags = TEXTAREA_DEFAULT; + + /* Get initial text */ + err = dom_html_input_element_get_value(input, &dom_text); + if (err != DOM_NO_ERR) + return false; + } + + if (dom_text != NULL) { + text = dom_string_data(dom_text); + } else { + /* No initial text, or failed reading it; + * use a blank string */ + text = ""; + } + + if (read_only || disabled) + ta_flags |= TEXTAREA_READONLY; + + gadget->data.text.data.gadget = gadget; + + /* Reset to correct values by layout */ + ta_setup.width = 200; + ta_setup.height = 20; + ta_setup.pad_top = 4; + ta_setup.pad_right = 4; + ta_setup.pad_bottom = 4; + ta_setup.pad_left = 4; + + /* Set remaining data */ + ta_setup.border_width = 0; + ta_setup.border_col = 0x000000; + ta_setup.text = fstyle; + ta_setup.text.background = NS_TRANSPARENT; + /* Make selected text either black or white, as gives greatest contrast + * with background colour. */ + ta_setup.selected_bg = fstyle.foreground; + ta_setup.selected_text = colour_to_bw_furthest(ta_setup.selected_bg); + + /* Hand reference to dom text over to gadget */ + gadget->data.text.initial = dom_text; + + gadget->data.text.ta = textarea_create(ta_flags, &ta_setup, + box_textarea_callback, &gadget->data.text.data); + + if (gadget->data.text.ta == NULL) { + return false; + } + + if (!textarea_set_text(gadget->data.text.ta, text)) + return false; + + return true; +} diff --git a/content/handlers/html/box_textarea.h b/content/handlers/html/box_textarea.h new file mode 100644 index 000000000..e2b02e811 --- /dev/null +++ b/content/handlers/html/box_textarea.h @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Michael Drake + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Box tree treeview box replacement (interface). + */ + +#ifndef NETSURF_HTML_BOX_TEXTAREA_H +#define NETSURF_HTML_BOX_TEXTAREA_H + + +#include "html/box.h" +#include "html/html_internal.h" + +struct dom_node; + +/** + * Create textarea widget for a form element + * + * \param html html content object + * \param box box with gadget to be given textarea widget + * \param node DOM node for form element + */ +bool box_textarea_create_textarea(html_content *html, + struct box *box, struct dom_node *node); + + +/** + * Handle form textarea keypress input + * + * \param html html content object + * \param box box with textarea widget + * \param key keypress + * \return true iff keypress handled + */ +bool box_textarea_keypress(html_content *html, struct box *box, uint32_t key); + +#endif diff --git a/content/handlers/html/font.c b/content/handlers/html/font.c new file mode 100644 index 000000000..9dbf5922b --- /dev/null +++ b/content/handlers/html/font.c @@ -0,0 +1,163 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * + * HTML internal font handling implementation. + */ + +#include "utils/nsoption.h" +#include "netsurf/plot_style.h" +#include "css/utils.h" + +#include "html/font.h" + +/** + * Map a generic CSS font family to a generic plot font family + * + * \param css Generic CSS font family + * \return Plot font family + */ +static plot_font_generic_family_t +plot_font_generic_family(enum css_font_family_e css) +{ + plot_font_generic_family_t plot; + + switch (css) { + case CSS_FONT_FAMILY_SERIF: + plot = PLOT_FONT_FAMILY_SERIF; + break; + case CSS_FONT_FAMILY_MONOSPACE: + plot = PLOT_FONT_FAMILY_MONOSPACE; + break; + case CSS_FONT_FAMILY_CURSIVE: + plot = PLOT_FONT_FAMILY_CURSIVE; + break; + case CSS_FONT_FAMILY_FANTASY: + plot = PLOT_FONT_FAMILY_FANTASY; + break; + case CSS_FONT_FAMILY_SANS_SERIF: + default: + plot = PLOT_FONT_FAMILY_SANS_SERIF; + break; + } + + return plot; +} + +/** + * Map a CSS font weight to a plot weight value + * + * \param css CSS font weight + * \return Plot weight + */ +static int plot_font_weight(enum css_font_weight_e css) +{ + int weight; + + switch (css) { + case CSS_FONT_WEIGHT_100: + weight = 100; + break; + case CSS_FONT_WEIGHT_200: + weight = 200; + break; + case CSS_FONT_WEIGHT_300: + weight = 300; + break; + case CSS_FONT_WEIGHT_400: + case CSS_FONT_WEIGHT_NORMAL: + default: + weight = 400; + break; + case CSS_FONT_WEIGHT_500: + weight = 500; + break; + case CSS_FONT_WEIGHT_600: + weight = 600; + break; + case CSS_FONT_WEIGHT_700: + case CSS_FONT_WEIGHT_BOLD: + weight = 700; + break; + case CSS_FONT_WEIGHT_800: + weight = 800; + break; + case CSS_FONT_WEIGHT_900: + weight = 900; + break; + } + + return weight; +} + +/** + * Map a CSS font style and font variant to plot font flags + * + * \param style CSS font style + * \param variant CSS font variant + * \return Computed plot flags + */ +static plot_font_flags_t plot_font_flags(enum css_font_style_e style, + enum css_font_variant_e variant) +{ + plot_font_flags_t flags = FONTF_NONE; + + if (style == CSS_FONT_STYLE_ITALIC) + flags |= FONTF_ITALIC; + else if (style == CSS_FONT_STYLE_OBLIQUE) + flags |= FONTF_OBLIQUE; + + if (variant == CSS_FONT_VARIANT_SMALL_CAPS) + flags |= FONTF_SMALLCAPS; + + return flags; +} + + +/* exported function documented in html/font.h */ +void font_plot_style_from_css( + const nscss_len_ctx *len_ctx, + const css_computed_style *css, + plot_font_style_t *fstyle) +{ + lwc_string **families; + css_fixed length = 0; + css_unit unit = CSS_UNIT_PX; + css_color col; + + fstyle->family = plot_font_generic_family( + css_computed_font_family(css, &families)); + + css_computed_font_size(css, &length, &unit); + fstyle->size = FIXTOINT(FMUL(nscss_len2pt(len_ctx, length, unit), + INTTOFIX(FONT_SIZE_SCALE))); + + /* Clamp font size to configured minimum */ + if (fstyle->size < (nsoption_int(font_min_size) * FONT_SIZE_SCALE) / 10) + fstyle->size = (nsoption_int(font_min_size) * FONT_SIZE_SCALE) / 10; + + fstyle->weight = plot_font_weight(css_computed_font_weight(css)); + fstyle->flags = plot_font_flags(css_computed_font_style(css), + css_computed_font_variant(css)); + + css_computed_color(css, &col); + fstyle->foreground = nscss_color_to_ns(col); + fstyle->background = 0; +} diff --git a/content/handlers/html/font.h b/content/handlers/html/font.h new file mode 100644 index 000000000..5f69ee7d3 --- /dev/null +++ b/content/handlers/html/font.h @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Vincent Sanders + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Internal font handling interfaces. + * + * These functions provide font related services. They all work on + * UTF-8 strings with lengths given. + */ + +#ifndef NETSURF_HTML_FONT_H +#define NETSURF_HTML_FONT_H + +struct plot_font_style; + +/** + * Populate a font style using data from a computed CSS style + * + * \param len_ctx Length conversion context + * \param css Computed style to consider + * \param fstyle Font style to populate + */ +void font_plot_style_from_css(const nscss_len_ctx *len_ctx, + const css_computed_style *css, + struct plot_font_style *fstyle); + +#endif diff --git a/content/handlers/html/form.c b/content/handlers/html/form.c new file mode 100644 index 000000000..9fe2e771a --- /dev/null +++ b/content/handlers/html/form.c @@ -0,0 +1,1895 @@ +/* + * Copyright 2004 James Bursa + * Copyright 2003 Phil Mellor + * Copyright 2004 John Tytgat + * Copyright 2005-9 John-Mark Bell + * Copyright 2009 Paul Blokus + * Copyright 2010 Michael Drake + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Form handling functions (implementation). + */ + +#include +#include +#include +#include +#include +#include + +#include "utils/corestrings.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/talloc.h" +#include "utils/url.h" +#include "utils/utf8.h" +#include "utils/ascii.h" +#include "netsurf/browser_window.h" +#include "netsurf/mouse.h" +#include "netsurf/plotters.h" +#include "netsurf/misc.h" +#include "content/fetch.h" +#include "content/hlcache.h" +#include "css/utils.h" +#include "desktop/knockout.h" +#include "desktop/scrollbar.h" +#include "desktop/textarea.h" +#include "desktop/gui_internal.h" + +#include "html/box.h" +#include "html/font.h" +#include "html/form_internal.h" +#include "html/html.h" +#include "html/html_internal.h" +#include "html/layout.h" + +#define MAX_SELECT_HEIGHT 210 +#define SELECT_LINE_SPACING 0.2 +#define SELECT_BORDER_WIDTH 1 +#define SELECT_SELECTED_COLOUR 0xDB9370 + +struct form_select_menu { + int line_height; + int width, height; + struct scrollbar *scrollbar; + int f_size; + bool scroll_capture; + select_menu_redraw_callback callback; + void *client_data; + struct content *c; +}; + +static plot_style_t plot_style_fill_selected = { + .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = SELECT_SELECTED_COLOUR, +}; + +static plot_font_style_t plot_fstyle_entry = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .weight = 400, + .flags = FONTF_NONE, + .background = 0xffffff, + .foreground = 0x000000, +}; + +static char *form_acceptable_charset(struct form *form); +static char *form_encode_item(const char *item, uint32_t len, const char *charset, + const char *fallback); +static void form_select_menu_clicked(struct form_control *control, + int x, int y); +static void form_select_menu_scroll_callback(void *client_data, + struct scrollbar_msg_data *scrollbar_data); + +/* exported interface documented in html/form_internal.h */ +struct form *form_new(void *node, const char *action, const char *target, + form_method method, const char *charset, + const char *doc_charset) +{ + struct form *form; + + form = calloc(1, sizeof *form); + if (!form) + return NULL; + + form->action = strdup(action != NULL ? action : ""); + if (form->action == NULL) { + free(form); + return NULL; + } + + form->target = target != NULL ? strdup(target) : NULL; + if (target != NULL && form->target == NULL) { + free(form->action); + free(form); + return NULL; + } + + form->method = method; + + form->accept_charsets = charset != NULL ? strdup(charset) : NULL; + if (charset != NULL && form->accept_charsets == NULL) { + free(form->target); + free(form->action); + free(form); + return NULL; + } + + form->document_charset = doc_charset != NULL ? strdup(doc_charset) + : NULL; + if (doc_charset && form->document_charset == NULL) { + free(form->accept_charsets); + free(form->target); + free(form->action); + free(form); + return NULL; + } + + form->node = node; + + return form; +} + + +/* exported interface documented in html/form_internal.h */ +void form_free(struct form *form) +{ + struct form_control *c, *d; + + for (c = form->controls; c != NULL; c = d) { + d = c->next; + + form_free_control(c); + } + + free(form->action); + free(form->target); + free(form->accept_charsets); + free(form->document_charset); + + free(form); +} + +/* exported interface documented in html/form_internal.h */ +struct form_control *form_new_control(void *node, form_control_type type) +{ + struct form_control *control; + + control = calloc(1, sizeof *control); + if (control == NULL) + return NULL; + + control->node = node; + control->type = type; + + return control; +} + + +/** + * Add a control to the list of controls in a form. + * + * \param form The form to add the control to + * \param control The control to add + */ +void form_add_control(struct form *form, struct form_control *control) +{ + if (form == NULL) { + return; + } + + control->form = form; + + if (form->controls != NULL) { + assert(form->last_control); + + form->last_control->next = control; + control->prev = form->last_control; + control->next = NULL; + form->last_control = control; + } else { + form->controls = form->last_control = control; + } +} + + +/** + * Free a struct form_control. + * + * \param control structure to free + */ +void form_free_control(struct form_control *control) +{ + struct form_control *c; + assert(control != NULL); + + NSLOG(netsurf, INFO, "Control:%p name:%p value:%p initial:%p", + control, control->name, control->value, control->initial_value); + free(control->name); + free(control->value); + free(control->initial_value); + + if (control->type == GADGET_SELECT) { + struct form_option *option, *next; + + for (option = control->data.select.items; option; + option = next) { + next = option->next; + NSLOG(netsurf, INFO, + "select option:%p text:%p value:%p", option, + option->text, option->value); + free(option->text); + free(option->value); + free(option); + } + if (control->data.select.menu != NULL) { + form_free_select_menu(control); + } + } + + if (control->type == GADGET_TEXTAREA || + control->type == GADGET_TEXTBOX || + control->type == GADGET_PASSWORD) { + + if (control->data.text.initial != NULL) { + dom_string_unref(control->data.text.initial); + } + + if (control->data.text.ta != NULL) { + textarea_destroy(control->data.text.ta); + } + } + + /* unlink the control from the form */ + if (control->form != NULL) { + for (c = control->form->controls; c != NULL; c = c->next) { + if (c->next == control) { + c->next = control->next; + if (control->form->last_control == control) + control->form->last_control = c; + break; + } + if (c == control) { + /* can only happen if control was first control */ + control->form->controls = control->next; + if (control->form->last_control == control) + control->form->controls = + control->form->last_control = NULL; + break; + } + } + } + + free(control); +} + + +/** + * Add an option to a form select control. + * + * \param control form control of type GADGET_SELECT + * \param value value of option, used directly (not copied) + * \param text text for option, used directly (not copied) + * \param selected this option is selected + * \param node the DOM node this option is associated with + * \return true on success, false on memory exhaustion + */ +bool form_add_option(struct form_control *control, char *value, char *text, + bool selected, void *node) +{ + struct form_option *option; + + assert(control); + assert(control->type == GADGET_SELECT); + + option = calloc(1, sizeof *option); + if (!option) + return false; + + option->value = value; + option->text = text; + + /* add to linked list */ + if (control->data.select.items == 0) + control->data.select.items = option; + else + control->data.select.last_item->next = option; + control->data.select.last_item = option; + + /* set selected */ + if (selected && (control->data.select.num_selected == 0 || + control->data.select.multiple)) { + option->selected = option->initial_selected = true; + control->data.select.num_selected++; + control->data.select.current = option; + } + + control->data.select.num_items++; + + option->node = node; + + return true; +} + + +/* exported interface documented in html/form_internal.h */ +bool form_successful_controls_dom(struct form *_form, + struct form_control *_submit_button, + struct fetch_multipart_data **successful_controls) +{ + dom_html_form_element *form = _form->node; + dom_html_element *submit_button = (_submit_button != NULL) ? _submit_button->node : NULL; + dom_html_collection *form_elements = NULL; + dom_html_options_collection *options = NULL; + dom_node *form_element = NULL, *option_element = NULL; + dom_exception err; + dom_string *nodename = NULL, *inputname = NULL, *inputvalue = NULL, *inputtype = NULL; + struct fetch_multipart_data sentinel, *last_success, *success_new; + bool had_submit = false, element_disabled, checked; + char *charset, *rawfile_temp = NULL, *basename; + uint32_t index, element_count; + struct image_input_coords *coords; + + last_success = &sentinel; + sentinel.next = NULL; + + /** \todo Replace this call with something DOMish */ + charset = form_acceptable_charset(_form); + if (charset == NULL) { + NSLOG(netsurf, INFO, "failed to find charset"); + return false; + } + +#define ENCODE_ITEM(i) (((i) == NULL) ? ( \ + form_encode_item("", 0, charset, _form->document_charset) \ + ):( \ + form_encode_item(dom_string_data(i), dom_string_byte_length(i), \ + charset, _form->document_charset) \ + )) + + err = dom_html_form_element_get_elements(form, &form_elements); + + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, "Could not get form elements"); + goto dom_no_memory; + } + + + err = dom_html_collection_get_length(form_elements, &element_count); + + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, "Could not get form element count"); + goto dom_no_memory; + } + + for (index = 0; index < element_count; index++) { + if (form_element != NULL) { + dom_node_unref(form_element); + form_element = NULL; + } + if (nodename != NULL) { + dom_string_unref(nodename); + nodename = NULL; + } + if (inputname != NULL) { + dom_string_unref(inputname); + inputname = NULL; + } + if (inputvalue != NULL) { + dom_string_unref(inputvalue); + inputvalue = NULL; + } + if (inputtype != NULL) { + dom_string_unref(inputtype); + inputtype = NULL; + } + if (options != NULL) { + dom_html_options_collection_unref(options); + options = NULL; + } + err = dom_html_collection_item(form_elements, + index, &form_element); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not retrieve form element %d", index); + goto dom_no_memory; + } + + /* Form elements are one of: + * HTMLButtonElement + * HTMLInputElement + * HTMLTextAreaElement + * HTMLSelectElement + */ + err = dom_node_get_node_name(form_element, &nodename); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, "Could not get node name"); + goto dom_no_memory; + } + + if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) { + err = dom_html_text_area_element_get_disabled( + (dom_html_text_area_element *)form_element, + &element_disabled); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get text area disabled property"); + goto dom_no_memory; + } + err = dom_html_text_area_element_get_name( + (dom_html_text_area_element *)form_element, + &inputname); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get text area name property"); + goto dom_no_memory; + } + } else if (dom_string_isequal(nodename, corestring_dom_SELECT)) { + err = dom_html_select_element_get_disabled( + (dom_html_select_element *)form_element, + &element_disabled); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get select disabled property"); + goto dom_no_memory; + } + err = dom_html_select_element_get_name( + (dom_html_select_element *)form_element, + &inputname); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get select name property"); + goto dom_no_memory; + } + } else if (dom_string_isequal(nodename, corestring_dom_INPUT)) { + err = dom_html_input_element_get_disabled( + (dom_html_input_element *)form_element, + &element_disabled); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get input disabled property"); + goto dom_no_memory; + } + err = dom_html_input_element_get_name( + (dom_html_input_element *)form_element, + &inputname); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get input name property"); + goto dom_no_memory; + } + } else if (dom_string_isequal(nodename, corestring_dom_BUTTON)) { + err = dom_html_button_element_get_disabled( + (dom_html_button_element *)form_element, + &element_disabled); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get button disabled property"); + goto dom_no_memory; + } + err = dom_html_button_element_get_name( + (dom_html_button_element *)form_element, + &inputname); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get button name property"); + goto dom_no_memory; + } + } else { + /* Unknown element type came through! */ + NSLOG(netsurf, INFO, "Unknown element type: %*s", + (int)dom_string_byte_length(nodename), + dom_string_data(nodename)); + goto dom_no_memory; + } + if (element_disabled) + continue; + if (inputname == NULL) + continue; + + if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) { + err = dom_html_text_area_element_get_value( + (dom_html_text_area_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get text area content"); + goto dom_no_memory; + } + } else if (dom_string_isequal(nodename, corestring_dom_SELECT)) { + uint32_t options_count, option_index; + err = dom_html_select_element_get_options( + (dom_html_select_element *)form_element, + &options); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get select options collection"); + goto dom_no_memory; + } + err = dom_html_options_collection_get_length( + options, &options_count); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get select options collection length"); + goto dom_no_memory; + } + for(option_index = 0; option_index < options_count; + ++option_index) { + bool selected; + if (option_element != NULL) { + dom_node_unref(option_element); + option_element = NULL; + } + if (inputvalue != NULL) { + dom_string_unref(inputvalue); + inputvalue = NULL; + } + err = dom_html_options_collection_item( + options, option_index, &option_element); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get options item %d", + option_index); + goto dom_no_memory; + } + err = dom_html_option_element_get_selected( + (dom_html_option_element *)option_element, + &selected); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get option selected property"); + goto dom_no_memory; + } + if (!selected) + continue; + err = dom_html_option_element_get_value( + (dom_html_option_element *)option_element, + &inputvalue); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get option value"); + goto dom_no_memory; + } + + success_new = calloc(1, sizeof(*success_new)); + if (success_new == NULL) { + NSLOG(netsurf, INFO, + "Could not allocate data for option"); + goto dom_no_memory; + } + + last_success->next = success_new; + last_success = success_new; + + success_new->name = ENCODE_ITEM(inputname); + if (success_new->name == NULL) { + NSLOG(netsurf, INFO, + "Could not encode name for option"); + goto dom_no_memory; + } + success_new->value = ENCODE_ITEM(inputvalue); + if (success_new->value == NULL) { + NSLOG(netsurf, INFO, + "Could not encode value for option"); + goto dom_no_memory; + } + } + continue; + } else if (dom_string_isequal(nodename, corestring_dom_BUTTON)) { + err = dom_html_button_element_get_type( + (dom_html_button_element *) form_element, + &inputtype); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get button element type"); + goto dom_no_memory; + } + if (dom_string_caseless_isequal( + inputtype, corestring_dom_submit)) { + + if (submit_button == NULL && !had_submit) { + /* no button used, and first submit + * node found, so use it + */ + had_submit = true; + } else if ((dom_node *)submit_button != + (dom_node *)form_element) { + continue; + } + + err = dom_html_button_element_get_value( + (dom_html_button_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get submit button value"); + goto dom_no_memory; + } + /* Drop through to report successful button */ + } else { + continue; + } + } else if (dom_string_isequal(nodename, corestring_dom_INPUT)) { + /* Things to consider here */ + /* Buttons -- only if the successful control */ + /* radio and checkbox -- only if selected */ + /* file -- also get the rawfile */ + /* everything else -- just value */ + err = dom_html_input_element_get_type( + (dom_html_input_element *) form_element, + &inputtype); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get input element type"); + goto dom_no_memory; + } + if (dom_string_caseless_isequal( + inputtype, corestring_dom_submit)) { + + if (submit_button == NULL && !had_submit) { + /* no button used, and first submit + * node found, so use it + */ + had_submit = true; + } else if ((dom_node *)submit_button != + (dom_node *)form_element) { + continue; + } + + err = dom_html_input_element_get_value( + (dom_html_input_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get submit button value"); + goto dom_no_memory; + } + /* Drop through to report the successful button */ + } else if (dom_string_caseless_isequal( + inputtype, corestring_dom_image)) { + /* We *ONLY* use an image input if it was the + * thing which activated us + */ + if ((dom_node *)submit_button != + (dom_node *)form_element) + continue; + + err = dom_node_get_user_data( + form_element, + corestring_dom___ns_key_image_coords_node_data, + &coords); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get image XY data"); + goto dom_no_memory; + } + if (coords == NULL) { + NSLOG(netsurf, INFO, + "No XY data on the image input"); + goto dom_no_memory; + } + + basename = ENCODE_ITEM(inputname); + + success_new = calloc(1, sizeof(*success_new)); + if (success_new == NULL) { + free(basename); + NSLOG(netsurf, INFO, + "Could not allocate data for image.x"); + goto dom_no_memory; + } + + last_success->next = success_new; + last_success = success_new; + + success_new->name = malloc(strlen(basename) + 3); + if (success_new->name == NULL) { + free(basename); + NSLOG(netsurf, INFO, + "Could not allocate name for image.x"); + goto dom_no_memory; + } + success_new->value = malloc(20); + if (success_new->value == NULL) { + free(basename); + NSLOG(netsurf, INFO, + "Could not allocate value for image.x"); + goto dom_no_memory; + } + sprintf(success_new->name, "%s.x", basename); + sprintf(success_new->value, "%d", coords->x); + + success_new = calloc(1, sizeof(*success_new)); + if (success_new == NULL) { + free(basename); + NSLOG(netsurf, INFO, + "Could not allocate data for image.y"); + goto dom_no_memory; + } + + last_success->next = success_new; + last_success = success_new; + + success_new->name = malloc(strlen(basename) + 3); + if (success_new->name == NULL) { + free(basename); + NSLOG(netsurf, INFO, + "Could not allocate name for image.y"); + goto dom_no_memory; + } + success_new->value = malloc(20); + if (success_new->value == NULL) { + free(basename); + NSLOG(netsurf, INFO, + "Could not allocate value for image.y"); + goto dom_no_memory; + } + sprintf(success_new->name, "%s.y", basename); + sprintf(success_new->value, "%d", coords->y); + free(basename); + continue; + } else if (dom_string_caseless_isequal( + inputtype, corestring_dom_radio) || + dom_string_caseless_isequal( + inputtype, corestring_dom_checkbox)) { + err = dom_html_input_element_get_checked( + (dom_html_input_element *)form_element, + &checked); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get input element checked"); + goto dom_no_memory; + } + if (!checked) + continue; + err = dom_html_input_element_get_value( + (dom_html_input_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get input element value"); + goto dom_no_memory; + } + if (inputvalue == NULL) { + inputvalue = dom_string_ref( + corestring_dom_on); + } + /* Fall through to simple allocation */ + } else if (dom_string_caseless_isequal( + inputtype, corestring_dom_file)) { + + err = dom_html_input_element_get_value( + (dom_html_input_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get file value"); + goto dom_no_memory; + } + err = dom_node_get_user_data( + form_element, + corestring_dom___ns_key_file_name_node_data, + &rawfile_temp); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get file rawname"); + goto dom_no_memory; + } + rawfile_temp = strdup(rawfile_temp != NULL ? + rawfile_temp : + ""); + if (rawfile_temp == NULL) { + NSLOG(netsurf, INFO, + "Could not copy file rawname"); + goto dom_no_memory; + } + /* Fall out to the allocation */ + } else if (dom_string_caseless_isequal( + inputtype, corestring_dom_reset) || + dom_string_caseless_isequal( + inputtype, corestring_dom_button)) { + /* Skip these */ + NSLOG(netsurf, INFO, + "Skipping RESET and BUTTON"); + continue; + } else { + /* Everything else is treated as text values */ + err = dom_html_input_element_get_value( + (dom_html_input_element *)form_element, + &inputvalue); + if (err != DOM_NO_ERR) { + NSLOG(netsurf, INFO, + "Could not get input value"); + goto dom_no_memory; + } + /* Fall out to the allocation */ + } + } + + success_new = calloc(1, sizeof(*success_new)); + if (success_new == NULL) { + NSLOG(netsurf, INFO, + "Could not allocate data for generic"); + goto dom_no_memory; + } + + last_success->next = success_new; + last_success = success_new; + + success_new->name = ENCODE_ITEM(inputname); + if (success_new->name == NULL) { + NSLOG(netsurf, INFO, + "Could not encode name for generic"); + goto dom_no_memory; + } + success_new->value = ENCODE_ITEM(inputvalue); + if (success_new->value == NULL) { + NSLOG(netsurf, INFO, + "Could not encode value for generic"); + goto dom_no_memory; + } + if (rawfile_temp != NULL) { + success_new->file = true; + success_new->rawfile = rawfile_temp; + rawfile_temp = NULL; + } + } + + free(charset); + + if (form_element != NULL) { + dom_node_unref(form_element); + } + + if (form_elements != NULL) { + dom_html_collection_unref(form_elements); + } + + if (nodename != NULL) { + dom_string_unref(nodename); + } + + if (inputname != NULL) { + dom_string_unref(inputname); + } + + if (inputvalue != NULL) { + dom_string_unref(inputvalue); + } + + if (options != NULL) { + dom_html_options_collection_unref(options); + } + + if (option_element != NULL) { + dom_node_unref(option_element); + } + + if (inputtype != NULL) { + dom_string_unref(inputtype); + } + + if (rawfile_temp != NULL) { + free(rawfile_temp); + } + + *successful_controls = sentinel.next; + + return true; + +dom_no_memory: + free(charset); + fetch_multipart_data_destroy(sentinel.next); + + if (form_elements != NULL) + dom_html_collection_unref(form_elements); + if (form_element != NULL) + dom_node_unref(form_element); + if (nodename != NULL) + dom_string_unref(nodename); + if (inputname != NULL) + dom_string_unref(inputname); + if (inputvalue != NULL) + dom_string_unref(inputvalue); + if (options != NULL) + dom_html_options_collection_unref(options); + if (option_element != NULL) + dom_node_unref(option_element); + if (inputtype != NULL) + dom_string_unref(inputtype); + if (rawfile_temp != NULL) + free(rawfile_temp); + + return false; +} +#undef ENCODE_ITEM + +/** + * Encode controls using application/x-www-form-urlencoded. + * + * \param form form to which successful controls relate + * \param control linked list of fetch_multipart_data + * \param query_string iff true add '?' to the start of returned data + * \return URL-encoded form, or 0 on memory exhaustion + */ + +static char *form_url_encode(struct form *form, + struct fetch_multipart_data *control, + bool query_string) +{ + char *name, *value; + char *s, *s2; + unsigned int len, len1, len_init; + nserror url_err; + + if (query_string) + s = malloc(2); + else + s = malloc(1); + + if (s == NULL) + return NULL; + + if (query_string) { + s[0] = '?'; + s[1] = '\0'; + len_init = len = 1; + } else { + s[0] = '\0'; + len_init = len = 0; + } + + for (; control; control = control->next) { + url_err = url_escape(control->name, true, NULL, &name); + if (url_err == NSERROR_NOMEM) { + free(s); + return NULL; + } + + assert(url_err == NSERROR_OK); + + url_err = url_escape(control->value, true, NULL, &value); + if (url_err == NSERROR_NOMEM) { + free(name); + free(s); + return NULL; + } + + assert(url_err == NSERROR_OK); + + len1 = len + strlen(name) + strlen(value) + 2; + s2 = realloc(s, len1 + 1); + if (!s2) { + free(value); + free(name); + free(s); + return NULL; + } + s = s2; + sprintf(s + len, "%s=%s&", name, value); + len = len1; + free(name); + free(value); + } + + if (len > len_init) { + /* Replace trailing '&' */ + s[len - 1] = '\0'; + } + return s; +} + +/** + * Find an acceptable character set encoding with which to submit the form + * + * \param form The form + * \return Pointer to charset name (on heap, caller should free) or NULL + */ +char *form_acceptable_charset(struct form *form) +{ + char *temp, *c; + + if (!form) + return NULL; + + if (!form->accept_charsets) { + /* no accept-charsets attribute for this form */ + if (form->document_charset) + /* document charset present, so use it */ + return strdup(form->document_charset); + else + /* no document charset, so default to 8859-1 */ + return strdup("ISO-8859-1"); + } + + /* make temporary copy of accept-charsets attribute */ + temp = strdup(form->accept_charsets); + if (!temp) + return NULL; + + /* make it upper case */ + for (c = temp; *c; c++) { + *c = ascii_to_upper(*c); + } + + /* is UTF-8 specified? */ + c = strstr(temp, "UTF-8"); + if (c) { + free(temp); + return strdup("UTF-8"); + } + + /* dispense with temporary copy */ + free(temp); + + /* according to RFC2070, the accept-charsets attribute of the + * form element contains a space and/or comma separated list */ + c = form->accept_charsets; + + /** \todo an improvement would be to choose an encoding + * acceptable to the server which covers as much of the input + * values as possible. Additionally, we need to handle the + * case where none of the acceptable encodings cover all the + * textual input values. For now, we just extract the first + * element of the charset list + */ + while (*c && !ascii_is_space(*c)) { + if (*c == ',') + break; + c++; + } + + return strndup(form->accept_charsets, c - form->accept_charsets); +} + +/** + * Convert a string from UTF-8 to the specified charset + * As a final fallback, this will attempt to convert to ISO-8859-1. + * + * \todo Return charset used? + * + * \param item String to convert + * \param len Length of string to convert + * \param charset Destination charset + * \param fallback Fallback charset (may be NULL), + * used iff converting to charset fails + * \return Pointer to converted string (on heap, caller frees), or NULL + */ +char *form_encode_item(const char *item, uint32_t len, const char *charset, + const char *fallback) +{ + nserror err; + char *ret = NULL; + char cset[256]; + + if (!item || !charset) + return NULL; + + snprintf(cset, sizeof cset, "%s//TRANSLIT", charset); + + err = utf8_to_enc(item, cset, 0, &ret); + if (err == NSERROR_BAD_ENCODING) { + /* charset not understood, try without transliteration */ + snprintf(cset, sizeof cset, "%s", charset); + err = utf8_to_enc(item, cset, len, &ret); + + if (err == NSERROR_BAD_ENCODING) { + /* nope, try fallback charset (if any) */ + if (fallback) { + snprintf(cset, sizeof cset, + "%s//TRANSLIT", fallback); + err = utf8_to_enc(item, cset, 0, &ret); + + if (err == NSERROR_BAD_ENCODING) { + /* and without transliteration */ + snprintf(cset, sizeof cset, + "%s", fallback); + err = utf8_to_enc(item, cset, 0, &ret); + } + } + + if (err == NSERROR_BAD_ENCODING) { + /* that also failed, use 8859-1 */ + err = utf8_to_enc(item, "ISO-8859-1//TRANSLIT", + 0, &ret); + if (err == NSERROR_BAD_ENCODING) { + /* and without transliteration */ + err = utf8_to_enc(item, "ISO-8859-1", + 0, &ret); + } + } + } + } + if (err == NSERROR_NOMEM) { + return NULL; + } + + return ret; +} + +/* exported interface documented in html/form_internal.h */ +bool form_open_select_menu(void *client_data, + struct form_control *control, + select_menu_redraw_callback callback, + struct content *c) +{ + int line_height_with_spacing; + struct box *box; + plot_font_style_t fstyle; + int total_height; + struct form_select_menu *menu; + html_content *html = (html_content *)c; + + + /* if the menu is opened for the first time */ + if (control->data.select.menu == NULL) { + + menu = calloc(1, sizeof (struct form_select_menu)); + if (menu == NULL) { + guit->misc->warning("NoMemory", 0); + return false; + } + + control->data.select.menu = menu; + + box = control->box; + + menu->width = box->width + + box->border[RIGHT].width + + box->border[LEFT].width + + box->padding[RIGHT] + box->padding[LEFT]; + + font_plot_style_from_css(&html->len_ctx, control->box->style, + &fstyle); + menu->f_size = fstyle.size; + + menu->line_height = FIXTOINT(FDIV((FMUL(FLTTOFIX(1.2), + FMUL(nscss_screen_dpi, + INTTOFIX(fstyle.size / FONT_SIZE_SCALE)))), + F_72)); + + line_height_with_spacing = menu->line_height + + menu->line_height * + SELECT_LINE_SPACING; + + total_height = control->data.select.num_items * + line_height_with_spacing; + menu->height = total_height; + + if (menu->height > MAX_SELECT_HEIGHT) { + + menu->height = MAX_SELECT_HEIGHT; + } + menu->client_data = client_data; + menu->callback = callback; + if (scrollbar_create(false, + menu->height, + total_height, + menu->height, + control, + form_select_menu_scroll_callback, + &(menu->scrollbar)) != NSERROR_OK) { + free(menu); + return false; + } + menu->c = c; + } + else menu = control->data.select.menu; + + menu->callback(client_data, 0, 0, menu->width, menu->height); + + return true; +} + + +/* exported interface documented in html/form_internal.h */ +void form_free_select_menu(struct form_control *control) +{ + if (control->data.select.menu->scrollbar != NULL) + scrollbar_destroy(control->data.select.menu->scrollbar); + free(control->data.select.menu); + control->data.select.menu = NULL; +} + + +/* exported interface documented in html/form_internal.h */ +bool form_redraw_select_menu(struct form_control *control, int x, int y, + float scale, const struct rect *clip, + const struct redraw_context *ctx) +{ + struct box *box; + struct form_select_menu *menu = control->data.select.menu; + struct form_option *option; + int line_height, line_height_with_spacing; + int width, height; + int x0, y0, x1, scrollbar_x, y1, y2, y3; + int item_y; + int text_pos_offset, text_x; + int scrollbar_width = SCROLLBAR_WIDTH; + int i; + int scroll; + int x_cp, y_cp; + struct rect r; + struct rect rect; + nserror res; + + box = control->box; + + x_cp = x; + y_cp = y; + width = menu->width; + height = menu->height; + line_height = menu->line_height; + + line_height_with_spacing = line_height + + line_height * SELECT_LINE_SPACING; + scroll = scrollbar_get_offset(menu->scrollbar); + + if (scale != 1.0) { + x *= scale; + y *= scale; + width *= scale; + height *= scale; + scrollbar_width *= scale; + + i = scroll / line_height_with_spacing; + scroll -= i * line_height_with_spacing; + line_height *= scale; + line_height_with_spacing *= scale; + scroll *= scale; + scroll += i * line_height_with_spacing; + } + + + x0 = x; + y0 = y; + x1 = x + width - 1; + y1 = y + height - 1; + scrollbar_x = x1 - scrollbar_width; + + r.x0 = x0; + r.y0 = y0; + r.x1 = x1 + 1; + r.y1 = y1 + 1; + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + rect.x0 = x0; + rect.y0 = y0; + rect.x1 = x1; + rect.y1 = y1; + res = ctx->plot->rectangle(ctx, plot_style_stroke_darkwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + x0 = x0 + SELECT_BORDER_WIDTH; + y0 = y0 + SELECT_BORDER_WIDTH; + x1 = x1 - SELECT_BORDER_WIDTH; + y1 = y1 - SELECT_BORDER_WIDTH; + height = height - 2 * SELECT_BORDER_WIDTH; + + r.x0 = x0; + r.y0 = y0; + r.x1 = x1 + 1; + r.y1 = y1 + 1; + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + res = ctx->plot->rectangle(ctx, plot_style_fill_lightwbasec, &r); + if (res != NSERROR_OK) { + return false; + } + + option = control->data.select.items; + item_y = line_height_with_spacing; + + while (item_y < scroll) { + option = option->next; + item_y += line_height_with_spacing; + } + item_y -= line_height_with_spacing; + text_pos_offset = y - scroll + + (int) (line_height * (0.75 + SELECT_LINE_SPACING)); + text_x = x + (box->border[LEFT].width + box->padding[LEFT]) * scale; + + plot_fstyle_entry.size = menu->f_size; + + while (option && item_y - scroll < height) { + + if (option->selected) { + y2 = y + item_y - scroll; + y3 = y + item_y + line_height_with_spacing - scroll; + + rect.x0 = x0; + rect.y0 = y0 > y2 ? y0 : y2; + rect.x1 = scrollbar_x + 1; + rect.y1 = y3 < y1 + 1 ? y3 : y1 + 1; + res = ctx->plot->rectangle(ctx, &plot_style_fill_selected, &rect); + if (res != NSERROR_OK) { + return false; + } + } + + y2 = text_pos_offset + item_y; + res = ctx->plot->text(ctx, + &plot_fstyle_entry, + text_x, y2, + option->text, strlen(option->text)); + if (res != NSERROR_OK) { + return false; + } + + item_y += line_height_with_spacing; + option = option->next; + } + + res = scrollbar_redraw(menu->scrollbar, + x_cp + menu->width - SCROLLBAR_WIDTH, + y_cp, + clip, scale, ctx); + if (res != NSERROR_OK) { + return false; + } + + return true; +} + +/** + * Check whether a clipping rectangle is completely contained in the + * select menu. + * + * \param control the select menu to check the clipping rectangle for + * \param scale the current browser window scale + * \param clip the clipping rectangle + * \return true if inside false otherwise + */ +bool form_clip_inside_select_menu(struct form_control *control, float scale, + const struct rect *clip) +{ + struct form_select_menu *menu = control->data.select.menu; + int width, height; + + + width = menu->width; + height = menu->height; + + if (scale != 1.0) { + width *= scale; + height *= scale; + } + + if (clip->x0 >= 0 && clip->x1 <= width && + clip->y0 >= 0 && clip->y1 <= height) + return true; + + return false; +} + + +/** + * Process a selection from a form select menu. + * + * \param html The html content handle for the form + * \param control form control with menu + * \param item index of item selected from the menu + * \return NSERROR_OK or appropriate error code. + */ +static nserror form__select_process_selection(html_content *html, + struct form_control *control, int item) +{ + struct box *inline_box; + struct form_option *o; + int count; + nserror ret = NSERROR_OK; + + assert(control != NULL); + assert(html != NULL); + + /** \todo Even though the form code is effectively part of the html + * content handler, poking around inside contents is not good + */ + + inline_box = control->box->children->children; + + for (count = 0, o = control->data.select.items; + o != NULL; + count++, o = o->next) { + if (!control->data.select.multiple && o->selected) { + o->selected = false; + dom_html_option_element_set_selected(o->node, false); + } + + if (count == item) { + if (control->data.select.multiple) { + if (o->selected) { + o->selected = false; + dom_html_option_element_set_selected( + o->node, false); + control->data.select.num_selected--; + } else { + o->selected = true; + dom_html_option_element_set_selected( + o->node, true); + control->data.select.num_selected++; + } + } else { + dom_html_option_element_set_selected( + o->node, true); + o->selected = true; + } + } + + if (o->selected) { + control->data.select.current = o; + } + } + + talloc_free(inline_box->text); + inline_box->text = 0; + + if (control->data.select.num_selected == 0) { + inline_box->text = talloc_strdup(html->bctx, + messages_get("Form_None")); + } else if (control->data.select.num_selected == 1) { + inline_box->text = talloc_strdup(html->bctx, + control->data.select.current->text); + } else { + inline_box->text = talloc_strdup(html->bctx, + messages_get("Form_Many")); + } + + if (!inline_box->text) { + ret = NSERROR_NOMEM; + inline_box->length = 0; + } else { + inline_box->length = strlen(inline_box->text); + } + inline_box->width = control->box->width; + + html__redraw_a_box(html, control->box); + + return ret; +} + +/* exported interface documented in netsurf/form.h */ +nserror form_select_process_selection(struct form_control *control, int item) +{ + assert(control != NULL); + + return form__select_process_selection(control->html, control, item); +} + +/* exported interface documented in netsurf/form.h */ +struct form_option * +form_select_get_option(struct form_control *control, int item) +{ + struct form_option *opt; + + opt = control->data.select.items; + while ((opt != NULL) && (item > 0)) { + opt = opt->next; + item--; + } + return opt; +} + +/* exported interface documented in netsurf/form.h */ +char *form_control_get_name(struct form_control *control) +{ + return control->name; +} + +/* exported interface documented in netsurf/form.h */ +nserror form_control_bounding_rect(struct form_control *control, struct rect *r) +{ + box_bounds( control->box, r ); + return NSERROR_OK; +} + + +/** + * Handle a click on the area of the currently opened select menu. + * + * \param control the select menu which received the click + * \param x X coordinate of click + * \param y Y coordinate of click + */ +void form_select_menu_clicked(struct form_control *control, int x, int y) +{ + struct form_select_menu *menu = control->data.select.menu; + struct form_option *option; + html_content *html = (html_content *)menu->c; + int line_height, line_height_with_spacing; + int item_bottom_y; + int scroll, i; + + scroll = scrollbar_get_offset(menu->scrollbar); + + line_height = menu->line_height; + line_height_with_spacing = line_height + + line_height * SELECT_LINE_SPACING; + + option = control->data.select.items; + item_bottom_y = line_height_with_spacing; + i = 0; + while (option && item_bottom_y < scroll + y) { + item_bottom_y += line_height_with_spacing; + option = option->next; + i++; + } + + if (option != NULL) { + form__select_process_selection(html, control, i); + } + + menu->callback(menu->client_data, 0, 0, menu->width, menu->height); +} + +/** + * Handle mouse action for the currently opened select menu. + * + * \param control the select menu which received the mouse action + * \param mouse current mouse state + * \param x X coordinate of click + * \param y Y coordinate of click + * \return text for the browser status bar or NULL if the menu has + * to be closed + */ +const char *form_select_mouse_action(struct form_control *control, + browser_mouse_state mouse, int x, int y) +{ + struct form_select_menu *menu = control->data.select.menu; + int x0, y0, x1, y1, scrollbar_x; + const char *status = NULL; + bool multiple = control->data.select.multiple; + + x0 = 0; + y0 = 0; + x1 = menu->width; + y1 = menu->height; + scrollbar_x = x1 - SCROLLBAR_WIDTH; + + if (menu->scroll_capture || + (x > scrollbar_x && x < x1 && y > y0 && y < y1)) { + /* The scroll is currently capturing all events or the mouse + * event is taking place on the scrollbar widget area + */ + x -= scrollbar_x; + return scrollbar_mouse_status_to_message( + scrollbar_mouse_action(menu->scrollbar, + mouse, x, y)); + } + + + if (x > x0 && x < scrollbar_x && y > y0 && y < y1) { + /* over option area */ + + if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) + /* button 1 or 2 click */ + form_select_menu_clicked(control, x, y); + + if (!(mouse & BROWSER_MOUSE_CLICK_1 && !multiple)) + /* anything but a button 1 click over a single select + menu */ + status = messages_get(control->data.select.multiple ? + "SelectMClick" : "SelectClick"); + + } else if (!(mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2))) + /* if not a button 1 or 2 click*/ + status = messages_get("SelectClose"); + + return status; +} + +/** + * Handle mouse drag end for the currently opened select menu. + * + * \param control the select menu which received the mouse drag end + * \param mouse current mouse state + * \param x X coordinate of drag end + * \param y Y coordinate of drag end + */ +void form_select_mouse_drag_end(struct form_control *control, + browser_mouse_state mouse, int x, int y) +{ + int x0, y0, x1, y1; + int box_x, box_y; + struct box *box; + struct form_select_menu *menu = control->data.select.menu; + + box = control->box; + + /* Get global coords of scrollbar */ + box_coords(box, &box_x, &box_y); + box_x -= box->border[LEFT].width; + box_y += box->height + box->border[BOTTOM].width + + box->padding[BOTTOM] + box->padding[TOP]; + + /* Get drag end coords relative to scrollbar */ + x = x - box_x; + y = y - box_y; + + if (menu->scroll_capture) { + x -= menu->width - SCROLLBAR_WIDTH; + scrollbar_mouse_drag_end(menu->scrollbar, mouse, x, y); + return; + } + + x0 = 0; + y0 = 0; + x1 = menu->width; + y1 = menu->height; + + + if (x > x0 && x < x1 - SCROLLBAR_WIDTH && y > y0 && y < y1) + /* handle drag end above the option area like a regular click */ + form_select_menu_clicked(control, x, y); +} + +/** + * Callback for the select menus scroll + */ +void form_select_menu_scroll_callback(void *client_data, + struct scrollbar_msg_data *scrollbar_data) +{ + struct form_control *control = client_data; + struct form_select_menu *menu = control->data.select.menu; + html_content *html = (html_content *)menu->c; + + switch (scrollbar_data->msg) { + case SCROLLBAR_MSG_MOVED: + menu->callback(menu->client_data, + 0, 0, + menu->width, + menu->height); + break; + case SCROLLBAR_MSG_SCROLL_START: + { + struct rect rect = { + .x0 = scrollbar_data->x0, + .y0 = scrollbar_data->y0, + .x1 = scrollbar_data->x1, + .y1 = scrollbar_data->y1 + }; + + browser_window_set_drag_type(html->bw, + DRAGGING_CONTENT_SCROLLBAR, &rect); + + menu->scroll_capture = true; + } + break; + case SCROLLBAR_MSG_SCROLL_FINISHED: + menu->scroll_capture = false; + + browser_window_set_drag_type(html->bw, + DRAGGING_NONE, NULL); + break; + default: + break; + } +} + +/** + * Get the dimensions of a select menu. + * + * \param control the select menu to get the dimensions of + * \param width gets updated to menu width + * \param height gets updated to menu height + */ +void form_select_get_dimensions(struct form_control *control, + int *width, int *height) +{ + *width = control->data.select.menu->width; + *height = control->data.select.menu->height; +} + +/** + * Callback for the core select menu. + */ +void form_select_menu_callback(void *client_data, + int x, int y, int width, int height) +{ + html_content *html = client_data; + int menu_x, menu_y; + struct box *box; + + box = html->visible_select_menu->box; + box_coords(box, &menu_x, &menu_y); + + menu_x -= box->border[LEFT].width; + menu_y += box->height + box->border[BOTTOM].width + + box->padding[BOTTOM] + + box->padding[TOP]; + content__request_redraw((struct content *)html, menu_x + x, menu_y + y, + width, height); +} + + +/** + * Set a radio form control and clear the others in the group. + * + * \param radio form control of type GADGET_RADIO + */ + +void form_radio_set(struct form_control *radio) +{ + struct form_control *control; + + assert(radio); + if (!radio->form) + return; + + if (radio->selected) + return; + + for (control = radio->form->controls; control; + control = control->next) { + if (control->type != GADGET_RADIO) + continue; + if (control == radio) + continue; + if (strcmp(control->name, radio->name) != 0) + continue; + + if (control->selected) { + control->selected = false; + dom_html_input_element_set_checked(control->node, false); + html__redraw_a_box(radio->html, control->box); + } + } + + radio->selected = true; + dom_html_input_element_set_checked(radio->node, true); + html__redraw_a_box(radio->html, radio->box); +} + + +/** + * Collect controls and submit a form. + */ + +void form_submit(nsurl *page_url, struct browser_window *target, + struct form *form, struct form_control *submit_button) +{ + char *data = NULL; + struct fetch_multipart_data *success; + nsurl *action_url; + nsurl *action_query; + nserror error; + + assert(form != NULL); + + if (form_successful_controls_dom(form, submit_button, &success) == false) { + guit->misc->warning("NoMemory", 0); + return; + } + + /* Decompose action */ + if (nsurl_create(form->action, &action_url) != NSERROR_OK) { + free(data); + fetch_multipart_data_destroy(success); + guit->misc->warning("NoMemory", 0); + return; + } + + switch (form->method) { + case method_GET: + data = form_url_encode(form, success, true); + if (data == NULL) { + fetch_multipart_data_destroy(success); + guit->misc->warning("NoMemory", 0); + return; + } + + /* Replace query segment */ + error = nsurl_replace_query(action_url, data, &action_query); + if (error != NSERROR_OK) { + nsurl_unref(action_query); + free(data); + fetch_multipart_data_destroy(success); + guit->misc->warning(messages_get_errorcode(error), 0); + return; + } + + /* Construct submit url */ + browser_window_navigate(target, + action_query, + page_url, + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + + nsurl_unref(action_query); + break; + + case method_POST_URLENC: + data = form_url_encode(form, success, false); + if (data == NULL) { + fetch_multipart_data_destroy(success); + guit->misc->warning("NoMemory", 0); + nsurl_unref(action_url); + return; + } + + browser_window_navigate(target, + action_url, + page_url, + BW_NAVIGATE_HISTORY, + data, + NULL, + NULL); + break; + + case method_POST_MULTIPART: + browser_window_navigate(target, + action_url, + page_url, + BW_NAVIGATE_HISTORY, + NULL, + success, + NULL); + + break; + } + + nsurl_unref(action_url); + fetch_multipart_data_destroy(success); + free(data); +} + +void form_gadget_update_value(struct form_control *control, char *value) +{ + switch (control->type) { + case GADGET_HIDDEN: + case GADGET_TEXTBOX: + case GADGET_TEXTAREA: + case GADGET_PASSWORD: + case GADGET_FILE: + if (control->value != NULL) { + free(control->value); + } + control->value = value; + if (control->node != NULL) { + dom_exception err; + dom_string *str; + err = dom_string_create((uint8_t *)value, + strlen(value), &str); + if (err == DOM_NO_ERR) { + if (control->type == GADGET_TEXTAREA) + err = dom_html_text_area_element_set_value( + (dom_html_text_area_element *)(control->node), + str); + else + err = dom_html_input_element_set_value( + (dom_html_input_element *)(control->node), + str); + dom_string_unref(str); + } + } + break; + default: + /* Do nothing */ + break; + } +} diff --git a/content/handlers/html/form_internal.h b/content/handlers/html/form_internal.h new file mode 100644 index 000000000..a77e823b3 --- /dev/null +++ b/content/handlers/html/form_internal.h @@ -0,0 +1,277 @@ +/* + * Copyright 2014 Vincent Sanders + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Interface to form handling functions internal to HTML content handler. + */ + +#ifndef NETSURF_HTML_FORM_INTERNAL_H +#define NETSURF_HTML_FORM_INTERNAL_H + +#include + +#include "netsurf/form.h" + +struct box; +struct form_control; +struct form_option; +struct form_select_menu; +struct form; +struct html_content; +struct dom_string; +struct content; +struct nsurl; +struct fetch_multipart_data; +struct redraw_context; +struct browser_window; + +enum browser_mouse_state; + +/** Type of a struct form_control. */ +typedef enum { + GADGET_HIDDEN, + GADGET_TEXTBOX, + GADGET_RADIO, + GADGET_CHECKBOX, + GADGET_SELECT, + GADGET_TEXTAREA, + GADGET_IMAGE, + GADGET_PASSWORD, + GADGET_SUBMIT, + GADGET_RESET, + GADGET_FILE, + GADGET_BUTTON +} form_control_type; + +/** Data for textarea */ +struct form_textarea_data { + struct form_control *gadget; +}; + +struct image_input_coords { + int x; + int y; +}; + +/** Form control. */ +struct form_control { + void *node; /**< Corresponding DOM node */ + struct html_content *html; /**< HTML content containing control */ + + form_control_type type; /**< Type of control */ + + struct form *form; /**< Containing form */ + + char *name; /**< Control name */ + char *value; /**< Current value of control */ + char *initial_value; /**< Initial value of control */ + bool disabled; /**< Whether control is disabled */ + + struct box *box; /**< Box for control */ + + unsigned int length; /**< Number of characters in control */ + unsigned int maxlength; /**< Maximum characters permitted */ + + bool selected; /**< Whether control is selected */ + + union { + struct { + int mx, my; + } image; + struct { + int num_items; + struct form_option *items, *last_item; + bool multiple; + int num_selected; + /** Currently selected item, if num_selected == 1. */ + struct form_option *current; + struct form_select_menu *menu; + } select; + struct { + struct textarea *ta; + struct dom_string *initial; + struct form_textarea_data data; + } text; /**< input type=text or textarea */ + } data; + + struct form_control *prev; /**< Previous control in this form */ + struct form_control *next; /**< Next control in this form. */ +}; + +/** Form submit method. */ +typedef enum { + method_GET, /**< GET, always url encoded. */ + method_POST_URLENC, /**< POST, url encoded. */ + method_POST_MULTIPART /**< POST, multipart/form-data. */ +} form_method; + +/** HTML form. */ +struct form { + void *node; /**< Corresponding DOM node */ + + char *action; /**< Absolute URL to submit to. */ + char *target; /**< Target to submit to. */ + form_method method; /**< Method and enctype. */ + char *accept_charsets; /**< Charset to submit form in */ + char *document_charset; /**< Charset of document containing form */ + struct form_control *controls; /**< Linked list of controls. */ + struct form_control *last_control; /**< Last control in list. */ + + struct form *prev; /**< Previous form in doc. */ +}; + +/** + * Called by the select menu when it wants an area to be redrawn. The + * coordinates are menu origin relative. + * + * \param client_data data which was passed to form_open_select_menu + * \param x X coordinate of redraw rectangle + * \param y Y coordinate of redraw rectangle + * \param width width of redraw rectangle + * \param height height of redraw rectangle + */ +typedef void(*select_menu_redraw_callback)(void *client_data, + int x, int y, int width, int height); + +/** + * Create a struct form. + * + * \param node DOM node associated with form + * \param action URL to submit form to, or NULL for default + * \param target Target frame of form, or NULL for default + * \param method method and enctype + * \param charset acceptable encodings for form submission, or NULL + * \param doc_charset encoding of containing document, or NULL + * \return A new form or NULL on memory exhaustion + */ +struct form *form_new(void *node, const char *action, const char *target, + form_method method, const char *charset, + const char *doc_charset); + +/** + * Free a form and any controls it owns. + * + * \note There may exist controls attached to box tree nodes which are not + * associated with any form. These will leak at present. Ideally, they will + * be cleaned up when the box tree is destroyed. As that currently happens + * via talloc, this won't happen. These controls are distinguishable, as their + * form field will be NULL. + * + * \param form The form to free + */ +void form_free(struct form *form); + +/** + * Create a struct form_control. + * + * \param node Associated DOM node + * \param type control type + * \return a new structure, or NULL on memory exhaustion + */ +struct form_control *form_new_control(void *node, form_control_type type); + +void form_add_control(struct form *form, struct form_control *control); +void form_free_control(struct form_control *control); +bool form_add_option(struct form_control *control, char *value, char *text, + bool selected, void *node); +bool form_successful_controls(struct form *form, + struct form_control *submit_button, + struct fetch_multipart_data **successful_controls); + +/** + * Identify 'successful' controls via the DOM. + * + * All text strings in the successful controls list will be in the charset most + * appropriate for submission. Therefore, no utf8_to_* processing should be + * performed upon them. + * + * \todo The chosen charset needs to be made available such that it can be + * included in the submission request (e.g. in the fetch's Content-Type header) + * + * See HTML 4.01 section 17.13.2. + * + * \param[in] form form to search for successful controls + * \param[in] submit_button control used to submit the form, if any + * \param[out] successful_controls updated to point to linked list of + * fetch_multipart_data, 0 if no controls + * \return true on success, false on memory exhaustion + */ +bool form_successful_controls_dom(struct form *form, + struct form_control *submit_button, + struct fetch_multipart_data **successful_controls); + + +/** + * Open a select menu for a select form control, creating it if necessary. + * + * \param client_data data passed to the redraw callback + * \param control The select form control for which the menu is being opened + * \param redraw_callback The callback to redraw the select menu. + * \param c The content the select menu is opening for. + * \return false on memory exhaustion, true otherwise + */ +bool form_open_select_menu(void *client_data, + struct form_control *control, + select_menu_redraw_callback redraw_callback, + struct content *c); + + +void form_select_menu_callback(void *client_data, + int x, int y, int width, int height); + + +/** + * Destroy a select menu and free allocated memory. + * + * \param control the select form control owning the select menu being + * destroyed. + */ +void form_free_select_menu(struct form_control *control); + + +/** + * Redraw an opened select menu. + * + * \param control the select menu being redrawn + * \param x the X coordinate to draw the menu at + * \param y the Y coordinate to draw the menu at + * \param scale current redraw scale + * \param clip clipping rectangle + * \param ctx current redraw context + * \return true on success, false otherwise + */ +bool form_redraw_select_menu(struct form_control *control, int x, int y, + float scale, const struct rect *clip, + const struct redraw_context *ctx); + +bool form_clip_inside_select_menu(struct form_control *control, float scale, + const struct rect *clip); +const char *form_select_mouse_action(struct form_control *control, + enum browser_mouse_state mouse, int x, int y); +void form_select_mouse_drag_end(struct form_control *control, + enum browser_mouse_state mouse, int x, int y); +void form_select_get_dimensions(struct form_control *control, + int *width, int *height); +void form_submit(struct nsurl *page_url, struct browser_window *target, + struct form *form, struct form_control *submit_button); +void form_radio_set(struct form_control *radio); + +void form_gadget_update_value(struct form_control *control, char *value); + +#endif diff --git a/content/handlers/html/html.c b/content/handlers/html/html.c new file mode 100644 index 000000000..18c1a7afb --- /dev/null +++ b/content/handlers/html/html.c @@ -0,0 +1,2468 @@ +/* + * Copyright 2007 James Bursa + * Copyright 2010 Michael Drake + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Implementation of HTML content handling. + */ + +#include +#include +#include +#include +#include +#include + +#include "utils/utils.h" +#include "utils/config.h" +#include "utils/corestrings.h" +#include "utils/http.h" +#include "utils/libdom.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/talloc.h" +#include "utils/utf8.h" +#include "utils/nsoption.h" +#include "utils/string.h" +#include "utils/ascii.h" +#include "netsurf/content.h" +#include "netsurf/browser_window.h" +#include "netsurf/utf8.h" +#include "netsurf/layout.h" +#include "netsurf/misc.h" +#include "content/hlcache.h" +#include "desktop/selection.h" +#include "desktop/scrollbar.h" +#include "desktop/textarea.h" +#include "netsurf/bitmap.h" +#include "javascript/js.h" +#include "desktop/gui_internal.h" + +#include "html/box.h" +#include "html/form_internal.h" +#include "html/html_internal.h" +#include "html/imagemap.h" +#include "html/layout.h" +#include "html/search.h" + +#define CHUNK 4096 + +/* Change these to 1 to cause a dump to stderr of the frameset or box + * when the trees have been built. + */ +#define ALWAYS_DUMP_FRAMESET 0 +#define ALWAYS_DUMP_BOX 0 + +static const char *html_types[] = { + "application/xhtml+xml", + "text/html" +}; + +/* Exported interface, see html_internal.h */ +bool fire_dom_event(dom_string *type, dom_node *target, + bool bubbles, bool cancelable) +{ + dom_exception exc; + dom_event *evt; + bool result; + + exc = dom_event_create(&evt); + if (exc != DOM_NO_ERR) return false; + exc = dom_event_init(evt, type, bubbles, cancelable); + if (exc != DOM_NO_ERR) { + dom_event_unref(evt); + return false; + } + NSLOG(netsurf, INFO, "Dispatching '%*s' against %p", + dom_string_length(type), dom_string_data(type), target); + exc = dom_event_target_dispatch_event(target, evt, &result); + if (exc != DOM_NO_ERR) { + result = false; + } + dom_event_unref(evt); + return result; +} + +/** + * Perform post-box-creation conversion of a document + * + * \param c HTML content to complete conversion of + * \param success Whether box tree construction was successful + */ +static void html_box_convert_done(html_content *c, bool success) +{ + nserror err; + dom_exception exc; /* returned by libdom functions */ + dom_node *html; + + NSLOG(netsurf, INFO, "Done XML to box (%p)", c); + + /* Clean up and report error if unsuccessful or aborted */ + if ((success == false) || (c->aborted)) { + html_object_free_objects(c); + + if (success == false) { + content_broadcast_errorcode(&c->base, NSERROR_BOX_CONVERT); + } else { + content_broadcast_errorcode(&c->base, NSERROR_STOPPED); + } + + content_set_error(&c->base); + return; + } + + +#if ALWAYS_DUMP_BOX + box_dump(stderr, c->layout->children, 0, true); +#endif +#if ALWAYS_DUMP_FRAMESET + if (c->frameset) + html_dump_frameset(c->frameset, 0); +#endif + + exc = dom_document_get_document_element(c->document, (void *) &html); + if ((exc != DOM_NO_ERR) || (html == NULL)) { + /** @todo should this call html_object_free_objects(c); + * like the other error paths + */ + NSLOG(netsurf, INFO, "error retrieving html element from dom"); + content_broadcast_errorcode(&c->base, NSERROR_DOM); + content_set_error(&c->base); + return; + } + + /* extract image maps - can't do this sensibly in dom_to_box */ + err = imagemap_extract(c); + if (err != NSERROR_OK) { + NSLOG(netsurf, INFO, "imagemap extraction failed"); + html_object_free_objects(c); + content_broadcast_errorcode(&c->base, err); + content_set_error(&c->base); + dom_node_unref(html); + return; + } + /*imagemap_dump(c);*/ + + /* Destroy the parser binding */ + dom_hubbub_parser_destroy(c->parser); + c->parser = NULL; + + content_set_ready(&c->base); + + if (c->base.active == 0) { + content_set_done(&c->base); + } + + dom_node_unref(html); +} + + +/** process link node */ +static bool html_process_link(html_content *c, dom_node *node) +{ + struct content_rfc5988_link link; /* the link added to the content */ + dom_exception exc; /* returned by libdom functions */ + dom_string *atr_string; + nserror error; + + memset(&link, 0, sizeof(struct content_rfc5988_link)); + + /* check that the relation exists - w3c spec says must be present */ + exc = dom_element_get_attribute(node, corestring_dom_rel, &atr_string); + if ((exc != DOM_NO_ERR) || (atr_string == NULL)) { + return false; + } + /* get a lwc string containing the link relation */ + exc = dom_string_intern(atr_string, &link.rel); + dom_string_unref(atr_string); + if (exc != DOM_NO_ERR) { + return false; + } + + /* check that the href exists - w3c spec says must be present */ + exc = dom_element_get_attribute(node, corestring_dom_href, &atr_string); + if ((exc != DOM_NO_ERR) || (atr_string == NULL)) { + lwc_string_unref(link.rel); + return false; + } + + /* get nsurl */ + error = nsurl_join(c->base_url, dom_string_data(atr_string), + &link.href); + dom_string_unref(atr_string); + if (error != NSERROR_OK) { + lwc_string_unref(link.rel); + return false; + } + + /* look for optional properties -- we don't care if internment fails */ + + exc = dom_element_get_attribute(node, + corestring_dom_hreflang, &atr_string); + if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { + /* get a lwc string containing the href lang */ + exc = dom_string_intern(atr_string, &link.hreflang); + dom_string_unref(atr_string); + } + + exc = dom_element_get_attribute(node, + corestring_dom_type, &atr_string); + if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { + /* get a lwc string containing the type */ + exc = dom_string_intern(atr_string, &link.type); + dom_string_unref(atr_string); + } + + exc = dom_element_get_attribute(node, + corestring_dom_media, &atr_string); + if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { + /* get a lwc string containing the media */ + exc = dom_string_intern(atr_string, &link.media); + dom_string_unref(atr_string); + } + + exc = dom_element_get_attribute(node, + corestring_dom_sizes, &atr_string); + if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { + /* get a lwc string containing the sizes */ + exc = dom_string_intern(atr_string, &link.sizes); + dom_string_unref(atr_string); + } + + /* add to content */ + content__add_rfc5988_link(&c->base, &link); + + if (link.sizes != NULL) + lwc_string_unref(link.sizes); + if (link.media != NULL) + lwc_string_unref(link.media); + if (link.type != NULL) + lwc_string_unref(link.type); + if (link.hreflang != NULL) + lwc_string_unref(link.hreflang); + + nsurl_unref(link.href); + lwc_string_unref(link.rel); + + return true; +} + +/** process title node */ +static bool html_process_title(html_content *c, dom_node *node) +{ + dom_exception exc; /* returned by libdom functions */ + dom_string *title; + char *title_str; + bool success; + + exc = dom_node_get_text_content(node, &title); + if ((exc != DOM_NO_ERR) || (title == NULL)) { + return false; + } + + title_str = squash_whitespace(dom_string_data(title)); + dom_string_unref(title); + + if (title_str == NULL) { + return false; + } + + success = content__set_title(&c->base, title_str); + + free(title_str); + + return success; +} + +static bool html_process_base(html_content *c, dom_node *node) +{ + dom_exception exc; /* returned by libdom functions */ + dom_string *atr_string; + + /* get href attribute if present */ + exc = dom_element_get_attribute(node, + corestring_dom_href, &atr_string); + if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { + nsurl *url; + nserror error; + + /* get url from string */ + error = nsurl_create(dom_string_data(atr_string), &url); + dom_string_unref(atr_string); + if (error == NSERROR_OK) { + if (c->base_url != NULL) + nsurl_unref(c->base_url); + c->base_url = url; + } + } + + + /* get target attribute if present and not already set */ + if (c->base_target != NULL) { + return true; + } + + exc = dom_element_get_attribute(node, + corestring_dom_target, &atr_string); + if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { + /* Validation rules from the HTML5 spec for the base element: + * The target must be one of _blank, _self, _parent, or + * _top or any identifier which does not begin with an + * underscore + */ + if (*dom_string_data(atr_string) != '_' || + dom_string_caseless_lwc_isequal(atr_string, + corestring_lwc__blank) || + dom_string_caseless_lwc_isequal(atr_string, + corestring_lwc__self) || + dom_string_caseless_lwc_isequal(atr_string, + corestring_lwc__parent) || + dom_string_caseless_lwc_isequal(atr_string, + corestring_lwc__top)) { + c->base_target = strdup(dom_string_data(atr_string)); + } + dom_string_unref(atr_string); + } + + return true; +} + +static nserror html_meta_refresh_process_element(html_content *c, dom_node *n) +{ + union content_msg_data msg_data; + const char *url, *end, *refresh = NULL; + char *new_url; + char quote = '\0'; + dom_string *equiv, *content; + dom_exception exc; + nsurl *nsurl; + nserror error = NSERROR_OK; + + exc = dom_element_get_attribute(n, corestring_dom_http_equiv, &equiv); + if (exc != DOM_NO_ERR) { + return NSERROR_DOM; + } + + if (equiv == NULL) { + return NSERROR_OK; + } + + if (!dom_string_caseless_lwc_isequal(equiv, corestring_lwc_refresh)) { + dom_string_unref(equiv); + return NSERROR_OK; + } + + dom_string_unref(equiv); + + exc = dom_element_get_attribute(n, corestring_dom_content, &content); + if (exc != DOM_NO_ERR) { + return NSERROR_DOM; + } + + if (content == NULL) { + return NSERROR_OK; + } + + end = dom_string_data(content) + dom_string_byte_length(content); + + /* content := *LWS intpart fracpart? *LWS [';' *LWS *1url *LWS] + * intpart := 1*DIGIT + * fracpart := 1*('.' | DIGIT) + * url := "url" *LWS '=' *LWS (url-nq | url-sq | url-dq) + * url-nq := *urlchar + * url-sq := "'" *(urlchar | '"') "'" + * url-dq := '"' *(urlchar | "'") '"' + * urlchar := [#x9#x21#x23-#x26#x28-#x7E] | nonascii + * nonascii := [#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF] + */ + + url = dom_string_data(content); + + /* *LWS */ + while (url < end && ascii_is_space(*url)) { + url++; + } + + /* intpart */ + if (url == end || (*url < '0' || '9' < *url)) { + /* Empty content, or invalid timeval */ + dom_string_unref(content); + return NSERROR_OK; + } + + msg_data.delay = (int) strtol(url, &new_url, 10); + /* a very small delay and self-referencing URL can cause a loop + * that grinds machines to a halt. To prevent this we set a + * minimum refresh delay of 1s. */ + if (msg_data.delay < 1) { + msg_data.delay = 1; + } + + url = new_url; + + /* fracpart? (ignored, as delay is integer only) */ + while (url < end && (('0' <= *url && *url <= '9') || + *url == '.')) { + url++; + } + + /* *LWS */ + while (url < end && ascii_is_space(*url)) { + url++; + } + + /* ';' */ + if (url < end && *url == ';') + url++; + + /* *LWS */ + while (url < end && ascii_is_space(*url)) { + url++; + } + + if (url == end) { + /* Just delay specified, so refresh current page */ + dom_string_unref(content); + + c->base.refresh = nsurl_ref( + content_get_url(&c->base)); + + content_broadcast(&c->base, CONTENT_MSG_REFRESH, &msg_data); + + return NSERROR_OK; + } + + /* "url" */ + if (url <= end - 3) { + if (strncasecmp(url, "url", 3) == 0) { + url += 3; + } else { + /* Unexpected input, ignore this header */ + dom_string_unref(content); + return NSERROR_OK; + } + } else { + /* Insufficient input, ignore this header */ + dom_string_unref(content); + return NSERROR_OK; + } + + /* *LWS */ + while (url < end && ascii_is_space(*url)) { + url++; + } + + /* '=' */ + if (url < end) { + if (*url == '=') { + url++; + } else { + /* Unexpected input, ignore this header */ + dom_string_unref(content); + return NSERROR_OK; + } + } else { + /* Insufficient input, ignore this header */ + dom_string_unref(content); + return NSERROR_OK; + } + + /* *LWS */ + while (url < end && ascii_is_space(*url)) { + url++; + } + + /* '"' or "'" */ + if (url < end && (*url == '"' || *url == '\'')) { + quote = *url; + url++; + } + + /* Start of URL */ + refresh = url; + + if (quote != 0) { + /* url-sq | url-dq */ + while (url < end && *url != quote) + url++; + } else { + /* url-nq */ + while (url < end && !ascii_is_space(*url)) + url++; + } + + /* '"' or "'" or *LWS (we don't care) */ + if (url > refresh) { + /* There's a URL */ + new_url = strndup(refresh, url - refresh); + if (new_url == NULL) { + dom_string_unref(content); + return NSERROR_NOMEM; + } + + error = nsurl_join(c->base_url, new_url, &nsurl); + if (error == NSERROR_OK) { + /* broadcast valid refresh url */ + + c->base.refresh = nsurl; + + content_broadcast(&c->base, CONTENT_MSG_REFRESH, + &msg_data); + c->refresh = true; + } + + free(new_url); + + } + + dom_string_unref(content); + + return error; +} + +static bool html_process_img(html_content *c, dom_node *node) +{ + dom_string *src; + nsurl *url; + nserror err; + dom_exception exc; + bool success; + + /* Do nothing if foreground images are disabled */ + if (nsoption_bool(foreground_images) == false) { + return true; + } + + exc = dom_element_get_attribute(node, corestring_dom_src, &src); + if (exc != DOM_NO_ERR || src == NULL) { + return true; + } + + err = nsurl_join(c->base_url, dom_string_data(src), &url); + if (err != NSERROR_OK) { + dom_string_unref(src); + return false; + } + dom_string_unref(src); + + /* Speculatively fetch the image */ + success = html_fetch_object(c, url, NULL, CONTENT_IMAGE, 0, 0, false); + nsurl_unref(url); + + return success; +} + +/* exported function documented in html/html_internal.h */ +void html_finish_conversion(html_content *htmlc) +{ + union content_msg_data msg_data; + dom_exception exc; /* returned by libdom functions */ + dom_node *html; + nserror error; + + /* Bail out if we've been aborted */ + if (htmlc->aborted) { + content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED); + content_set_error(&htmlc->base); + return; + } + + /* create new css selection context */ + error = html_css_new_selection_context(htmlc, &htmlc->select_ctx); + if (error != NSERROR_OK) { + content_broadcast_errorcode(&htmlc->base, error); + content_set_error(&htmlc->base); + return; + } + + + /* fire a simple event named load at the Document's Window + * object, but with its target set to the Document object (and + * the currentTarget set to the Window object) + */ + if (htmlc->jscontext != NULL) { + js_fire_event(htmlc->jscontext, "load", htmlc->document, NULL); + } + + /* convert dom tree to box tree */ + NSLOG(netsurf, INFO, "DOM to box (%p)", htmlc); + content_set_status(&htmlc->base, messages_get("Processing")); + msg_data.explicit_status_text = NULL; + content_broadcast(&htmlc->base, CONTENT_MSG_STATUS, &msg_data); + + exc = dom_document_get_document_element(htmlc->document, (void *) &html); + if ((exc != DOM_NO_ERR) || (html == NULL)) { + NSLOG(netsurf, INFO, "error retrieving html element from dom"); + content_broadcast_errorcode(&htmlc->base, NSERROR_DOM); + content_set_error(&htmlc->base); + return; + } + + error = dom_to_box(html, htmlc, html_box_convert_done); + if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, "box conversion failed"); + dom_node_unref(html); + html_object_free_objects(htmlc); + content_broadcast_errorcode(&htmlc->base, error); + content_set_error(&htmlc->base); + return; + } + + dom_node_unref(html); +} + +/* callback for DOMNodeInserted end type */ +static void +dom_default_action_DOMNodeInserted_cb(struct dom_event *evt, void *pw) +{ + dom_event_target *node; + dom_node_type type; + dom_exception exc; + html_content *htmlc = pw; + + exc = dom_event_get_target(evt, &node); + if ((exc == DOM_NO_ERR) && (node != NULL)) { + exc = dom_node_get_node_type(node, &type); + if ((exc == DOM_NO_ERR) && (type == DOM_ELEMENT_NODE)) { + /* an element node has been inserted */ + dom_html_element_type tag_type; + + exc = dom_html_element_get_tag_type(node, &tag_type); + if (exc != DOM_NO_ERR) { + tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; + } + + switch (tag_type) { + case DOM_HTML_ELEMENT_TYPE_LINK: + /* Handle stylesheet loading */ + html_css_process_link(htmlc, (dom_node *)node); + /* Generic link handling */ + html_process_link(htmlc, (dom_node *)node); + break; + case DOM_HTML_ELEMENT_TYPE_META: + if (htmlc->refresh) + break; + html_meta_refresh_process_element(htmlc, + (dom_node *)node); + break; + case DOM_HTML_ELEMENT_TYPE_TITLE: + if (htmlc->title != NULL) + break; + htmlc->title = dom_node_ref(node); + break; + case DOM_HTML_ELEMENT_TYPE_BASE: + html_process_base(htmlc, (dom_node *)node); + break; + case DOM_HTML_ELEMENT_TYPE_IMG: + html_process_img(htmlc, (dom_node *) node); + break; + case DOM_HTML_ELEMENT_TYPE_STYLE: + html_css_process_style(htmlc, (dom_node *) node); + break; + default: + break; + } + if (htmlc->enable_scripting) { + /* ensure javascript context is available */ + if (htmlc->jscontext == NULL) { + union content_msg_data msg_data; + + msg_data.jscontext = &htmlc->jscontext; + content_broadcast(&htmlc->base, + CONTENT_MSG_GETCTX, + &msg_data); + NSLOG(netsurf, INFO, + "javascript context: %p (htmlc: %p)", + htmlc->jscontext, + htmlc); + } + if (htmlc->jscontext != NULL) { + js_handle_new_element(htmlc->jscontext, + (dom_element *) node); + } + } + } + dom_node_unref(node); + } +} + +/* callback for DOMNodeInserted end type */ +static void +dom_default_action_DOMSubtreeModified_cb(struct dom_event *evt, void *pw) +{ + dom_event_target *node; + dom_node_type type; + dom_exception exc; + html_content *htmlc = pw; + + exc = dom_event_get_target(evt, &node); + if ((exc == DOM_NO_ERR) && (node != NULL)) { + if (htmlc->title == (dom_node *)node) { + /* Node is our title node */ + html_process_title(htmlc, (dom_node *)node); + dom_node_unref(node); + return; + } + + exc = dom_node_get_node_type(node, &type); + if ((exc == DOM_NO_ERR) && (type == DOM_ELEMENT_NODE)) { + /* an element node has been modified */ + dom_html_element_type tag_type; + + exc = dom_html_element_get_tag_type(node, &tag_type); + if (exc != DOM_NO_ERR) { + tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; + } + + switch (tag_type) { + case DOM_HTML_ELEMENT_TYPE_STYLE: + html_css_update_style(htmlc, (dom_node *)node); + break; + default: + break; + } + } + dom_node_unref(node); + } +} + +static void +dom_default_action_finished_cb(struct dom_event *evt, void *pw) +{ + html_content *htmlc = pw; + + if (htmlc->jscontext != NULL) + js_event_cleanup(htmlc->jscontext, evt); +} + +/* callback function selector + * + * selects a callback function for libdom to call based on the type and phase. + * dom_default_action_phase from events/document_event.h + * + * The principle events are: + * DOMSubtreeModified + * DOMAttrModified + * DOMNodeInserted + * DOMNodeInsertedIntoDocument + * + * @return callback function pointer or NULL for none + */ +static dom_default_action_callback +dom_event_fetcher(dom_string *type, + dom_default_action_phase phase, + void **pw) +{ + NSLOG(netsurf, DEEPDEBUG, "type:%s", dom_string_data(type)); + + if (phase == DOM_DEFAULT_ACTION_END) { + if (dom_string_isequal(type, corestring_dom_DOMNodeInserted)) { + return dom_default_action_DOMNodeInserted_cb; + } else if (dom_string_isequal(type, corestring_dom_DOMSubtreeModified)) { + return dom_default_action_DOMSubtreeModified_cb; + } + } else if (phase == DOM_DEFAULT_ACTION_FINISHED) { + return dom_default_action_finished_cb; + } + return NULL; +} + +static void +html_document_user_data_handler(dom_node_operation operation, + dom_string *key, void *data, + struct dom_node *src, + struct dom_node *dst) +{ + if (dom_string_isequal(corestring_dom___ns_key_html_content_data, + key) == false || data == NULL) { + return; + } + + switch (operation) { + case DOM_NODE_CLONED: + NSLOG(netsurf, INFO, "Cloned"); + break; + case DOM_NODE_RENAMED: + NSLOG(netsurf, INFO, "Renamed"); + break; + case DOM_NODE_IMPORTED: + NSLOG(netsurf, INFO, "imported"); + break; + case DOM_NODE_ADOPTED: + NSLOG(netsurf, INFO, "Adopted"); + break; + case DOM_NODE_DELETED: + /* This is the only path I expect */ + break; + default: + NSLOG(netsurf, INFO, "User data operation not handled."); + assert(0); + } +} + + +static nserror +html_create_html_data(html_content *c, const http_parameter *params) +{ + lwc_string *charset; + nserror nerror; + dom_hubbub_parser_params parse_params; + dom_hubbub_error error; + dom_exception err; + void *old_node_data; + + c->parser = NULL; + c->parse_completed = false; + c->document = NULL; + c->quirks = DOM_DOCUMENT_QUIRKS_MODE_NONE; + c->encoding = NULL; + c->base_url = nsurl_ref(content_get_url(&c->base)); + c->base_target = NULL; + c->aborted = false; + c->refresh = false; + c->reflowing = false; + c->title = NULL; + c->bctx = NULL; + c->layout = NULL; + c->background_colour = NS_TRANSPARENT; + c->stylesheet_count = 0; + c->stylesheets = NULL; + c->select_ctx = NULL; + c->universal = NULL; + c->num_objects = 0; + c->object_list = NULL; + c->forms = NULL; + c->imagemaps = NULL; + c->bw = NULL; + c->frameset = NULL; + c->iframe = NULL; + c->page = NULL; + c->font_func = guit->layout; + c->drag_type = HTML_DRAG_NONE; + c->drag_owner.no_owner = true; + c->selection_type = HTML_SELECTION_NONE; + c->selection_owner.none = true; + c->focus_type = HTML_FOCUS_SELF; + c->focus_owner.self = true; + c->search = NULL; + c->search_string = NULL; + c->scripts_count = 0; + c->scripts = NULL; + c->jscontext = NULL; + + c->enable_scripting = nsoption_bool(enable_javascript); + c->base.active = 1; /* The html content itself is active */ + + if (lwc_intern_string("*", SLEN("*"), &c->universal) != lwc_error_ok) { + return NSERROR_NOMEM; + } + + selection_prepare(&c->sel, (struct content *)c, true); + + nerror = http_parameter_list_find_item(params, corestring_lwc_charset, &charset); + if (nerror == NSERROR_OK) { + c->encoding = strdup(lwc_string_data(charset)); + + lwc_string_unref(charset); + + if (c->encoding == NULL) { + lwc_string_unref(c->universal); + c->universal = NULL; + return NSERROR_NOMEM; + + } + c->encoding_source = DOM_HUBBUB_ENCODING_SOURCE_HEADER; + } + + /* Create the parser binding */ + parse_params.enc = c->encoding; + parse_params.fix_enc = true; + parse_params.enable_script = c->enable_scripting; + parse_params.msg = NULL; + parse_params.script = html_process_script; + parse_params.ctx = c; + parse_params.daf = dom_event_fetcher; + + error = dom_hubbub_parser_create(&parse_params, + &c->parser, + &c->document); + if ((error != DOM_HUBBUB_OK) && (c->encoding != NULL)) { + /* Ok, we don't support the declared encoding. Bailing out + * isn't exactly user-friendly, so fall back to autodetect */ + free(c->encoding); + c->encoding = NULL; + + parse_params.enc = c->encoding; + + error = dom_hubbub_parser_create(&parse_params, + &c->parser, + &c->document); + } + if (error != DOM_HUBBUB_OK) { + nsurl_unref(c->base_url); + c->base_url = NULL; + + lwc_string_unref(c->universal); + c->universal = NULL; + + return libdom_hubbub_error_to_nserror(error); + } + + err = dom_node_set_user_data(c->document, + corestring_dom___ns_key_html_content_data, + c, html_document_user_data_handler, + (void *) &old_node_data); + if (err != DOM_NO_ERR) { + dom_hubbub_parser_destroy(c->parser); + nsurl_unref(c->base_url); + c->base_url = NULL; + + lwc_string_unref(c->universal); + c->universal = NULL; + + NSLOG(netsurf, INFO, "Unable to set user data."); + return NSERROR_DOM; + } + + assert(old_node_data == NULL); + + return NSERROR_OK; + +} + +/** + * Create a CONTENT_HTML. + * + * The content_html_data structure is initialized and the HTML parser is + * created. + */ + +static nserror +html_create(const content_handler *handler, + lwc_string *imime_type, + const http_parameter *params, + llcache_handle *llcache, + const char *fallback_charset, + bool quirks, + struct content **c) +{ + html_content *html; + nserror error; + + html = calloc(1, sizeof(html_content)); + if (html == NULL) + return NSERROR_NOMEM; + + error = content__init(&html->base, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(html); + return error; + } + + error = html_create_html_data(html, params); + if (error != NSERROR_OK) { + content_broadcast_errorcode(&html->base, error); + free(html); + return error; + } + + error = html_css_new_stylesheets(html); + if (error != NSERROR_OK) { + content_broadcast_errorcode(&html->base, error); + free(html); + return error; + } + + *c = (struct content *) html; + + return NSERROR_OK; +} + + + +static nserror +html_process_encoding_change(struct content *c, + const char *data, + unsigned int size) +{ + html_content *html = (html_content *) c; + dom_hubbub_parser_params parse_params; + dom_hubbub_error error; + const char *encoding; + const char *source_data; + unsigned long source_size; + + /* Retrieve new encoding */ + encoding = dom_hubbub_parser_get_encoding(html->parser, + &html->encoding_source); + if (encoding == NULL) { + return NSERROR_NOMEM; + } + + if (html->encoding != NULL) { + free(html->encoding); + html->encoding = NULL; + } + + html->encoding = strdup(encoding); + if (html->encoding == NULL) { + return NSERROR_NOMEM; + } + + /* Destroy binding */ + dom_hubbub_parser_destroy(html->parser); + html->parser = NULL; + + if (html->document != NULL) { + dom_node_unref(html->document); + } + + parse_params.enc = html->encoding; + parse_params.fix_enc = true; + parse_params.enable_script = html->enable_scripting; + parse_params.msg = NULL; + parse_params.script = html_process_script; + parse_params.ctx = html; + parse_params.daf = dom_event_fetcher; + + /* Create new binding, using the new encoding */ + error = dom_hubbub_parser_create(&parse_params, + &html->parser, + &html->document); + if (error != DOM_HUBBUB_OK) { + /* Ok, we don't support the declared encoding. Bailing out + * isn't exactly user-friendly, so fall back to Windows-1252 */ + free(html->encoding); + html->encoding = strdup("Windows-1252"); + if (html->encoding == NULL) { + return NSERROR_NOMEM; + } + parse_params.enc = html->encoding; + + error = dom_hubbub_parser_create(&parse_params, + &html->parser, + &html->document); + + if (error != DOM_HUBBUB_OK) { + return libdom_hubbub_error_to_nserror(error); + } + + } + + source_data = content__get_source_data(c, &source_size); + + /* Reprocess all the data. This is safe because + * the encoding is now specified at parser start which means + * it cannot be changed again. + */ + error = dom_hubbub_parser_parse_chunk(html->parser, + (const uint8_t *)source_data, + source_size); + + return libdom_hubbub_error_to_nserror(error); +} + + +/** + * Process data for CONTENT_HTML. + */ + +static bool +html_process_data(struct content *c, const char *data, unsigned int size) +{ + html_content *html = (html_content *) c; + dom_hubbub_error dom_ret; + nserror err = NSERROR_OK; /* assume its all going to be ok */ + + dom_ret = dom_hubbub_parser_parse_chunk(html->parser, + (const uint8_t *) data, + size); + + err = libdom_hubbub_error_to_nserror(dom_ret); + + /* deal with encoding change */ + if (err == NSERROR_ENCODING_CHANGE) { + err = html_process_encoding_change(c, data, size); + } + + /* broadcast the error if necessary */ + if (err != NSERROR_OK) { + content_broadcast_errorcode(c, err); + return false; + } + + return true; +} + + +/** + * Convert a CONTENT_HTML for display. + * + * The following steps are carried out in order: + * + * - parsing to an XML tree is completed + * - stylesheets are fetched + * - the XML tree is converted to a box tree and object fetches are started + * + * On exit, the content status will be either CONTENT_STATUS_DONE if the + * document is completely loaded or CONTENT_STATUS_READY if objects are still + * being fetched. + */ + +static bool html_convert(struct content *c) +{ + html_content *htmlc = (html_content *) c; + dom_exception exc; /* returned by libdom functions */ + + /* The quirk check and associated stylesheet fetch is "safe" + * once the root node has been inserted into the document + * which must have happened by this point in the parse. + * + * faliure to retrive the quirk mode or to start the + * stylesheet fetch is non fatal as this "only" affects the + * render and it would annoy the user to fail the entire + * render for want of a quirks stylesheet. + */ + exc = dom_document_get_quirks_mode(htmlc->document, &htmlc->quirks); + if (exc == DOM_NO_ERR) { + html_css_quirks_stylesheets(htmlc); + NSLOG(netsurf, INFO, "quirks set to %d", htmlc->quirks); + } + + htmlc->base.active--; /* the html fetch is no longer active */ + NSLOG(netsurf, INFO, "%d fetches active (%p)", htmlc->base.active, c); + + /* The parse cannot be completed here because it may be paused + * untill all the resources being fetched have completed. + */ + + /* if there are no active fetches in progress no scripts are + * being fetched or they completed already. + */ + if (html_can_begin_conversion(htmlc)) { + return html_begin_conversion(htmlc); + } + return true; +} + +/* Exported interface documented in html_internal.h */ +bool html_can_begin_conversion(html_content *htmlc) +{ + unsigned int i; + + if (htmlc->base.active != 0) + return false; + + for (i = 0; i != htmlc->stylesheet_count; i++) { + if (htmlc->stylesheets[i].modified) + return false; + } + + return true; +} + +bool +html_begin_conversion(html_content *htmlc) +{ + dom_node *html; + nserror ns_error; + struct form *f; + dom_exception exc; /* returned by libdom functions */ + dom_string *node_name = NULL; + dom_hubbub_error error; + + /* The act of completing the parse can result in additional data + * being flushed through the parser. This may result in new style or + * script nodes, upon which the conversion depends. Thus, once we + * have completed the parse, we must check again to see if we can + * begin the conversion. If we can't, we must stop and wait for the + * new styles/scripts to be processed. Once they have been processed, + * we will be called again to begin the conversion for real. Thus, + * we must also ensure that we don't attempt to complete the parse + * multiple times, so store a flag to indicate that parsing is + * complete to avoid repeating the completion pointlessly. + */ + if (htmlc->parse_completed == false) { + NSLOG(netsurf, INFO, "Completing parse (%p)", htmlc); + /* complete parsing */ + error = dom_hubbub_parser_completed(htmlc->parser); + if (error != DOM_HUBBUB_OK) { + NSLOG(netsurf, INFO, "Parsing failed"); + + content_broadcast_errorcode(&htmlc->base, + libdom_hubbub_error_to_nserror(error)); + + return false; + } + htmlc->parse_completed = true; + } + + if (html_can_begin_conversion(htmlc) == false) { + NSLOG(netsurf, INFO, "Can't begin conversion (%p)", htmlc); + /* We can't proceed (see commentary above) */ + return true; + } + + /* Give up processing if we've been aborted */ + if (htmlc->aborted) { + NSLOG(netsurf, INFO, "Conversion aborted (%p) (active: %u)", + htmlc, htmlc->base.active); + content_set_error(&htmlc->base); + content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED); + return false; + } + + /* complete script execution */ + html_script_exec(htmlc); + + /* fire a simple event that bubbles named DOMContentLoaded at + * the Document. + */ + + /* get encoding */ + if (htmlc->encoding == NULL) { + const char *encoding; + + encoding = dom_hubbub_parser_get_encoding(htmlc->parser, + &htmlc->encoding_source); + if (encoding == NULL) { + content_broadcast_errorcode(&htmlc->base, + NSERROR_NOMEM); + return false; + } + + htmlc->encoding = strdup(encoding); + if (htmlc->encoding == NULL) { + content_broadcast_errorcode(&htmlc->base, + NSERROR_NOMEM); + return false; + } + } + + /* locate root element and ensure it is html */ + exc = dom_document_get_document_element(htmlc->document, (void *) &html); + if ((exc != DOM_NO_ERR) || (html == NULL)) { + NSLOG(netsurf, INFO, "error retrieving html element from dom"); + content_broadcast_errorcode(&htmlc->base, NSERROR_DOM); + return false; + } + + exc = dom_node_get_node_name(html, &node_name); + if ((exc != DOM_NO_ERR) || + (node_name == NULL) || + (!dom_string_caseless_lwc_isequal(node_name, + corestring_lwc_html))) { + NSLOG(netsurf, INFO, "root element not html"); + content_broadcast_errorcode(&htmlc->base, NSERROR_DOM); + dom_node_unref(html); + return false; + } + dom_string_unref(node_name); + + /* Retrieve forms from parser */ + htmlc->forms = html_forms_get_forms(htmlc->encoding, + (dom_html_document *) htmlc->document); + for (f = htmlc->forms; f != NULL; f = f->prev) { + nsurl *action; + + /* Make all actions absolute */ + if (f->action == NULL || f->action[0] == '\0') { + /* HTML5 4.10.22.3 step 9 */ + nsurl *doc_addr = content_get_url(&htmlc->base); + ns_error = nsurl_join(htmlc->base_url, + nsurl_access(doc_addr), + &action); + } else { + ns_error = nsurl_join(htmlc->base_url, + f->action, + &action); + } + + if (ns_error != NSERROR_OK) { + content_broadcast_errorcode(&htmlc->base, ns_error); + + dom_node_unref(html); + return false; + } + + free(f->action); + f->action = strdup(nsurl_access(action)); + nsurl_unref(action); + if (f->action == NULL) { + content_broadcast_errorcode(&htmlc->base, + NSERROR_NOMEM); + + dom_node_unref(html); + return false; + } + + /* Ensure each form has a document encoding */ + if (f->document_charset == NULL) { + f->document_charset = strdup(htmlc->encoding); + if (f->document_charset == NULL) { + content_broadcast_errorcode(&htmlc->base, + NSERROR_NOMEM); + dom_node_unref(html); + return false; + } + } + } + + dom_node_unref(html); + + if (htmlc->base.active == 0) { + html_finish_conversion(htmlc); + } + + return true; +} + + +/** + * Stop loading a CONTENT_HTML. + * + * called when the content is aborted. This must clean up any state + * created during the fetch. + */ + +static void html_stop(struct content *c) +{ + html_content *htmlc = (html_content *) c; + + /* invalidate the html content reference to the javascript context + * as it is about to become invalid and must not be used any + * more. + */ + html_script_invalidate_ctx(htmlc); + + switch (c->status) { + case CONTENT_STATUS_LOADING: + /* Still loading; simply flag that we've been aborted + * html_convert/html_finish_conversion will do the rest */ + htmlc->aborted = true; + break; + + case CONTENT_STATUS_READY: + html_object_abort_objects(htmlc); + + /* If there are no further active fetches and we're still + * in the READY state, transition to the DONE state. */ + if (c->status == CONTENT_STATUS_READY && c->active == 0) { + content_set_done(c); + } + + break; + + case CONTENT_STATUS_DONE: + /* Nothing to do */ + break; + + default: + NSLOG(netsurf, INFO, "Unexpected status %d (%p)", c->status, + c); + assert(0); + } +} + + +/** + * Reformat a CONTENT_HTML to a new width. + */ + +static void html_reformat(struct content *c, int width, int height) +{ + html_content *htmlc = (html_content *) c; + struct box *layout; + uint64_t ms_before; + uint64_t ms_after; + uint64_t ms_interval; + + nsu_getmonotonic_ms(&ms_before); + + htmlc->reflowing = true; + + htmlc->len_ctx.vw = width; + htmlc->len_ctx.vh = height; + htmlc->len_ctx.root_style = htmlc->layout->style; + + layout_document(htmlc, width, height); + layout = htmlc->layout; + + /* width and height are at least margin box of document */ + c->width = layout->x + layout->padding[LEFT] + layout->width + + layout->padding[RIGHT] + layout->border[RIGHT].width + + layout->margin[RIGHT]; + c->height = layout->y + layout->padding[TOP] + layout->height + + layout->padding[BOTTOM] + layout->border[BOTTOM].width + + layout->margin[BOTTOM]; + + /* if boxes overflow right or bottom edge, expand to contain it */ + if (c->width < layout->x + layout->descendant_x1) + c->width = layout->x + layout->descendant_x1; + if (c->height < layout->y + layout->descendant_y1) + c->height = layout->y + layout->descendant_y1; + + selection_reinit(&htmlc->sel, htmlc->layout); + + htmlc->reflowing = false; + + /* calculate next reflow time at three times what it took to reflow */ + nsu_getmonotonic_ms(&ms_after); + + ms_interval = (ms_before - ms_after) * 3; + if (ms_interval < (nsoption_uint(min_reflow_period) * 10)) { + ms_interval = nsoption_uint(min_reflow_period) * 10; + } + c->reformat_time = ms_after + ms_interval; +} + + +/** + * Redraw a box. + * + * \param h content containing the box, of type CONTENT_HTML + * \param box box to redraw + */ + +void html_redraw_a_box(hlcache_handle *h, struct box *box) +{ + int x, y; + + box_coords(box, &x, &y); + + content_request_redraw(h, x, y, + box->padding[LEFT] + box->width + box->padding[RIGHT], + box->padding[TOP] + box->height + box->padding[BOTTOM]); +} + + +/** + * Redraw a box. + * + * \param html content containing the box, of type CONTENT_HTML + * \param box box to redraw. + */ + +void html__redraw_a_box(struct html_content *html, struct box *box) +{ + int x, y; + + box_coords(box, &x, &y); + + content__request_redraw((struct content *)html, x, y, + box->padding[LEFT] + box->width + box->padding[RIGHT], + box->padding[TOP] + box->height + box->padding[BOTTOM]); +} + +static void html_destroy_frameset(struct content_html_frames *frameset) +{ + int i; + + if (frameset->name) { + talloc_free(frameset->name); + frameset->name = NULL; + } + if (frameset->url) { + talloc_free(frameset->url); + frameset->url = NULL; + } + if (frameset->children) { + for (i = 0; i < (frameset->rows * frameset->cols); i++) { + if (frameset->children[i].name) { + talloc_free(frameset->children[i].name); + frameset->children[i].name = NULL; + } + if (frameset->children[i].url) { + nsurl_unref(frameset->children[i].url); + frameset->children[i].url = NULL; + } + if (frameset->children[i].children) + html_destroy_frameset(&frameset->children[i]); + } + talloc_free(frameset->children); + frameset->children = NULL; + } +} + +static void html_destroy_iframe(struct content_html_iframe *iframe) +{ + struct content_html_iframe *next; + next = iframe; + while ((iframe = next) != NULL) { + next = iframe->next; + if (iframe->name) + talloc_free(iframe->name); + if (iframe->url) { + nsurl_unref(iframe->url); + iframe->url = NULL; + } + talloc_free(iframe); + } +} + + +static void html_free_layout(html_content *htmlc) +{ + if (htmlc->bctx != NULL) { + /* freeing talloc context should let the entire box + * set be destroyed + */ + talloc_free(htmlc->bctx); + } +} + +/** + * Destroy a CONTENT_HTML and free all resources it owns. + */ + +static void html_destroy(struct content *c) +{ + html_content *html = (html_content *) c; + struct form *f, *g; + + NSLOG(netsurf, INFO, "content %p", c); + + /* Destroy forms */ + for (f = html->forms; f != NULL; f = g) { + g = f->prev; + + form_free(f); + } + + imagemap_destroy(html); + + if (c->refresh) + nsurl_unref(c->refresh); + + if (html->base_url) + nsurl_unref(html->base_url); + + if (html->parser != NULL) { + dom_hubbub_parser_destroy(html->parser); + html->parser = NULL; + } + + if (html->document != NULL) { + dom_node_unref(html->document); + html->document = NULL; + } + + if (html->title != NULL) { + dom_node_unref(html->title); + html->title = NULL; + } + + /* Free encoding */ + if (html->encoding != NULL) { + free(html->encoding); + html->encoding = NULL; + } + + /* Free base target */ + if (html->base_target != NULL) { + free(html->base_target); + html->base_target = NULL; + } + + /* Free frameset */ + if (html->frameset != NULL) { + html_destroy_frameset(html->frameset); + talloc_free(html->frameset); + html->frameset = NULL; + } + + /* Free iframes */ + if (html->iframe != NULL) { + html_destroy_iframe(html->iframe); + html->iframe = NULL; + } + + /* Destroy selection context */ + if (html->select_ctx != NULL) { + css_select_ctx_destroy(html->select_ctx); + html->select_ctx = NULL; + } + + if (html->universal != NULL) { + lwc_string_unref(html->universal); + html->universal = NULL; + } + + /* Free stylesheets */ + html_css_free_stylesheets(html); + + /* Free scripts */ + html_script_free(html); + + /* Free objects */ + html_object_free_objects(html); + + /* free layout */ + html_free_layout(html); +} + + +static nserror html_clone(const struct content *old, struct content **newc) +{ + /** \todo Clone HTML specifics */ + + /* In the meantime, we should never be called, as HTML contents + * cannot be shared and we're not intending to fix printing's + * cloning of documents. */ + assert(0 && "html_clone should never be called"); + + return true; +} + + +/** + * Handle a window containing a CONTENT_HTML being opened. + */ + +static void +html_open(struct content *c, + struct browser_window *bw, + struct content *page, + struct object_params *params) +{ + html_content *html = (html_content *) c; + + html->bw = bw; + html->page = (html_content *) page; + + html->drag_type = HTML_DRAG_NONE; + html->drag_owner.no_owner = true; + + /* text selection */ + selection_init(&html->sel, html->layout, &html->len_ctx); + html->selection_type = HTML_SELECTION_NONE; + html->selection_owner.none = true; + + html_object_open_objects(html, bw); +} + + +/** + * Handle a window containing a CONTENT_HTML being closed. + */ + +static void html_close(struct content *c) +{ + html_content *htmlc = (html_content *) c; + + selection_clear(&htmlc->sel, false); + + if (htmlc->search != NULL) { + search_destroy_context(htmlc->search); + } + + /* clear the html content reference to the browser window */ + htmlc->bw = NULL; + + /* invalidate the html content reference to the javascript context + * as it is about to become invalid and must not be used any + * more. + */ + html_script_invalidate_ctx(htmlc); + + /* remove all object references from the html content */ + html_object_close_objects(htmlc); +} + + +/** + * Return an HTML content's selection context + */ + +static void html_clear_selection(struct content *c) +{ + html_content *html = (html_content *) c; + + switch (html->selection_type) { + case HTML_SELECTION_NONE: + /* Nothing to do */ + assert(html->selection_owner.none == true); + break; + case HTML_SELECTION_TEXTAREA: + textarea_clear_selection(html->selection_owner.textarea-> + gadget->data.text.ta); + break; + case HTML_SELECTION_SELF: + assert(html->selection_owner.none == false); + selection_clear(&html->sel, true); + break; + case HTML_SELECTION_CONTENT: + content_clear_selection(html->selection_owner.content->object); + break; + default: + break; + } + + /* There is no selection now. */ + html->selection_type = HTML_SELECTION_NONE; + html->selection_owner.none = true; +} + + +/** + * Return an HTML content's selection context + */ + +static char *html_get_selection(struct content *c) +{ + html_content *html = (html_content *) c; + + switch (html->selection_type) { + case HTML_SELECTION_TEXTAREA: + return textarea_get_selection(html->selection_owner.textarea-> + gadget->data.text.ta); + case HTML_SELECTION_SELF: + assert(html->selection_owner.none == false); + return selection_get_copy(&html->sel); + case HTML_SELECTION_CONTENT: + return content_get_selection( + html->selection_owner.content->object); + case HTML_SELECTION_NONE: + /* Nothing to do */ + assert(html->selection_owner.none == true); + break; + default: + break; + } + + return NULL; +} + + +/** + * Get access to any content, link URLs and objects (images) currently + * at the given (x, y) coordinates. + * + * \param[in] c html content to look inside + * \param[in] x x-coordinate of point of interest + * \param[in] y y-coordinate of point of interest + * \param[out] data Positional features struct to be updated with any + * relevent content, or set to NULL if none. + * \return NSERROR_OK on success else appropriate error code. + */ +static nserror +html_get_contextual_content(struct content *c, int x, int y, + struct browser_window_features *data) +{ + html_content *html = (html_content *) c; + + struct box *box = html->layout; + struct box *next; + int box_x = 0, box_y = 0; + + while ((next = box_at_point(&html->len_ctx, box, x, y, + &box_x, &box_y)) != NULL) { + box = next; + + /* hidden boxes are ignored */ + if ((box->style != NULL) && + css_computed_visibility(box->style) == CSS_VISIBILITY_HIDDEN) { + continue; + } + + if (box->iframe) { + browser_window_get_features(box->iframe, + x - box_x, y - box_y, data); + } + + if (box->object) + content_get_contextual_content(box->object, + x - box_x, y - box_y, data); + + if (box->object) + data->object = box->object; + + if (box->href) + data->link = box->href; + + if (box->usemap) { + const char *target = NULL; + nsurl *url = imagemap_get(html, box->usemap, box_x, + box_y, x, y, &target); + /* Box might have imagemap, but no actual link area + * at point */ + if (url != NULL) + data->link = url; + } + if (box->gadget) { + switch (box->gadget->type) { + case GADGET_TEXTBOX: + case GADGET_TEXTAREA: + case GADGET_PASSWORD: + data->form_features = CTX_FORM_TEXT; + break; + + case GADGET_FILE: + data->form_features = CTX_FORM_FILE; + break; + + default: + data->form_features = CTX_FORM_NONE; + break; + } + } + } + return NSERROR_OK; +} + + +/** + * Scroll deepest thing within the content which can be scrolled at given point + * + * \param c html content to look inside + * \param x x-coordinate of point of interest + * \param y y-coordinate of point of interest + * \param scrx number of px try to scroll something in x direction + * \param scry number of px try to scroll something in y direction + * \return true iff scroll was consumed by something in the content + */ +static bool +html_scroll_at_point(struct content *c, int x, int y, int scrx, int scry) +{ + html_content *html = (html_content *) c; + + struct box *box = html->layout; + struct box *next; + int box_x = 0, box_y = 0; + bool handled_scroll = false; + + /* TODO: invert order; visit deepest box first */ + + while ((next = box_at_point(&html->len_ctx, box, x, y, + &box_x, &box_y)) != NULL) { + box = next; + + if (box->style && css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN) + continue; + + /* Pass into iframe */ + if (box->iframe && browser_window_scroll_at_point(box->iframe, + x - box_x, y - box_y, scrx, scry) == true) + return true; + + /* Pass into textarea widget */ + if (box->gadget && (box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_TEXTBOX) && + textarea_scroll(box->gadget->data.text.ta, + scrx, scry) == true) + return true; + + /* Pass into object */ + if (box->object != NULL && content_scroll_at_point( + box->object, x - box_x, y - box_y, + scrx, scry) == true) + return true; + + /* Handle box scrollbars */ + if (box->scroll_y && scrollbar_scroll(box->scroll_y, scry)) + handled_scroll = true; + + if (box->scroll_x && scrollbar_scroll(box->scroll_x, scrx)) + handled_scroll = true; + + if (handled_scroll == true) + return true; + } + + return false; +} + +/** Helper for file gadgets to store their filename unencoded on the + * dom node associated with the gadget. + * + * \todo Get rid of this crap eventually + */ +static void html__dom_user_data_handler(dom_node_operation operation, + dom_string *key, void *_data, struct dom_node *src, + struct dom_node *dst) +{ + char *oldfile; + char *data = (char *)_data; + + if (!dom_string_isequal(corestring_dom___ns_key_file_name_node_data, + key) || data == NULL) { + return; + } + + switch (operation) { + case DOM_NODE_CLONED: + if (dom_node_set_user_data(dst, + corestring_dom___ns_key_file_name_node_data, + strdup(data), html__dom_user_data_handler, + &oldfile) == DOM_NO_ERR) { + if (oldfile != NULL) + free(oldfile); + } + break; + + case DOM_NODE_RENAMED: + case DOM_NODE_IMPORTED: + case DOM_NODE_ADOPTED: + break; + + case DOM_NODE_DELETED: + free(data); + break; + default: + NSLOG(netsurf, INFO, "User data operation not handled."); + assert(0); + } +} + +static void html__set_file_gadget_filename(struct content *c, + struct form_control *gadget, const char *fn) +{ + nserror ret; + char *utf8_fn, *oldfile = NULL; + html_content *html = (html_content *)c; + struct box *file_box = gadget->box; + + ret = guit->utf8->local_to_utf8(fn, 0, &utf8_fn); + if (ret != NSERROR_OK) { + assert(ret != NSERROR_BAD_ENCODING); + NSLOG(netsurf, INFO, + "utf8 to local encoding conversion failed"); + /* Load was for us - just no memory */ + return; + } + + form_gadget_update_value(gadget, utf8_fn); + + /* corestring_dom___ns_key_file_name_node_data */ + if (dom_node_set_user_data((dom_node *)file_box->gadget->node, + corestring_dom___ns_key_file_name_node_data, + strdup(fn), html__dom_user_data_handler, + &oldfile) == DOM_NO_ERR) { + if (oldfile != NULL) + free(oldfile); + } + + /* Redraw box. */ + html__redraw_a_box(html, file_box); +} + +void html_set_file_gadget_filename(struct hlcache_handle *hl, + struct form_control *gadget, const char *fn) +{ + return html__set_file_gadget_filename(hlcache_handle_get_content(hl), + gadget, fn); +} + +/** + * Drop a file onto a content at a particular point, or determine if a file + * may be dropped onto the content at given point. + * + * \param c html content to look inside + * \param x x-coordinate of point of interest + * \param y y-coordinate of point of interest + * \param file path to file to be dropped, or NULL to know if drop allowed + * \return true iff file drop has been handled, or if drop possible (NULL file) + */ +static bool html_drop_file_at_point(struct content *c, int x, int y, char *file) +{ + html_content *html = (html_content *) c; + + struct box *box = html->layout; + struct box *next; + struct box *file_box = NULL; + struct box *text_box = NULL; + int box_x = 0, box_y = 0; + + /* Scan box tree for boxes that can handle drop */ + while ((next = box_at_point(&html->len_ctx, box, x, y, + &box_x, &box_y)) != NULL) { + box = next; + + if (box->style && css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN) + continue; + + if (box->iframe) + return browser_window_drop_file_at_point(box->iframe, + x - box_x, y - box_y, file); + + if (box->object && content_drop_file_at_point(box->object, + x - box_x, y - box_y, file) == true) + return true; + + if (box->gadget) { + switch (box->gadget->type) { + case GADGET_FILE: + file_box = box; + break; + + case GADGET_TEXTBOX: + case GADGET_TEXTAREA: + case GADGET_PASSWORD: + text_box = box; + break; + + default: /* appease compiler */ + break; + } + } + } + + if (!file_box && !text_box) + /* No box capable of handling drop */ + return false; + + if (file == NULL) + /* There is a box capable of handling drop here */ + return true; + + /* Handle the drop */ + if (file_box) { + /* File dropped on file input */ + html__set_file_gadget_filename(c, file_box->gadget, file); + + } else { + /* File dropped on text input */ + + size_t file_len; + FILE *fp = NULL; + char *buffer; + char *utf8_buff; + nserror ret; + unsigned int size; + int bx, by; + + /* Open file */ + fp = fopen(file, "rb"); + if (fp == NULL) { + /* Couldn't open file, but drop was for us */ + return true; + } + + /* Get filesize */ + fseek(fp, 0, SEEK_END); + file_len = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if ((long)file_len == -1) { + /* unable to get file length, but drop was for us */ + fclose(fp); + return true; + } + + /* Allocate buffer for file data */ + buffer = malloc(file_len + 1); + if (buffer == NULL) { + /* No memory, but drop was for us */ + fclose(fp); + return true; + } + + /* Stick file into buffer */ + if (file_len != fread(buffer, 1, file_len, fp)) { + /* Failed, but drop was for us */ + free(buffer); + fclose(fp); + return true; + } + + /* Done with file */ + fclose(fp); + + /* Ensure buffer's string termination */ + buffer[file_len] = '\0'; + + /* TODO: Sniff for text? */ + + /* Convert to UTF-8 */ + ret = guit->utf8->local_to_utf8(buffer, file_len, &utf8_buff); + if (ret != NSERROR_OK) { + /* bad encoding shouldn't happen */ + assert(ret != NSERROR_BAD_ENCODING); + NSLOG(netsurf, INFO, "local to utf8 encoding failed"); + free(buffer); + guit->misc->warning("NoMemory", NULL); + return true; + } + + /* Done with buffer */ + free(buffer); + + /* Get new length */ + size = strlen(utf8_buff); + + /* Simulate a click over the input box, to place caret */ + box_coords(text_box, &bx, &by); + textarea_mouse_action(text_box->gadget->data.text.ta, + BROWSER_MOUSE_PRESS_1, x - bx, y - by); + + /* Paste the file as text */ + textarea_drop_text(text_box->gadget->data.text.ta, + utf8_buff, size); + + free(utf8_buff); + } + + return true; +} + + +/** + * set debug status. + * + * \param c The content to debug + * \param op The debug operation type + */ +static nserror +html_debug(struct content *c, enum content_debug op) +{ + html_redraw_debug = !html_redraw_debug; + + return NSERROR_OK; +} + + +/** + * Dump debug info concerning the html_content + * + * \param c The content to debug + * \param f The file to dump to + * \param op The debug dump type + */ +static nserror +html_debug_dump(struct content *c, FILE *f, enum content_debug op) +{ + html_content *htmlc = (html_content *)c; + dom_node *html; + dom_exception exc; /* returned by libdom functions */ + nserror ret; + + assert(htmlc != NULL); + + if (op == CONTENT_DEBUG_RENDER) { + assert(htmlc->layout != NULL); + box_dump(f, htmlc->layout, 0, true); + ret = NSERROR_OK; + } else { + if (htmlc->document == NULL) { + NSLOG(netsurf, INFO, "No document to dump"); + return NSERROR_DOM; + } + + exc = dom_document_get_document_element(htmlc->document, (void *) &html); + if ((exc != DOM_NO_ERR) || (html == NULL)) { + NSLOG(netsurf, INFO, "Unable to obtain root node"); + return NSERROR_DOM; + } + + ret = libdom_dump_structure(html, f, 0); + + NSLOG(netsurf, INFO, "DOM structure dump returning %d", ret); + + dom_node_unref(html); + } + + return ret; +} + + +#if ALWAYS_DUMP_FRAMESET +/** + * Print a frameset tree to stderr. + */ + +static void +html_dump_frameset(struct content_html_frames *frame, unsigned int depth) +{ + unsigned int i; + int row, col, index; + const char *unit[] = {"px", "%", "*"}; + const char *scrolling[] = {"auto", "yes", "no"}; + + assert(frame); + + fprintf(stderr, "%p ", frame); + + fprintf(stderr, "(%i %i) ", frame->rows, frame->cols); + + fprintf(stderr, "w%g%s ", frame->width.value, unit[frame->width.unit]); + fprintf(stderr, "h%g%s ", frame->height.value,unit[frame->height.unit]); + fprintf(stderr, "(margin w%i h%i) ", + frame->margin_width, frame->margin_height); + + if (frame->name) + fprintf(stderr, "'%s' ", frame->name); + if (frame->url) + fprintf(stderr, "<%s> ", frame->url); + + if (frame->no_resize) + fprintf(stderr, "noresize "); + fprintf(stderr, "(scrolling %s) ", scrolling[frame->scrolling]); + if (frame->border) + fprintf(stderr, "border %x ", + (unsigned int) frame->border_colour); + + fprintf(stderr, "\n"); + + if (frame->children) { + for (row = 0; row != frame->rows; row++) { + for (col = 0; col != frame->cols; col++) { + for (i = 0; i != depth; i++) + fprintf(stderr, " "); + fprintf(stderr, "(%i %i): ", row, col); + index = (row * frame->cols) + col; + html_dump_frameset(&frame->children[index], + depth + 1); + } + } + } +} + +#endif + +/** + * Retrieve HTML document tree + * + * \param h HTML content to retrieve document tree from + * \return Pointer to document tree + */ +dom_document *html_get_document(hlcache_handle *h) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + + return c->document; +} + +/** + * Retrieve box tree + * + * \param h HTML content to retrieve tree from + * \return Pointer to box tree + * + * \todo This API must die, as must all use of the box tree outside of + * HTML content handler + */ +struct box *html_get_box_tree(hlcache_handle *h) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + + return c->layout; +} + +/** + * Retrieve the charset of an HTML document + * + * \param c Content to retrieve charset from + * \param op The content encoding operation to perform. + * \return Pointer to charset, or NULL + */ +static const char *html_encoding(const struct content *c, enum content_encoding_type op) +{ + html_content *html = (html_content *) c; + static char enc_token[10] = "Encoding0"; + + assert(html != NULL); + + if (op == CONTENT_ENCODING_SOURCE) { + enc_token[8] = '0' + html->encoding_source; + return messages_get(enc_token); + } + + return html->encoding; +} + + +/** + * Retrieve framesets used in an HTML document + * + * \param h Content to inspect + * \return Pointer to framesets, or NULL if none + */ +struct content_html_frames *html_get_frameset(hlcache_handle *h) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + + return c->frameset; +} + +/** + * Retrieve iframes used in an HTML document + * + * \param h Content to inspect + * \return Pointer to iframes, or NULL if none + */ +struct content_html_iframe *html_get_iframe(hlcache_handle *h) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + + return c->iframe; +} + +/** + * Retrieve an HTML content's base URL + * + * \param h Content to retrieve base target from + * \return Pointer to URL + */ +nsurl *html_get_base_url(hlcache_handle *h) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + + return c->base_url; +} + +/** + * Retrieve an HTML content's base target + * + * \param h Content to retrieve base target from + * \return Pointer to target, or NULL if none + */ +const char *html_get_base_target(hlcache_handle *h) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + + return c->base_target; +} + + +/** + * Retrieve layout coordinates of box with given id + * + * \param h HTML document to search + * \param frag_id String containing an element id + * \param x Updated to global x coord iff id found + * \param y Updated to global y coord iff id found + * \return true iff id found + */ +bool html_get_id_offset(hlcache_handle *h, lwc_string *frag_id, int *x, int *y) +{ + struct box *pos; + struct box *layout; + + if (content_get_type(h) != CONTENT_HTML) + return false; + + layout = html_get_box_tree(h); + + if ((pos = box_find_by_id(layout, frag_id)) != 0) { + box_coords(pos, x, y); + return true; + } + return false; +} + +/** + * Compute the type of a content + * + * \return CONTENT_HTML + */ +static content_type html_content_type(void) +{ + return CONTENT_HTML; +} + + +static void html_fini(void) +{ + html_css_fini(); +} + +static const content_handler html_content_handler = { + .fini = html_fini, + .create = html_create, + .process_data = html_process_data, + .data_complete = html_convert, + .reformat = html_reformat, + .destroy = html_destroy, + .stop = html_stop, + .mouse_track = html_mouse_track, + .mouse_action = html_mouse_action, + .keypress = html_keypress, + .redraw = html_redraw, + .open = html_open, + .close = html_close, + .get_selection = html_get_selection, + .clear_selection = html_clear_selection, + .get_contextual_content = html_get_contextual_content, + .scroll_at_point = html_scroll_at_point, + .drop_file_at_point = html_drop_file_at_point, + .search = html_search, + .search_clear = html_search_clear, + .debug_dump = html_debug_dump, + .debug = html_debug, + .clone = html_clone, + .get_encoding = html_encoding, + .type = html_content_type, + .no_share = true, +}; + +nserror html_init(void) +{ + uint32_t i; + nserror error; + + error = html_css_init(); + if (error != NSERROR_OK) + goto error; + + for (i = 0; i < NOF_ELEMENTS(html_types); i++) { + error = content_factory_register_handler(html_types[i], + &html_content_handler); + if (error != NSERROR_OK) + goto error; + } + + return NSERROR_OK; + +error: + html_fini(); + + return error; +} + +/** + * Get the browser window containing an HTML content + * + * \param c HTML content + * \return the browser window + */ +struct browser_window *html_get_browser_window(struct content *c) +{ + html_content *html = (html_content *) c; + + assert(c != NULL); + assert(c->handler == &html_content_handler); + + return html->bw; +} diff --git a/content/handlers/html/html.h b/content/handlers/html/html.h new file mode 100644 index 000000000..691e969a5 --- /dev/null +++ b/content/handlers/html/html.h @@ -0,0 +1,187 @@ +/* + * Copyright 2004 James Bursa + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Interface to text/html content handler. + * + * These functions should in general be called via the content interface. + */ + +#ifndef NETSURF_HTML_HTML_H +#define NETSURF_HTML_HTML_H + +#include + +#include +#include + +#include "netsurf/types.h" +#include "netsurf/content_type.h" +#include "netsurf/browser_window.h" +#include "netsurf/mouse.h" +#include "desktop/frame_types.h" + +struct fetch_multipart_data; +struct box; +struct rect; +struct browser_window; +struct content; +struct hlcache_handle; +struct http_parameter; +struct imagemap; +struct object_params; +struct plotters; +struct textarea; +struct scrollbar; +struct scrollbar_msg_data; +struct search_context; +struct selection; +struct nsurl; +struct plot_font_style; + +/** + * Container for stylesheets used by an HTML document + */ +struct html_stylesheet { + struct dom_node *node; /**< dom node associated with sheet */ + struct hlcache_handle *sheet; + bool modified; + bool unused; +}; + +/** + * Container for scripts used by an HTML document + */ +struct html_script { + /** Type of script */ + enum html_script_type { HTML_SCRIPT_INLINE, + HTML_SCRIPT_SYNC, + HTML_SCRIPT_DEFER, + HTML_SCRIPT_ASYNC } type; + union { + struct hlcache_handle *handle; + struct dom_string *string; + } data; /**< Script data */ + struct dom_string *mimetype; + struct dom_string *encoding; + bool already_started; + bool parser_inserted; + bool force_async; + bool ready_exec; + bool async; + bool defer; +}; + + +/** + * An object (img, object, etc. tag) in a CONTENT_HTML document. + */ +struct content_html_object { + struct content *parent; /**< Parent document */ + struct content_html_object *next; /**< Next in chain */ + + struct hlcache_handle *content; /**< Content, or 0. */ + struct box *box; /**< Node in box tree containing it. */ + /** Bitmap of acceptable content types */ + content_type permitted_types; + bool background; /**< This object is a background image. */ +}; + +struct html_scrollbar_data { + struct content *c; + struct box *box; +}; + +/** Frame tree (frameset or frame tag) */ +struct content_html_frames { + int cols; /** number of columns in frameset */ + int rows; /** number of rows in frameset */ + + struct frame_dimension width; /** frame width */ + struct frame_dimension height; /** frame width */ + int margin_width; /** frame margin width */ + int margin_height; /** frame margin height */ + + char *name; /** frame name (for targetting) */ + struct nsurl *url; /** frame url */ + + bool no_resize; /** frame is not resizable */ + browser_scrolling scrolling; /** scrolling characteristics */ + bool border; /** frame has a border */ + colour border_colour; /** frame border colour */ + + struct content_html_frames *children; /** [cols * rows] children */ +}; + +/** Inline frame list (iframe tag) */ +struct content_html_iframe { + struct box *box; + + int margin_width; /** frame margin width */ + int margin_height; /** frame margin height */ + + char *name; /** frame name (for targetting) */ + struct nsurl *url; /** frame url */ + + browser_scrolling scrolling; /** scrolling characteristics */ + bool border; /** frame has a border */ + colour border_colour; /** frame border colour */ + + struct content_html_iframe *next; +}; + +/* entries in stylesheet_content */ +#define STYLESHEET_BASE 0 /* base style sheet */ +#define STYLESHEET_QUIRKS 1 /* quirks mode stylesheet */ +#define STYLESHEET_ADBLOCK 2 /* adblocking stylesheet */ +#define STYLESHEET_USER 3 /* user stylesheet */ +#define STYLESHEET_START 4 /* start of document stylesheets */ + +nserror html_init(void); + +void html_redraw_a_box(struct hlcache_handle *h, struct box *box); + +void html_overflow_scroll_drag_end(struct scrollbar *scrollbar, + browser_mouse_state mouse, int x, int y); + +dom_document *html_get_document(struct hlcache_handle *h); +struct box *html_get_box_tree(struct hlcache_handle *h); +struct content_html_frames *html_get_frameset(struct hlcache_handle *h); +struct content_html_iframe *html_get_iframe(struct hlcache_handle *h); +struct nsurl *html_get_base_url(struct hlcache_handle *h); +const char *html_get_base_target(struct hlcache_handle *h); +void html_set_file_gadget_filename(struct hlcache_handle *hl, + struct form_control *gadget, const char *fn); + +/** + * Retrieve stylesheets used by HTML document + * + * \param h Content to retrieve stylesheets from + * \param n Pointer to location to receive number of sheets + * \return Pointer to array of stylesheets + */ +struct html_stylesheet *html_get_stylesheets(struct hlcache_handle *h, + unsigned int *n); + +struct content_html_object *html_get_objects(struct hlcache_handle *h, + unsigned int *n); +bool html_get_id_offset(struct hlcache_handle *h, lwc_string *frag_id, + int *x, int *y); + +#endif diff --git a/content/handlers/html/html_css.c b/content/handlers/html/html_css.c new file mode 100644 index 000000000..b67d19af6 --- /dev/null +++ b/content/handlers/html/html_css.c @@ -0,0 +1,714 @@ +/* + * Copyright 2013 Vincent Sanders + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Processing for html content css operations. + */ + +#include +#include +#include +#include +#include +#include + +#include "utils/nsoption.h" +#include "utils/corestrings.h" +#include "utils/config.h" +#include "utils/log.h" +#include "netsurf/misc.h" +#include "netsurf/content.h" +#include "content/hlcache.h" +#include "css/css.h" +#include "desktop/gui_internal.h" + +#include "html/html_internal.h" + +static nsurl *html_default_stylesheet_url; +static nsurl *html_adblock_stylesheet_url; +static nsurl *html_quirks_stylesheet_url; +static nsurl *html_user_stylesheet_url; + +static nserror css_error_to_nserror(css_error error) +{ + switch (error) { + case CSS_OK: + return NSERROR_OK; + + case CSS_NOMEM: + return NSERROR_NOMEM; + + case CSS_BADPARM: + return NSERROR_BAD_PARAMETER; + + case CSS_INVALID: + return NSERROR_INVALID; + + case CSS_FILENOTFOUND: + return NSERROR_NOT_FOUND; + + case CSS_NEEDDATA: + return NSERROR_NEED_DATA; + + case CSS_BADCHARSET: + return NSERROR_BAD_ENCODING; + + case CSS_EOF: + case CSS_IMPORTS_PENDING: + case CSS_PROPERTY_NOT_SET: + default: + break; + } + return NSERROR_CSS; +} + +/** + * Callback for fetchcache() for stylesheets. + */ + +static nserror +html_convert_css_callback(hlcache_handle *css, + const hlcache_event *event, + void *pw) +{ + html_content *parent = pw; + unsigned int i; + struct html_stylesheet *s; + + /* Find sheet */ + for (i = 0, s = parent->stylesheets; + i != parent->stylesheet_count; + i++, s++) { + if (s->sheet == css) + break; + } + + assert(i != parent->stylesheet_count); + + switch (event->type) { + + case CONTENT_MSG_DONE: + NSLOG(netsurf, INFO, "done stylesheet slot %d '%s'", i, + nsurl_access(hlcache_handle_get_url(css))); + parent->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); + break; + + case CONTENT_MSG_ERROR: + NSLOG(netsurf, INFO, "stylesheet %s failed: %s", + nsurl_access(hlcache_handle_get_url(css)), + event->data.error); + /* fall through */ + + case CONTENT_MSG_ERRORCODE: + hlcache_handle_release(css); + s->sheet = NULL; + parent->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); + content_add_error(&parent->base, "?", 0); + break; + + case CONTENT_MSG_POINTER: + /* Really don't want this to continue after the switch */ + return NSERROR_OK; + + default: + break; + } + + if (html_can_begin_conversion(parent)) { + html_begin_conversion(parent); + } + + return NSERROR_OK; +} + +static nserror +html_stylesheet_from_domnode(html_content *c, + dom_node *node, + hlcache_handle **sheet) +{ + hlcache_child_context child; + dom_string *style; + nsurl *url; + dom_exception exc; + nserror error; + uint32_t key; + char urlbuf[64]; + + child.charset = c->encoding; + child.quirks = c->base.quirks; + + exc = dom_node_get_text_content(node, &style); + if ((exc != DOM_NO_ERR) || (style == NULL)) { + NSLOG(netsurf, INFO, "No text content"); + return NSERROR_OK; + } + + error = html_css_fetcher_add_item(style, c->base_url, &key); + if (error != NSERROR_OK) { + dom_string_unref(style); + return error; + } + + dom_string_unref(style); + + snprintf(urlbuf, sizeof(urlbuf), "x-ns-css:%u", key); + + error = nsurl_create(urlbuf, &url); + if (error != NSERROR_OK) { + return error; + } + + error = hlcache_handle_retrieve(url, 0, + content_get_url(&c->base), NULL, + html_convert_css_callback, c, &child, CONTENT_CSS, + sheet); + if (error != NSERROR_OK) { + nsurl_unref(url); + return error; + } + + nsurl_unref(url); + + c->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + + return NSERROR_OK; +} + +/** + * Process an inline stylesheet in the document. + * + * \param c content structure + * \param style xml node of style element + * \return true on success, false if an error occurred + */ + +static struct html_stylesheet * +html_create_style_element(html_content *c, dom_node *style) +{ + dom_string *val; + dom_exception exc; + struct html_stylesheet *stylesheets; + + /* type='text/css', or not present (invalid but common) */ + exc = dom_element_get_attribute(style, corestring_dom_type, &val); + if (exc == DOM_NO_ERR && val != NULL) { + if (!dom_string_caseless_lwc_isequal(val, + corestring_lwc_text_css)) { + dom_string_unref(val); + return NULL; + } + dom_string_unref(val); + } + + /* media contains 'screen' or 'all' or not present */ + exc = dom_element_get_attribute(style, corestring_dom_media, &val); + if (exc == DOM_NO_ERR && val != NULL) { + if (strcasestr(dom_string_data(val), "screen") == NULL && + strcasestr(dom_string_data(val), + "all") == NULL) { + dom_string_unref(val); + return NULL; + } + dom_string_unref(val); + } + + /* Extend array */ + stylesheets = realloc(c->stylesheets, + sizeof(struct html_stylesheet) * + (c->stylesheet_count + 1)); + if (stylesheets == NULL) { + + content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + return false; + + } + c->stylesheets = stylesheets; + + c->stylesheets[c->stylesheet_count].node = dom_node_ref(style); + c->stylesheets[c->stylesheet_count].sheet = NULL; + c->stylesheets[c->stylesheet_count].modified = false; + c->stylesheets[c->stylesheet_count].unused = false; + c->stylesheet_count++; + + return c->stylesheets + (c->stylesheet_count - 1); +} + +static bool html_css_process_modified_style(html_content *c, + struct html_stylesheet *s) +{ + hlcache_handle *sheet = NULL; + nserror error; + + error = html_stylesheet_from_domnode(c, s->node, &sheet); + if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, "Failed to update sheet"); + content_broadcast_errorcode(&c->base, error); + return false; + } + + if (sheet != NULL) { + NSLOG(netsurf, INFO, "Updating sheet %p with %p", s->sheet, + sheet); + + if (s->sheet != NULL) { + switch (content_get_status(s->sheet)) { + case CONTENT_STATUS_DONE: + break; + default: + hlcache_handle_abort(s->sheet); + c->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", + c->base.active); + } + hlcache_handle_release(s->sheet); + } + s->sheet = sheet; + } + + s->modified = false; + + return true; +} + +static void html_css_process_modified_styles(void *pw) +{ + html_content *c = pw; + struct html_stylesheet *s; + unsigned int i; + bool all_done = true; + + for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) { + if (c->stylesheets[i].modified) { + all_done &= html_css_process_modified_style(c, s); + } + } + + /* If we failed to process any sheet, schedule a retry */ + if (all_done == false) { + guit->misc->schedule(1000, html_css_process_modified_styles, c); + } +} + +bool html_css_update_style(html_content *c, dom_node *style) +{ + unsigned int i; + struct html_stylesheet *s; + + /* Find sheet */ + for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) { + if (s->node == style) + break; + } + if (i == c->stylesheet_count) { + s = html_create_style_element(c, style); + } + if (s == NULL) { + NSLOG(netsurf, INFO, + "Could not find or create inline stylesheet for %p", + style); + return false; + } + + s->modified = true; + + guit->misc->schedule(0, html_css_process_modified_styles, c); + + return true; +} + +bool html_css_process_style(html_content *c, dom_node *node) +{ + unsigned int i; + dom_string *val; + dom_exception exc; + struct html_stylesheet *s; + + /* Find sheet */ + for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) { + if (s->node == node) + break; + } + + /* Should already exist */ + if (i == c->stylesheet_count) { + return false; + } + + exc = dom_element_get_attribute(node, corestring_dom_media, &val); + if (exc == DOM_NO_ERR && val != NULL) { + if (strcasestr(dom_string_data(val), "screen") == NULL && + strcasestr(dom_string_data(val), + "all") == NULL) { + s->unused = true; + } + dom_string_unref(val); + } + + return true; +} + +bool html_css_process_link(html_content *htmlc, dom_node *node) +{ + dom_string *rel, *type_attr, *media, *href; + struct html_stylesheet *stylesheets; + nsurl *joined; + dom_exception exc; + nserror ns_error; + hlcache_child_context child; + + /* rel= */ + exc = dom_element_get_attribute(node, corestring_dom_rel, &rel); + if (exc != DOM_NO_ERR || rel == NULL) + return true; + + if (strcasestr(dom_string_data(rel), "stylesheet") == 0) { + dom_string_unref(rel); + return true; + } else if (strcasestr(dom_string_data(rel), "alternate") != 0) { + /* Ignore alternate stylesheets */ + dom_string_unref(rel); + return true; + } + dom_string_unref(rel); + + /* type='text/css' or not present */ + exc = dom_element_get_attribute(node, corestring_dom_type, &type_attr); + if (exc == DOM_NO_ERR && type_attr != NULL) { + if (!dom_string_caseless_lwc_isequal(type_attr, + corestring_lwc_text_css)) { + dom_string_unref(type_attr); + return true; + } + dom_string_unref(type_attr); + } + + /* media contains 'screen' or 'all' or not present */ + exc = dom_element_get_attribute(node, corestring_dom_media, &media); + if (exc == DOM_NO_ERR && media != NULL) { + if (strcasestr(dom_string_data(media), "screen") == NULL && + strcasestr(dom_string_data(media), "all") == NULL) { + dom_string_unref(media); + return true; + } + dom_string_unref(media); + } + + /* href='...' */ + exc = dom_element_get_attribute(node, corestring_dom_href, &href); + if (exc != DOM_NO_ERR || href == NULL) + return true; + + /* TODO: only the first preferred stylesheets (ie. + * those with a title attribute) should be loaded + * (see HTML4 14.3) */ + + ns_error = nsurl_join(htmlc->base_url, dom_string_data(href), &joined); + if (ns_error != NSERROR_OK) { + dom_string_unref(href); + goto no_memory; + } + dom_string_unref(href); + + NSLOG(netsurf, INFO, "linked stylesheet %i '%s'", + htmlc->stylesheet_count, nsurl_access(joined)); + + /* extend stylesheets array to allow for new sheet */ + stylesheets = realloc(htmlc->stylesheets, + sizeof(struct html_stylesheet) * + (htmlc->stylesheet_count + 1)); + if (stylesheets == NULL) { + nsurl_unref(joined); + ns_error = NSERROR_NOMEM; + goto no_memory; + } + + htmlc->stylesheets = stylesheets; + htmlc->stylesheets[htmlc->stylesheet_count].node = NULL; + htmlc->stylesheets[htmlc->stylesheet_count].modified = false; + htmlc->stylesheets[htmlc->stylesheet_count].unused = false; + + /* start fetch */ + child.charset = htmlc->encoding; + child.quirks = htmlc->base.quirks; + + ns_error = hlcache_handle_retrieve(joined, 0, + content_get_url(&htmlc->base), + NULL, html_convert_css_callback, + htmlc, &child, CONTENT_CSS, + &htmlc->stylesheets[htmlc->stylesheet_count].sheet); + + nsurl_unref(joined); + + if (ns_error != NSERROR_OK) + goto no_memory; + + htmlc->stylesheet_count++; + + htmlc->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", htmlc->base.active); + + return true; + +no_memory: + content_broadcast_errorcode(&htmlc->base, ns_error); + return false; +} + +/* exported interface documented in html/html.h */ +struct html_stylesheet *html_get_stylesheets(hlcache_handle *h, unsigned int *n) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + assert(n != NULL); + + *n = c->stylesheet_count; + + return c->stylesheets; +} + + +/* exported interface documented in html/html_internal.h */ +nserror html_css_free_stylesheets(html_content *html) +{ + unsigned int i; + + guit->misc->schedule(-1, html_css_process_modified_styles, html); + + for (i = 0; i != html->stylesheet_count; i++) { + if (html->stylesheets[i].sheet != NULL) { + hlcache_handle_release(html->stylesheets[i].sheet); + } + if (html->stylesheets[i].node != NULL) { + dom_node_unref(html->stylesheets[i].node); + } + } + free(html->stylesheets); + + return NSERROR_OK; +} + +/* exported interface documented in html/html_internal.h */ +nserror html_css_quirks_stylesheets(html_content *c) +{ + nserror ns_error = NSERROR_OK; + hlcache_child_context child; + + assert(c->stylesheets != NULL); + + if (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL) { + child.charset = c->encoding; + child.quirks = c->base.quirks; + + ns_error = hlcache_handle_retrieve(html_quirks_stylesheet_url, + 0, content_get_url(&c->base), NULL, + html_convert_css_callback, c, &child, + CONTENT_CSS, + &c->stylesheets[STYLESHEET_QUIRKS].sheet); + if (ns_error != NSERROR_OK) { + return ns_error; + } + + c->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + } + + return ns_error; +} + +/* exported interface documented in html/html_internal.h */ +nserror html_css_new_stylesheets(html_content *c) +{ + nserror ns_error; + hlcache_child_context child; + + if (c->stylesheets != NULL) { + return NSERROR_OK; /* already initialised */ + } + + /* stylesheet 0 is the base style sheet, + * stylesheet 1 is the quirks mode style sheet, + * stylesheet 2 is the adblocking stylesheet, + * stylesheet 3 is the user stylesheet */ + c->stylesheets = calloc(STYLESHEET_START, + sizeof(struct html_stylesheet)); + if (c->stylesheets == NULL) { + return NSERROR_NOMEM; + } + + c->stylesheets[STYLESHEET_BASE].sheet = NULL; + c->stylesheets[STYLESHEET_QUIRKS].sheet = NULL; + c->stylesheets[STYLESHEET_ADBLOCK].sheet = NULL; + c->stylesheets[STYLESHEET_USER].sheet = NULL; + c->stylesheet_count = STYLESHEET_START; + + child.charset = c->encoding; + child.quirks = c->base.quirks; + + ns_error = hlcache_handle_retrieve(html_default_stylesheet_url, 0, + content_get_url(&c->base), NULL, + html_convert_css_callback, c, &child, CONTENT_CSS, + &c->stylesheets[STYLESHEET_BASE].sheet); + if (ns_error != NSERROR_OK) { + return ns_error; + } + + c->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + + + if (nsoption_bool(block_advertisements)) { + ns_error = hlcache_handle_retrieve(html_adblock_stylesheet_url, + 0, content_get_url(&c->base), NULL, + html_convert_css_callback, + c, &child, CONTENT_CSS, + &c->stylesheets[STYLESHEET_ADBLOCK].sheet); + if (ns_error != NSERROR_OK) { + return ns_error; + } + + c->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + + } + + ns_error = hlcache_handle_retrieve(html_user_stylesheet_url, 0, + content_get_url(&c->base), NULL, + html_convert_css_callback, c, &child, CONTENT_CSS, + &c->stylesheets[STYLESHEET_USER].sheet); + if (ns_error != NSERROR_OK) { + return ns_error; + } + + c->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + + return ns_error; +} + +nserror +html_css_new_selection_context(html_content *c, css_select_ctx **ret_select_ctx) +{ + uint32_t i; + css_error css_ret; + css_select_ctx *select_ctx; + + /* check that the base stylesheet loaded; layout fails without it */ + if (c->stylesheets[STYLESHEET_BASE].sheet == NULL) { + return NSERROR_CSS_BASE; + } + + /* Create selection context */ + css_ret = css_select_ctx_create(&select_ctx); + if (css_ret != CSS_OK) { + return css_error_to_nserror(css_ret); + } + + /* Add sheets to it */ + for (i = STYLESHEET_BASE; i != c->stylesheet_count; i++) { + const struct html_stylesheet *hsheet = &c->stylesheets[i]; + css_stylesheet *sheet = NULL; + css_origin origin = CSS_ORIGIN_AUTHOR; + + /* Filter out stylesheets for non-screen media. */ + if (hsheet->unused) { + continue; + } + + if (i < STYLESHEET_USER) { + origin = CSS_ORIGIN_UA; + } else if (i < STYLESHEET_START) { + origin = CSS_ORIGIN_USER; + } + + if (hsheet->sheet != NULL) { + sheet = nscss_get_stylesheet(hsheet->sheet); + } + + if (sheet != NULL) { + css_ret = css_select_ctx_append_sheet(select_ctx, + sheet, + origin, + CSS_MEDIA_SCREEN); + if (css_ret != CSS_OK) { + css_select_ctx_destroy(select_ctx); + return css_error_to_nserror(css_ret); + } + } + } + + /* return new selection context to caller */ + *ret_select_ctx = select_ctx; + return NSERROR_OK; +} + +nserror html_css_init(void) +{ + nserror error; + + error = html_css_fetcher_register(); + if (error != NSERROR_OK) + return error; + + error = nsurl_create("resource:default.css", + &html_default_stylesheet_url); + if (error != NSERROR_OK) + return error; + + error = nsurl_create("resource:adblock.css", + &html_adblock_stylesheet_url); + if (error != NSERROR_OK) + return error; + + error = nsurl_create("resource:quirks.css", + &html_quirks_stylesheet_url); + if (error != NSERROR_OK) + return error; + + error = nsurl_create("resource:user.css", + &html_user_stylesheet_url); + + return error; +} + +void html_css_fini(void) +{ + if (html_user_stylesheet_url != NULL) { + nsurl_unref(html_user_stylesheet_url); + html_user_stylesheet_url = NULL; + } + + if (html_quirks_stylesheet_url != NULL) { + nsurl_unref(html_quirks_stylesheet_url); + html_quirks_stylesheet_url = NULL; + } + + if (html_adblock_stylesheet_url != NULL) { + nsurl_unref(html_adblock_stylesheet_url); + html_adblock_stylesheet_url = NULL; + } + + if (html_default_stylesheet_url != NULL) { + nsurl_unref(html_default_stylesheet_url); + html_default_stylesheet_url = NULL; + } +} diff --git a/content/handlers/html/html_css_fetcher.c b/content/handlers/html/html_css_fetcher.c new file mode 100644 index 000000000..7987ea094 --- /dev/null +++ b/content/handlers/html/html_css_fetcher.c @@ -0,0 +1,325 @@ +/* + * Copyright 2008 Rob Kendrick + * Copyright 2013 John-Mark Bell + * + * This file is part of NetSurf. + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * HTML fetcher for CSS objects + */ + +#include +#include +#include +#include +#include +#include + +#include "netsurf/inttypes.h" +#include "utils/config.h" +#include "utils/log.h" +#include "utils/ring.h" +#include "utils/nsurl.h" +#include "utils/utils.h" +#include "content/fetch.h" +#include "content/fetchers.h" + +#include "html/html_internal.h" + +typedef struct html_css_fetcher_item { + uint32_t key; + dom_string *data; + nsurl *base_url; + + struct html_css_fetcher_item *r_next, *r_prev; +} html_css_fetcher_item; + +typedef struct html_css_fetcher_context { + struct fetch *parent_fetch; + + nsurl *url; + html_css_fetcher_item *item; + + bool aborted; + bool locked; + + struct html_css_fetcher_context *r_next, *r_prev; +} html_css_fetcher_context; + +static uint32_t current_key = 0; +static html_css_fetcher_item *items = NULL; +static html_css_fetcher_context *ring = NULL; + +static bool html_css_fetcher_initialise(lwc_string *scheme) +{ + NSLOG(netsurf, INFO, "html_css_fetcher_initialise called for %s", + lwc_string_data(scheme)); + return true; +} + +static void html_css_fetcher_finalise(lwc_string *scheme) +{ + NSLOG(netsurf, INFO, "html_css_fetcher_finalise called for %s", + lwc_string_data(scheme)); +} + +static bool html_css_fetcher_can_fetch(const nsurl *url) +{ + return true; +} + +static void *html_css_fetcher_setup(struct fetch *parent_fetch, nsurl *url, + bool only_2xx, bool downgrade_tls, const char *post_urlenc, + const struct fetch_multipart_data *post_multipart, + const char **headers) +{ + html_css_fetcher_context *ctx; + lwc_string *path; + uint32_t key; + html_css_fetcher_item *item, *found = NULL; + + /* format of a x-ns-css URL is: + * x-ns-url: + * Where key is an unsigned 32bit integer + */ + + path = nsurl_get_component(url, NSURL_PATH); + /* The path must exist */ + if (path == NULL) { + return NULL; + } + + key = strtoul(lwc_string_data(path), NULL, 10); + + lwc_string_unref(path); + + /* There must be at least one item */ + if (items == NULL) { + return NULL; + } + + item = items; + do { + if (item->key == key) { + found = item; + break; + } + + item = item->r_next; + } while (item != items); + + /* We must have found the item */ + if (found == NULL) { + return NULL; + } + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) + return NULL; + + ctx->parent_fetch = parent_fetch; + ctx->url = nsurl_ref(url); + ctx->item = found; + + RING_INSERT(ring, ctx); + + return ctx; +} + +static bool html_css_fetcher_start(void *ctx) +{ + return true; +} + +static void html_css_fetcher_free(void *ctx) +{ + html_css_fetcher_context *c = ctx; + + nsurl_unref(c->url); + if (c->item != NULL) { + nsurl_unref(c->item->base_url); + dom_string_unref(c->item->data); + RING_REMOVE(items, c->item); + free(c->item); + } + RING_REMOVE(ring, c); + free(ctx); +} + +static void html_css_fetcher_abort(void *ctx) +{ + html_css_fetcher_context *c = ctx; + + /* To avoid the poll loop having to deal with the fetch context + * disappearing from under it, we simply flag the abort here. + * The poll loop itself will perform the appropriate cleanup. + */ + c->aborted = true; +} + +static void html_css_fetcher_send_callback(const fetch_msg *msg, + html_css_fetcher_context *c) +{ + c->locked = true; + fetch_send_callback(msg, c->parent_fetch); + c->locked = false; +} + +static void html_css_fetcher_poll(lwc_string *scheme) +{ + fetch_msg msg; + html_css_fetcher_context *c, *next; + + if (ring == NULL) return; + + /* Iterate over ring, processing each pending fetch */ + c = ring; + do { + /* Ignore fetches that have been flagged as locked. + * This allows safe re-entrant calls to this function. + * Re-entrancy can occur if, as a result of a callback, + * the interested party causes fetch_poll() to be called + * again. + */ + if (c->locked == true) { + next = c->r_next; + continue; + } + + /* Only process non-aborted fetches */ + if (c->aborted) { + /* Nothing to do */ + assert(c->locked == false); + } else if (c->item != NULL) { + char header[4096]; + + fetch_set_http_code(c->parent_fetch, 200); + + /* Any callback can result in the fetch being aborted. + * Therefore, we _must_ check for this after _every_ + * call to html_css_fetcher_send_callback(). + */ + snprintf(header, sizeof header, + "Content-Type: text/css; charset=utf-8"); + msg.type = FETCH_HEADER; + msg.data.header_or_data.buf = (const uint8_t *) header; + msg.data.header_or_data.len = strlen(header); + html_css_fetcher_send_callback(&msg, c); + + if (c->aborted == false) { + snprintf(header, sizeof header, + "Content-Length: %"PRIsizet, + dom_string_byte_length(c->item->data)); + msg.type = FETCH_HEADER; + msg.data.header_or_data.buf = + (const uint8_t *) header; + msg.data.header_or_data.len = strlen(header); + html_css_fetcher_send_callback(&msg, c); + } + + if (c->aborted == false) { + snprintf(header, sizeof header, + "X-NS-Base: %.*s", + (int) nsurl_length(c->item->base_url), + nsurl_access(c->item->base_url)); + msg.type = FETCH_HEADER; + msg.data.header_or_data.buf = + (const uint8_t *) header; + msg.data.header_or_data.len = strlen(header); + html_css_fetcher_send_callback(&msg, c); + } + + if (c->aborted == false) { + msg.type = FETCH_DATA; + msg.data.header_or_data.buf = + (const uint8_t *) + dom_string_data(c->item->data); + msg.data.header_or_data.len = + dom_string_byte_length(c->item->data); + html_css_fetcher_send_callback(&msg, c); + } + + if (c->aborted == false) { + msg.type = FETCH_FINISHED; + html_css_fetcher_send_callback(&msg, c); + } + } else { + NSLOG(netsurf, INFO, "Processing of %s failed!", + nsurl_access(c->url)); + + /* Ensure that we're unlocked here. If we aren't, + * then html_css_fetcher_process() is broken. + */ + assert(c->locked == false); + } + + /* Compute next fetch item at the last possible moment as + * processing this item may have added to the ring. + */ + next = c->r_next; + + fetch_remove_from_queues(c->parent_fetch); + fetch_free(c->parent_fetch); + + /* Advance to next ring entry, exiting if we've reached + * the start of the ring or the ring has become empty + */ + } while ( (c = next) != ring && ring != NULL); +} + +/* exported interface documented in html_internal.h */ +nserror html_css_fetcher_register(void) +{ + lwc_string *scheme; + const struct fetcher_operation_table html_css_fetcher_ops = { + .initialise = html_css_fetcher_initialise, + .acceptable = html_css_fetcher_can_fetch, + .setup = html_css_fetcher_setup, + .start = html_css_fetcher_start, + .abort = html_css_fetcher_abort, + .free = html_css_fetcher_free, + .poll = html_css_fetcher_poll, + .finalise = html_css_fetcher_finalise + }; + + if (lwc_intern_string("x-ns-css", SLEN("x-ns-css"), + &scheme) != lwc_error_ok) { + NSLOG(netsurf, INFO, "could not intern \"x-ns-css\"."); + return NSERROR_INIT_FAILED; + } + + return fetcher_add(scheme, &html_css_fetcher_ops); +} + +/* exported interface documented in html_internal.h */ +nserror +html_css_fetcher_add_item(dom_string *data, nsurl *base_url, uint32_t *key) +{ + html_css_fetcher_item *item = malloc(sizeof(*item)); + + if (item == NULL) { + return NSERROR_NOMEM; + } + + *key = item->key = current_key++; + item->data = dom_string_ref(data); + item->base_url = nsurl_ref(base_url); + + RING_INSERT(items, item); + + return NSERROR_OK; +} diff --git a/content/handlers/html/html_forms.c b/content/handlers/html/html_forms.c new file mode 100644 index 000000000..915eb002f --- /dev/null +++ b/content/handlers/html/html_forms.c @@ -0,0 +1,579 @@ +/* + * Copyright 2011 Vincent Sanders + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * HTML form handling implementation + */ + +#include "utils/config.h" +#include "utils/corestrings.h" +#include "utils/log.h" + +#include "html/form_internal.h" +#include "html/html_internal.h" + +/** + * process form element from dom + */ +static struct form * +parse_form_element(const char *docenc, dom_node *node) +{ + dom_string *ds_action = NULL; + dom_string *ds_charset = NULL; + dom_string *ds_target = NULL; + dom_string *ds_method = NULL; + dom_string *ds_enctype = NULL; + char *action = NULL, *charset = NULL, *target = NULL; + form_method method; + dom_html_form_element *formele = (dom_html_form_element *)(node); + struct form * ret = NULL; + + /* Retrieve the attributes from the node */ + if (dom_html_form_element_get_action(formele, + &ds_action) != DOM_NO_ERR) + goto out; + + if (dom_html_form_element_get_accept_charset(formele, + &ds_charset) != DOM_NO_ERR) + goto out; + + if (dom_html_form_element_get_target(formele, + &ds_target) != DOM_NO_ERR) + goto out; + + if (dom_html_form_element_get_method(formele, + &ds_method) != DOM_NO_ERR) + goto out; + + if (dom_html_form_element_get_enctype(formele, + &ds_enctype) != DOM_NO_ERR) + goto out; + + /* Extract the plain attributes ready for use. We have to do this + * because we cannot guarantee that the dom_strings are NULL terminated + * and thus we copy them. + */ + if (ds_action != NULL) + action = strndup(dom_string_data(ds_action), + dom_string_byte_length(ds_action)); + + if (ds_charset != NULL) + charset = strndup(dom_string_data(ds_charset), + dom_string_byte_length(ds_charset)); + + if (ds_target != NULL) + target = strndup(dom_string_data(ds_target), + dom_string_byte_length(ds_target)); + + /* Determine the method */ + method = method_GET; + if (ds_method != NULL) { + if (dom_string_caseless_lwc_isequal(ds_method, + corestring_lwc_post)) { + method = method_POST_URLENC; + if (ds_enctype != NULL) { + if (dom_string_caseless_lwc_isequal(ds_enctype, + corestring_lwc_multipart_form_data)) { + + method = method_POST_MULTIPART; + } + } + } + } + + /* Construct the form object */ + ret = form_new(node, action, target, method, charset, docenc); + +out: + if (ds_action != NULL) + dom_string_unref(ds_action); + if (ds_charset != NULL) + dom_string_unref(ds_charset); + if (ds_target != NULL) + dom_string_unref(ds_target); + if (ds_method != NULL) + dom_string_unref(ds_method); + if (ds_enctype != NULL) + dom_string_unref(ds_enctype); + if (action != NULL) + free(action); + if (charset != NULL) + free(charset); + if (target != NULL) + free(target); + return ret; +} + +/* documented in html_internal.h */ +struct form *html_forms_get_forms(const char *docenc, dom_html_document *doc) +{ + dom_html_collection *forms; + struct form *ret = NULL, *newf; + dom_node *node; + unsigned long n; + uint32_t nforms; + + if (doc == NULL) + return NULL; + + /* Attempt to build a set of all the forms */ + if (dom_html_document_get_forms(doc, &forms) != DOM_NO_ERR) + return NULL; + + /* Count the number of forms so we can iterate */ + if (dom_html_collection_get_length(forms, &nforms) != DOM_NO_ERR) + goto out; + + /* Iterate the forms collection, making form structs for returning */ + for (n = 0; n < nforms; ++n) { + if (dom_html_collection_item(forms, n, &node) != DOM_NO_ERR) { + goto out; + } + newf = parse_form_element(docenc, node); + dom_node_unref(node); + if (newf == NULL) { + goto err; + } + newf->prev = ret; + ret = newf; + } + + /* All went well */ + goto out; +err: + while (ret != NULL) { + struct form *prev = ret->prev; + /* Destroy ret */ + free(ret); + ret = prev; + } +out: + /* Finished with the collection, return it */ + dom_html_collection_unref(forms); + + return ret; +} + +static struct form * +find_form(struct form *forms, dom_html_form_element *form) +{ + while (forms != NULL) { + if (forms->node == form) + break; + forms = forms->prev; + } + + return forms; +} + +static struct form_control * +parse_button_element(struct form *forms, dom_html_button_element *button) +{ + struct form_control *control = NULL; + dom_exception err; + dom_html_form_element *form = NULL; + dom_string *ds_type = NULL; + dom_string *ds_value = NULL; + dom_string *ds_name = NULL; + + err = dom_html_button_element_get_form(button, &form); + if (err != DOM_NO_ERR) + goto out; + + err = dom_html_button_element_get_type(button, &ds_type); + if (err != DOM_NO_ERR) + goto out; + + if (ds_type == NULL) { + control = form_new_control(button, GADGET_SUBMIT); + } else { + if (dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_submit)) { + control = form_new_control(button, GADGET_SUBMIT); + } else if (dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_reset)) { + control = form_new_control(button, GADGET_RESET); + } else { + control = form_new_control(button, GADGET_BUTTON); + } + } + + if (control == NULL) + goto out; + + err = dom_html_button_element_get_value(button, &ds_value); + if (err != DOM_NO_ERR) + goto out; + err = dom_html_button_element_get_name(button, &ds_name); + if (err != DOM_NO_ERR) + goto out; + + if (ds_value != NULL) { + control->value = strndup( + dom_string_data(ds_value), + dom_string_byte_length(ds_value)); + + if (control->value == NULL) { + form_free_control(control); + control = NULL; + goto out; + } + } + + if (ds_name != NULL) { + control->name = strndup( + dom_string_data(ds_name), + dom_string_byte_length(ds_name)); + + if (control->name == NULL) { + form_free_control(control); + control = NULL; + goto out; + } + } + + if (form != NULL && control != NULL) + form_add_control(find_form(forms, form), control); + +out: + if (form != NULL) + dom_node_unref(form); + if (ds_type != NULL) + dom_string_unref(ds_type); + if (ds_value != NULL) + dom_string_unref(ds_value); + if (ds_name != NULL) + dom_string_unref(ds_name); + + return control; +} + +static struct form_control * +parse_input_element(struct form *forms, dom_html_input_element *input) +{ + struct form_control *control = NULL; + dom_html_form_element *form = NULL; + dom_string *ds_type = NULL; + dom_string *ds_name = NULL; + dom_string *ds_value = NULL; + + char *name = NULL; + + if (dom_html_input_element_get_form(input, &form) != DOM_NO_ERR) + goto out; + + if (dom_html_input_element_get_type(input, &ds_type) != DOM_NO_ERR) + goto out; + + if (dom_html_input_element_get_name(input, &ds_name) != DOM_NO_ERR) + goto out; + + if (ds_name != NULL) + name = strndup(dom_string_data(ds_name), + dom_string_byte_length(ds_name)); + + if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_password)) { + control = form_new_control(input, GADGET_PASSWORD); + } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_file)) { + control = form_new_control(input, GADGET_FILE); + } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_hidden)) { + control = form_new_control(input, GADGET_HIDDEN); + } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_checkbox)) { + control = form_new_control(input, GADGET_CHECKBOX); + } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_radio)) { + control = form_new_control(input, GADGET_RADIO); + } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_submit)) { + control = form_new_control(input, GADGET_SUBMIT); + } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_reset)) { + control = form_new_control(input, GADGET_RESET); + } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_button)) { + control = form_new_control(input, GADGET_BUTTON); + } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, + corestring_lwc_image)) { + control = form_new_control(input, GADGET_IMAGE); + } else { + control = form_new_control(input, GADGET_TEXTBOX); + } + + if (control == NULL) + goto out; + + if (name != NULL) { + /* Hand the name string over */ + control->name = name; + name = NULL; + } + + if (control->type == GADGET_CHECKBOX || control->type == GADGET_RADIO) { + bool selected; + if (dom_html_input_element_get_checked( + input, &selected) == DOM_NO_ERR) { + control->selected = selected; + } + } + + if (control->type == GADGET_PASSWORD || + control->type == GADGET_TEXTBOX) { + int32_t maxlength; + if (dom_html_input_element_get_max_length( + input, &maxlength) != DOM_NO_ERR) { + maxlength = -1; + } + + if (maxlength >= 0) { + /* Got valid maxlength */ + control->maxlength = maxlength; + } else { + /* Input has no maxlength attr, or + * dom_html_input_element_get_max_length failed. + * + * Set it to something insane. */ + control->maxlength = UINT_MAX; + } + } + + if (control->type != GADGET_FILE && control->type != GADGET_IMAGE) { + if (dom_html_input_element_get_value( + input, &ds_value) == DOM_NO_ERR) { + if (ds_value != NULL) { + control->value = strndup( + dom_string_data(ds_value), + dom_string_byte_length(ds_value)); + if (control->value == NULL) { + form_free_control(control); + control = NULL; + goto out; + } + control->length = strlen(control->value); + } + } + + if (control->type == GADGET_TEXTBOX || + control->type == GADGET_PASSWORD) { + if (control->value == NULL) { + control->value = strdup(""); + if (control->value == NULL) { + form_free_control(control); + control = NULL; + goto out; + } + + control->length = 0; + } + + control->initial_value = strdup(control->value); + if (control->initial_value == NULL) { + form_free_control(control); + control = NULL; + goto out; + } + } + } + + if (form != NULL && control != NULL) + form_add_control(find_form(forms, form), control); + +out: + if (form != NULL) + dom_node_unref(form); + if (ds_type != NULL) + dom_string_unref(ds_type); + if (ds_name != NULL) + dom_string_unref(ds_name); + if (ds_value != NULL) + dom_string_unref(ds_value); + + if (name != NULL) + free(name); + + return control; +} + +static struct form_control * +parse_textarea_element(struct form *forms, dom_html_text_area_element *ta) +{ + struct form_control *control = NULL; + dom_html_form_element *form = NULL; + dom_string *ds_name = NULL; + + char *name = NULL; + + if (dom_html_text_area_element_get_form(ta, &form) != DOM_NO_ERR) + goto out; + + if (dom_html_text_area_element_get_name(ta, &ds_name) != DOM_NO_ERR) + goto out; + + if (ds_name != NULL) + name = strndup(dom_string_data(ds_name), + dom_string_byte_length(ds_name)); + + control = form_new_control(ta, GADGET_TEXTAREA); + + if (control == NULL) + goto out; + + if (name != NULL) { + /* Hand the name string over */ + control->name = name; + name = NULL; + } + + if (form != NULL && control != NULL) + form_add_control(find_form(forms, form), control); + +out: + if (form != NULL) + dom_node_unref(form); + if (ds_name != NULL) + dom_string_unref(ds_name); + + if (name != NULL) + free(name); + + + return control; +} + +static struct form_control * +parse_select_element(struct form *forms, dom_html_select_element *select) +{ + struct form_control *control = NULL; + dom_html_form_element *form = NULL; + dom_string *ds_name = NULL; + + char *name = NULL; + + if (dom_html_select_element_get_form(select, &form) != DOM_NO_ERR) + goto out; + + if (dom_html_select_element_get_name(select, &ds_name) != DOM_NO_ERR) + goto out; + + if (ds_name != NULL) + name = strndup(dom_string_data(ds_name), + dom_string_byte_length(ds_name)); + + control = form_new_control(select, GADGET_SELECT); + + if (control == NULL) + goto out; + + if (name != NULL) { + /* Hand the name string over */ + control->name = name; + name = NULL; + } + + dom_html_select_element_get_multiple(select, + &(control->data.select.multiple)); + + if (form != NULL && control != NULL) + form_add_control(find_form(forms, form), control); + +out: + if (form != NULL) + dom_node_unref(form); + if (ds_name != NULL) + dom_string_unref(ds_name); + + if (name != NULL) + free(name); + + + return control; +} + + +static struct form_control * +invent_fake_gadget(dom_node *node) +{ + struct form_control *ctl = form_new_control(node, GADGET_HIDDEN); + if (ctl != NULL) { + ctl->value = strdup(""); + ctl->initial_value = strdup(""); + ctl->name = strdup("foo"); + + if (ctl->value == NULL || ctl->initial_value == NULL || + ctl->name == NULL) { + form_free_control(ctl); + ctl = NULL; + } + } + return ctl; +} + +/* documented in html_internal.h */ +struct form_control *html_forms_get_control_for_node(struct form *forms, + dom_node *node) +{ + struct form *f; + struct form_control *ctl = NULL; + dom_exception err; + dom_string *ds_name = NULL; + + /* Step one, see if we already have a control */ + for (f = forms; f != NULL; f = f->prev) { + for (ctl = f->controls; ctl != NULL; ctl = ctl->next) { + if (ctl->node == node) + return ctl; + } + } + + /* Step two, extract the node's name so we can construct a gadget. */ + err = dom_element_get_tag_name(node, &ds_name); + if (err == DOM_NO_ERR && ds_name != NULL) { + + /* Step three, attempt to work out what gadget to make */ + if (dom_string_caseless_lwc_isequal(ds_name, + corestring_lwc_button)) { + ctl = parse_button_element(forms, + (dom_html_button_element *) node); + } else if (dom_string_caseless_lwc_isequal(ds_name, + corestring_lwc_input)) { + ctl = parse_input_element(forms, + (dom_html_input_element *) node); + } else if (dom_string_caseless_lwc_isequal(ds_name, + corestring_lwc_textarea)) { + ctl = parse_textarea_element(forms, + (dom_html_text_area_element *) node); + } else if (dom_string_caseless_lwc_isequal(ds_name, + corestring_lwc_select)) { + ctl = parse_select_element(forms, + (dom_html_select_element *) node); + } + } + + /* If all else fails, fake gadget time */ + if (ctl == NULL) + ctl = invent_fake_gadget(node); + + if (ds_name != NULL) + dom_string_unref(ds_name); + + return ctl; +} diff --git a/content/handlers/html/html_interaction.c b/content/handlers/html/html_interaction.c new file mode 100644 index 000000000..898f55bc2 --- /dev/null +++ b/content/handlers/html/html_interaction.c @@ -0,0 +1,1434 @@ +/* + * Copyright 2006 James Bursa + * Copyright 2006 Richard Wilson + * Copyright 2008 Michael Drake + * Copyright 2009 Paul Blokus + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * implementation of user interaction with a CONTENT_HTML. + */ + +#include +#include + +#include + +#include "utils/corestrings.h" +#include "utils/messages.h" +#include "utils/utils.h" +#include "utils/log.h" +#include "utils/nsoption.h" +#include "netsurf/content.h" +#include "netsurf/browser_window.h" +#include "netsurf/mouse.h" +#include "netsurf/misc.h" +#include "netsurf/layout.h" +#include "netsurf/keypress.h" +#include "content/hlcache.h" +#include "desktop/frames.h" +#include "desktop/scrollbar.h" +#include "desktop/selection.h" +#include "desktop/textarea.h" +#include "javascript/js.h" +#include "desktop/gui_internal.h" + +#include "html/box.h" +#include "html/box_textarea.h" +#include "html/font.h" +#include "html/form_internal.h" +#include "html/html_internal.h" +#include "html/imagemap.h" +#include "html/search.h" + +/** + * Get pointer shape for given box + * + * \param box box in question + * \param imagemap whether an imagemap applies to the box + */ + +static browser_pointer_shape get_pointer_shape(struct box *box, bool imagemap) +{ + browser_pointer_shape pointer; + css_computed_style *style; + enum css_cursor_e cursor; + lwc_string **cursor_uris; + + if (box->type == BOX_FLOAT_LEFT || box->type == BOX_FLOAT_RIGHT) + style = box->children->style; + else + style = box->style; + + if (style == NULL) + return BROWSER_POINTER_DEFAULT; + + cursor = css_computed_cursor(style, &cursor_uris); + + switch (cursor) { + case CSS_CURSOR_AUTO: + if (box->href || (box->gadget && + (box->gadget->type == GADGET_IMAGE || + box->gadget->type == GADGET_SUBMIT)) || + imagemap) { + /* link */ + pointer = BROWSER_POINTER_POINT; + } else if (box->gadget && + (box->gadget->type == GADGET_TEXTBOX || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_TEXTAREA)) { + /* text input */ + pointer = BROWSER_POINTER_CARET; + } else { + /* html content doesn't mind */ + pointer = BROWSER_POINTER_AUTO; + } + break; + case CSS_CURSOR_CROSSHAIR: + pointer = BROWSER_POINTER_CROSS; + break; + case CSS_CURSOR_POINTER: + pointer = BROWSER_POINTER_POINT; + break; + case CSS_CURSOR_MOVE: + pointer = BROWSER_POINTER_MOVE; + break; + case CSS_CURSOR_E_RESIZE: + pointer = BROWSER_POINTER_RIGHT; + break; + case CSS_CURSOR_W_RESIZE: + pointer = BROWSER_POINTER_LEFT; + break; + case CSS_CURSOR_N_RESIZE: + pointer = BROWSER_POINTER_UP; + break; + case CSS_CURSOR_S_RESIZE: + pointer = BROWSER_POINTER_DOWN; + break; + case CSS_CURSOR_NE_RESIZE: + pointer = BROWSER_POINTER_RU; + break; + case CSS_CURSOR_SW_RESIZE: + pointer = BROWSER_POINTER_LD; + break; + case CSS_CURSOR_SE_RESIZE: + pointer = BROWSER_POINTER_RD; + break; + case CSS_CURSOR_NW_RESIZE: + pointer = BROWSER_POINTER_LU; + break; + case CSS_CURSOR_TEXT: + pointer = BROWSER_POINTER_CARET; + break; + case CSS_CURSOR_WAIT: + pointer = BROWSER_POINTER_WAIT; + break; + case CSS_CURSOR_PROGRESS: + pointer = BROWSER_POINTER_PROGRESS; + break; + case CSS_CURSOR_HELP: + pointer = BROWSER_POINTER_HELP; + break; + default: + pointer = BROWSER_POINTER_DEFAULT; + break; + } + + return pointer; +} + + +/** + * Start drag scrolling the contents of a box + * + * \param box the box to be scrolled + * \param x x ordinate of initial mouse position + * \param y y ordinate + */ + +static void html_box_drag_start(struct box *box, int x, int y) +{ + int box_x, box_y; + int scroll_mouse_x, scroll_mouse_y; + + box_coords(box, &box_x, &box_y); + + if (box->scroll_x != NULL) { + scroll_mouse_x = x - box_x ; + scroll_mouse_y = y - (box_y + box->padding[TOP] + + box->height + box->padding[BOTTOM] - + SCROLLBAR_WIDTH); + scrollbar_start_content_drag(box->scroll_x, + scroll_mouse_x, scroll_mouse_y); + } else if (box->scroll_y != NULL) { + scroll_mouse_x = x - (box_x + box->padding[LEFT] + + box->width + box->padding[RIGHT] - + SCROLLBAR_WIDTH); + scroll_mouse_y = y - box_y; + + scrollbar_start_content_drag(box->scroll_y, + scroll_mouse_x, scroll_mouse_y); + } +} + + +/** + * End overflow scroll scrollbar drags + * + * \param html html content + * \param mouse state of mouse buttons and modifier keys + * \param x coordinate of mouse + * \param y coordinate of mouse + * \param dir Direction of drag + */ +static size_t html_selection_drag_end(struct html_content *html, + browser_mouse_state mouse, int x, int y, int dir) +{ + int pixel_offset; + struct box *box; + int dx, dy; + size_t idx = 0; + + box = box_pick_text_box(html, x, y, dir, &dx, &dy); + if (box) { + plot_font_style_t fstyle; + + font_plot_style_from_css(&html->len_ctx, box->style, &fstyle); + + guit->layout->position(&fstyle, box->text, box->length, + dx, &idx, &pixel_offset); + + idx += box->byte_offset; + } + + return idx; +} + + +/** + * Handle mouse tracking (including drags) in an HTML content window. + * + * \param c content of type html + * \param bw browser window + * \param mouse state of mouse buttons and modifier keys + * \param x coordinate of mouse + * \param y coordinate of mouse + */ + +void html_mouse_track(struct content *c, struct browser_window *bw, + browser_mouse_state mouse, int x, int y) +{ + html_mouse_action(c, bw, mouse, x, y); +} + +/** + * Helper for file gadgets to store their filename. + * + * Stores the filename unencoded on the dom node associated with the + * gadget. + * + * \todo Get rid of this crap eventually + * + * \param operation DOM operation + * \param key DOM node key being considerd + * \param _data The data assocated with the key + * \param src The source DOM node. + * \param dst The destination DOM node. + */ +static void +html__image_coords_dom_user_data_handler(dom_node_operation operation, + dom_string *key, + void *_data, + struct dom_node *src, + struct dom_node *dst) +{ + struct image_input_coords *oldcoords, *coords = _data, *newcoords; + + if (!dom_string_isequal(corestring_dom___ns_key_image_coords_node_data, + key) || coords == NULL) { + return; + } + + switch (operation) { + case DOM_NODE_CLONED: + newcoords = calloc(1, sizeof(*newcoords)); + if (newcoords != NULL) { + *newcoords = *coords; + if (dom_node_set_user_data(dst, + corestring_dom___ns_key_image_coords_node_data, + newcoords, + html__image_coords_dom_user_data_handler, + &oldcoords) == DOM_NO_ERR) { + free(oldcoords); + } + } + break; + + case DOM_NODE_DELETED: + free(coords); + break; + + case DOM_NODE_RENAMED: + case DOM_NODE_IMPORTED: + case DOM_NODE_ADOPTED: + break; + + default: + NSLOG(netsurf, INFO, "User data operation not handled."); + assert(0); + } +} + +/** + * Handle mouse clicks and movements in an HTML content window. + * + * \param c content of type html + * \param bw browser window + * \param mouse state of mouse buttons and modifier keys + * \param x coordinate of mouse + * \param y coordinate of mouse + * + * This function handles both hovering and clicking. It is important that the + * code path is identical (except that hovering doesn't carry out the action), + * so that the status bar reflects exactly what will happen. Having separate + * code paths opens the possibility that an attacker will make the status bar + * show some harmless action where clicking will be harmful. + */ + +void html_mouse_action(struct content *c, struct browser_window *bw, + browser_mouse_state mouse, int x, int y) +{ + html_content *html = (html_content *) c; + enum { ACTION_NONE, ACTION_SUBMIT, ACTION_GO } action = ACTION_NONE; + const char *title = 0; + nsurl *url = 0; + char *url_s = NULL; + size_t url_l = 0; + const char *target = 0; + char status_buffer[200]; + const char *status = 0; + browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; + bool imagemap = false; + int box_x = 0, box_y = 0; + int gadget_box_x = 0, gadget_box_y = 0; + int html_object_pos_x = 0, html_object_pos_y = 0; + int text_box_x = 0; + struct box *url_box = 0; + struct box *gadget_box = 0; + struct box *text_box = 0; + struct box *box; + struct form_control *gadget = 0; + hlcache_handle *object = NULL; + struct box *html_object_box = NULL; + struct browser_window *iframe = NULL; + struct box *drag_candidate = NULL; + struct scrollbar *scrollbar = NULL; + plot_font_style_t fstyle; + int scroll_mouse_x = 0, scroll_mouse_y = 0; + int padding_left, padding_right, padding_top, padding_bottom; + browser_drag_type drag_type = browser_window_get_drag_type(bw); + union content_msg_data msg_data; + struct dom_node *node = NULL; + union html_drag_owner drag_owner; + union html_selection_owner sel_owner; + bool click = mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2 | + BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2 | + BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2); + + if (drag_type != DRAGGING_NONE && !mouse && + html->visible_select_menu != NULL) { + /* drag end: select menu */ + form_select_mouse_drag_end(html->visible_select_menu, + mouse, x, y); + } + + if (html->visible_select_menu != NULL) { + box = html->visible_select_menu->box; + box_coords(box, &box_x, &box_y); + + box_x -= box->border[LEFT].width; + box_y += box->height + box->border[BOTTOM].width + + box->padding[BOTTOM] + box->padding[TOP]; + status = form_select_mouse_action(html->visible_select_menu, + mouse, x - box_x, y - box_y); + if (status != NULL) { + msg_data.explicit_status_text = status; + content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); + } else { + int width, height; + form_select_get_dimensions(html->visible_select_menu, + &width, &height); + html->visible_select_menu = NULL; + browser_window_redraw_rect(bw, box_x, box_y, + width, height); + } + return; + } + + if (html->drag_type == HTML_DRAG_SELECTION) { + /* Selection drag */ + struct box *box; + int dir = -1; + int dx, dy; + + if (!mouse) { + /* End of selection drag */ + int dir = -1; + size_t idx; + + if (selection_dragging_start(&html->sel)) + dir = 1; + + idx = html_selection_drag_end(html, mouse, x, y, dir); + + if (idx != 0) + selection_track(&html->sel, mouse, idx); + + drag_owner.no_owner = true; + html_set_drag_type(html, HTML_DRAG_NONE, + drag_owner, NULL); + return; + } + + if (selection_dragging_start(&html->sel)) + dir = 1; + + box = box_pick_text_box(html, x, y, dir, &dx, &dy); + + if (box != NULL) { + int pixel_offset; + size_t idx; + plot_font_style_t fstyle; + + font_plot_style_from_css(&html->len_ctx, + box->style, &fstyle); + + guit->layout->position(&fstyle, + box->text, box->length, + dx, &idx, &pixel_offset); + + selection_track(&html->sel, mouse, + box->byte_offset + idx); + } + return; + } + + if (html->drag_type == HTML_DRAG_SCROLLBAR) { + struct scrollbar *scr = html->drag_owner.scrollbar; + struct html_scrollbar_data *data = scrollbar_get_data(scr); + + if (!mouse) { + /* drag end: scrollbar */ + html_overflow_scroll_drag_end(scr, mouse, x, y); + } + + box = data->box; + box_coords(box, &box_x, &box_y); + if (scrollbar_is_horizontal(scr)) { + scroll_mouse_x = x - box_x ; + scroll_mouse_y = y - (box_y + box->padding[TOP] + + box->height + box->padding[BOTTOM] - + SCROLLBAR_WIDTH); + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action(scr, mouse, + scroll_mouse_x, + scroll_mouse_y)); + } else { + scroll_mouse_x = x - (box_x + box->padding[LEFT] + + box->width + box->padding[RIGHT] - + SCROLLBAR_WIDTH); + scroll_mouse_y = y - box_y; + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action(scr, mouse, + scroll_mouse_x, + scroll_mouse_y)); + } + + msg_data.explicit_status_text = status; + content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); + return; + } + + if (html->drag_type == HTML_DRAG_TEXTAREA_SELECTION || + html->drag_type == HTML_DRAG_TEXTAREA_SCROLLBAR) { + box = html->drag_owner.textarea; + assert(box->gadget != NULL); + assert(box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_TEXTBOX); + + box_coords(box, &box_x, &box_y); + textarea_mouse_action(box->gadget->data.text.ta, mouse, + x - box_x, y - box_y); + + /* TODO: Set appropriate statusbar message */ + return; + } + + if (html->drag_type == HTML_DRAG_CONTENT_SELECTION || + html->drag_type == HTML_DRAG_CONTENT_SCROLL) { + box = html->drag_owner.content; + assert(box->object != NULL); + + box_coords(box, &box_x, &box_y); + content_mouse_track(box->object, bw, mouse, + x - box_x, y - box_y); + return; + } + + if (html->drag_type == HTML_DRAG_CONTENT_SELECTION) { + box = html->drag_owner.content; + assert(box->object != NULL); + + box_coords(box, &box_x, &box_y); + content_mouse_track(box->object, bw, mouse, + x - box_x, y - box_y); + return; + } + + /* Content related drags handled by now */ + assert(html->drag_type == HTML_DRAG_NONE); + + /* search the box tree for a link, imagemap, form control, or + * box with scrollbars + */ + + box = html->layout; + + /* Consider the margins of the html page now */ + box_x = box->margin[LEFT]; + box_y = box->margin[TOP]; + + /* descend through visible boxes setting more specific values for: + * box - deepest box at point + * html_object_box - html object + * html_object_pos_x - html object + * html_object_pos_y - html object + * object - non html object + * iframe - iframe + * url - href or imagemap + * target - href or imagemap or gadget + * url_box - href or imagemap + * imagemap - imagemap + * gadget - gadget + * gadget_box - gadget + * gadget_box_x - gadget + * gadget_box_y - gadget + * title - title + * pointer + * + * drag_candidate - first box with scroll + * padding_left - box with scroll + * padding_right + * padding_top + * padding_bottom + * scrollbar - inside padding box stops decent + * scroll_mouse_x - inside padding box stops decent + * scroll_mouse_y - inside padding box stops decent + * + * text_box - text box + * text_box_x - text_box + */ + do { + if ((box->style != NULL) && + (css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN)) { + continue; + } + + if (box->node != NULL) { + node = box->node; + } + + if (box->object) { + if (content_get_type(box->object) == CONTENT_HTML) { + html_object_box = box; + html_object_pos_x = box_x; + html_object_pos_y = box_y; + } else { + object = box->object; + } + } + + if (box->iframe) { + iframe = box->iframe; + } + + if (box->href) { + url = box->href; + target = box->target; + url_box = box; + } + + if (box->usemap) { + url = imagemap_get(html, box->usemap, + box_x, box_y, x, y, &target); + if (url) { + imagemap = true; + url_box = box; + } + } + + if (box->gadget) { + gadget = box->gadget; + gadget_box = box; + gadget_box_x = box_x; + gadget_box_y = box_y; + if (gadget->form) + target = gadget->form->target; + } + + if (box->title) { + title = box->title; + } + + pointer = get_pointer_shape(box, false); + + if ((box->scroll_x != NULL) || + (box->scroll_y != NULL)) { + + if (drag_candidate == NULL) { + drag_candidate = box; + } + + padding_left = box_x + + scrollbar_get_offset(box->scroll_x); + padding_right = padding_left + box->padding[LEFT] + + box->width + box->padding[RIGHT]; + padding_top = box_y + + scrollbar_get_offset(box->scroll_y); + padding_bottom = padding_top + box->padding[TOP] + + box->height + box->padding[BOTTOM]; + + if ((x > padding_left) && + (x < padding_right) && + (y > padding_top) && + (y < padding_bottom)) { + /* mouse inside padding box */ + + if ((box->scroll_y != NULL) && + (x > (padding_right - + SCROLLBAR_WIDTH))) { + /* mouse above vertical box scroll */ + + scrollbar = box->scroll_y; + scroll_mouse_x = x - (padding_right - + SCROLLBAR_WIDTH); + scroll_mouse_y = y - padding_top; + break; + + } else if ((box->scroll_x != NULL) && + (y > (padding_bottom - + SCROLLBAR_WIDTH))) { + /* mouse above horizontal box scroll */ + + scrollbar = box->scroll_x; + scroll_mouse_x = x - padding_left; + scroll_mouse_y = y - (padding_bottom - + SCROLLBAR_WIDTH); + break; + } + } + } + + if (box->text && !box->object) { + text_box = box; + text_box_x = box_x; + } + } while ((box = box_at_point(&html->len_ctx, box, x, y, + &box_x, &box_y)) != NULL); + + /* use of box_x, box_y, or content below this point is probably a + * mistake; they will refer to the last box returned by box_at_point */ + assert(node != NULL); + + if (scrollbar) { + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action(scrollbar, mouse, + scroll_mouse_x, + scroll_mouse_y)); + pointer = BROWSER_POINTER_DEFAULT; + } else if (gadget) { + textarea_mouse_status ta_status; + + switch (gadget->type) { + case GADGET_SELECT: + status = messages_get("FormSelect"); + pointer = BROWSER_POINTER_MENU; + if (mouse & BROWSER_MOUSE_CLICK_1 && + nsoption_bool(core_select_menu)) { + html->visible_select_menu = gadget; + form_open_select_menu(c, gadget, + form_select_menu_callback, + c); + pointer = BROWSER_POINTER_DEFAULT; + } else if (mouse & BROWSER_MOUSE_CLICK_1) { + msg_data.select_menu.gadget = gadget; + content_broadcast(c, CONTENT_MSG_SELECTMENU, + &msg_data); + } + break; + case GADGET_CHECKBOX: + status = messages_get("FormCheckbox"); + if (mouse & BROWSER_MOUSE_CLICK_1) { + gadget->selected = !gadget->selected; + dom_html_input_element_set_checked( + (dom_html_input_element *)(gadget->node), + gadget->selected); + html__redraw_a_box(html, gadget_box); + } + break; + case GADGET_RADIO: + status = messages_get("FormRadio"); + if (mouse & BROWSER_MOUSE_CLICK_1) + form_radio_set(gadget); + break; + case GADGET_IMAGE: + /* This falls through to SUBMIT */ + if (mouse & BROWSER_MOUSE_CLICK_1) { + struct image_input_coords *coords, *oldcoords; + /** \todo Find a way to not ignore errors */ + coords = calloc(1, sizeof(*coords)); + if (coords == NULL) { + return; + } + coords->x = x - gadget_box_x; + coords->y = y - gadget_box_y; + if (dom_node_set_user_data( + gadget->node, + corestring_dom___ns_key_image_coords_node_data, + coords, html__image_coords_dom_user_data_handler, + &oldcoords) != DOM_NO_ERR) + return; + free(oldcoords); + } + /* Fall through */ + case GADGET_SUBMIT: + if (gadget->form) { + snprintf(status_buffer, sizeof status_buffer, + messages_get("FormSubmit"), + gadget->form->action); + status = status_buffer; + pointer = get_pointer_shape(gadget_box, false); + if (mouse & (BROWSER_MOUSE_CLICK_1 | + BROWSER_MOUSE_CLICK_2)) + action = ACTION_SUBMIT; + } else { + status = messages_get("FormBadSubmit"); + } + break; + case GADGET_TEXTBOX: + case GADGET_PASSWORD: + case GADGET_TEXTAREA: + if (gadget->type == GADGET_TEXTAREA) + status = messages_get("FormTextarea"); + else + status = messages_get("FormTextbox"); + + if (click && (html->selection_type != + HTML_SELECTION_TEXTAREA || + html->selection_owner.textarea != + gadget_box)) { + sel_owner.none = true; + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); + } + + ta_status = textarea_mouse_action(gadget->data.text.ta, + mouse, x - gadget_box_x, + y - gadget_box_y); + + if (ta_status & TEXTAREA_MOUSE_EDITOR) { + pointer = get_pointer_shape(gadget_box, false); + } else { + pointer = BROWSER_POINTER_DEFAULT; + status = scrollbar_mouse_status_to_message( + ta_status >> 3); + } + break; + case GADGET_HIDDEN: + /* not possible: no box generated */ + break; + case GADGET_RESET: + status = messages_get("FormReset"); + break; + case GADGET_FILE: + status = messages_get("FormFile"); + if (mouse & BROWSER_MOUSE_CLICK_1) { + msg_data.gadget_click.gadget = gadget; + content_broadcast(c, CONTENT_MSG_GADGETCLICK, + &msg_data); + } + break; + case GADGET_BUTTON: + /* This gadget cannot be activated */ + status = messages_get("FormButton"); + break; + } + + } else if (object && (mouse & BROWSER_MOUSE_MOD_2)) { + + if (mouse & BROWSER_MOUSE_DRAG_2) { + msg_data.dragsave.type = CONTENT_SAVE_NATIVE; + msg_data.dragsave.content = object; + content_broadcast(c, CONTENT_MSG_DRAGSAVE, &msg_data); + + } else if (mouse & BROWSER_MOUSE_DRAG_1) { + msg_data.dragsave.type = CONTENT_SAVE_ORIG; + msg_data.dragsave.content = object; + content_broadcast(c, CONTENT_MSG_DRAGSAVE, &msg_data); + } + + /* \todo should have a drag-saving object msg */ + + } else if (iframe) { + int pos_x, pos_y; + float scale = browser_window_get_scale(bw); + + browser_window_get_position(iframe, false, &pos_x, &pos_y); + + pos_x /= scale; + pos_y /= scale; + + if (mouse & BROWSER_MOUSE_CLICK_1 || + mouse & BROWSER_MOUSE_CLICK_2) { + browser_window_mouse_click(iframe, mouse, + x - pos_x, y - pos_y); + } else { + browser_window_mouse_track(iframe, mouse, + x - pos_x, y - pos_y); + } + } else if (html_object_box) { + + if (click && (html->selection_type != HTML_SELECTION_CONTENT || + html->selection_owner.content != + html_object_box)) { + sel_owner.none = true; + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); + } + if (mouse & BROWSER_MOUSE_CLICK_1 || + mouse & BROWSER_MOUSE_CLICK_2) { + content_mouse_action(html_object_box->object, + bw, mouse, + x - html_object_pos_x, + y - html_object_pos_y); + } else { + content_mouse_track(html_object_box->object, + bw, mouse, + x - html_object_pos_x, + y - html_object_pos_y); + } + } else if (url) { + if (nsoption_bool(display_decoded_idn) == true) { + if (nsurl_get_utf8(url, &url_s, &url_l) != NSERROR_OK) { + /* Unable to obtain a decoded IDN. This is not a fatal error. + * Ensure the string pointer is NULL so we use the encoded version. */ + url_s = NULL; + } + } + + if (title) { + snprintf(status_buffer, sizeof status_buffer, "%s: %s", + url_s ? url_s : nsurl_access(url), title); + } else { + snprintf(status_buffer, sizeof status_buffer, "%s", + url_s ? url_s : nsurl_access(url)); + } + + status = status_buffer; + + if (url_s != NULL) + free(url_s); + + pointer = get_pointer_shape(url_box, imagemap); + + if (mouse & BROWSER_MOUSE_CLICK_1 && + mouse & BROWSER_MOUSE_MOD_1) { + /* force download of link */ + browser_window_navigate(bw, + url, + content_get_url(c), + BW_NAVIGATE_DOWNLOAD, + NULL, + NULL, + NULL); + + } else if (mouse & BROWSER_MOUSE_CLICK_2 && + mouse & BROWSER_MOUSE_MOD_1) { + msg_data.savelink.url = url; + msg_data.savelink.title = title; + content_broadcast(c, CONTENT_MSG_SAVELINK, &msg_data); + + } else if (mouse & (BROWSER_MOUSE_CLICK_1 | + BROWSER_MOUSE_CLICK_2)) + action = ACTION_GO; + } else { + bool done = false; + + /* frame resizing */ + if (browser_window_frame_resize_start(bw, mouse, x, y, + &pointer)) { + if (mouse & (BROWSER_MOUSE_DRAG_1 | + BROWSER_MOUSE_DRAG_2)) { + status = messages_get("FrameDrag"); + } + done = true; + } + + /* if clicking in the main page, remove the selection from any + * text areas */ + if (!done) { + + if (click && html->focus_type != HTML_FOCUS_SELF) { + union html_focus_owner fo; + fo.self = true; + html_set_focus(html, HTML_FOCUS_SELF, fo, + true, 0, 0, 0, NULL); + } + if (click && html->selection_type != + HTML_SELECTION_SELF) { + sel_owner.none = true; + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); + } + + if (text_box) { + int pixel_offset; + size_t idx; + + font_plot_style_from_css(&html->len_ctx, + text_box->style, &fstyle); + + guit->layout->position(&fstyle, + text_box->text, + text_box->length, + x - text_box_x, + &idx, + &pixel_offset); + + if (selection_click(&html->sel, mouse, + text_box->byte_offset + idx)) { + /* key presses must be directed at the + * main browser window, paste text + * operations ignored */ + html_drag_type drag_type; + union html_drag_owner drag_owner; + + if (selection_dragging(&html->sel)) { + drag_type = HTML_DRAG_SELECTION; + drag_owner.no_owner = true; + html_set_drag_type(html, + drag_type, + drag_owner, + NULL); + status = messages_get( + "Selecting"); + } + + done = true; + } + + } else if (mouse & BROWSER_MOUSE_PRESS_1) { + sel_owner.none = true; + selection_clear(&html->sel, true); + } + + if (selection_defined(&html->sel)) { + sel_owner.none = false; + html_set_selection(html, HTML_SELECTION_SELF, + sel_owner, true); + } else if (click && html->selection_type != + HTML_SELECTION_NONE) { + sel_owner.none = true; + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); + } + } + + if (!done) { + if (title) + status = title; + + if (mouse & BROWSER_MOUSE_DRAG_1) { + if (mouse & BROWSER_MOUSE_MOD_2) { + msg_data.dragsave.type = + CONTENT_SAVE_COMPLETE; + msg_data.dragsave.content = NULL; + content_broadcast(c, + CONTENT_MSG_DRAGSAVE, + &msg_data); + } else { + if (drag_candidate == NULL) { + browser_window_page_drag_start( + bw, x, y); + } else { + html_box_drag_start( + drag_candidate, + x, y); + } + pointer = BROWSER_POINTER_MOVE; + } + } + else if (mouse & BROWSER_MOUSE_DRAG_2) { + if (mouse & BROWSER_MOUSE_MOD_2) { + msg_data.dragsave.type = + CONTENT_SAVE_SOURCE; + msg_data.dragsave.content = NULL; + content_broadcast(c, + CONTENT_MSG_DRAGSAVE, + &msg_data); + } else { + if (drag_candidate == NULL) { + browser_window_page_drag_start( + bw, x, y); + } else { + html_box_drag_start( + drag_candidate, + x, y); + } + pointer = BROWSER_POINTER_MOVE; + } + } + } + if (mouse && mouse < BROWSER_MOUSE_MOD_1) { + /* ensure key presses still act on the browser window */ + union html_focus_owner fo; + fo.self = true; + html_set_focus(html, HTML_FOCUS_SELF, fo, + true, 0, 0, 0, NULL); + } + } + + if (!iframe && !html_object_box) { + msg_data.explicit_status_text = status; + content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); + + msg_data.pointer = pointer; + content_broadcast(c, CONTENT_MSG_POINTER, &msg_data); + } + + /* fire dom click event */ + if (mouse & BROWSER_MOUSE_CLICK_1) { + fire_dom_event(corestring_dom_click, node, true, true); + } + + /* deferred actions that can cause this browser_window to be destroyed + * and must therefore be done after set_status/pointer + */ + switch (action) { + case ACTION_SUBMIT: + form_submit(content_get_url(c), + browser_window_find_target(bw, target, mouse), + gadget->form, gadget); + break; + case ACTION_GO: + browser_window_navigate(browser_window_find_target(bw, target, mouse), + url, + content_get_url(c), + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + break; + case ACTION_NONE: + break; + } +} + + +/** + * Handle keypresses. + * + * \param c content of type HTML + * \param key The UCS4 character codepoint + * \return true if key handled, false otherwise + */ + +bool html_keypress(struct content *c, uint32_t key) +{ + html_content *html = (html_content *) c; + struct selection *sel = &html->sel; + struct box *box; + + switch (html->focus_type) { + case HTML_FOCUS_CONTENT: + box = html->focus_owner.content; + return content_keypress(box->object, key); + + case HTML_FOCUS_TEXTAREA: + box = html->focus_owner.textarea; + return box_textarea_keypress(html, box, key); + + default: + /* Deal with it below */ + break; + } + + switch (key) { + case NS_KEY_COPY_SELECTION: + selection_copy_to_clipboard(sel); + return true; + + case NS_KEY_CLEAR_SELECTION: + selection_clear(sel, true); + return true; + + case NS_KEY_SELECT_ALL: + selection_select_all(sel); + return true; + + case NS_KEY_ESCAPE: + if (selection_defined(sel)) { + selection_clear(sel, true); + return true; + } + + /* if there's no selection, leave Escape for the caller */ + return false; + } + + return false; +} + + +/** + * Handle search. + * + * \param c content of type HTML + * \param context front end private data + * \param flags search flags + * \param string search string + */ +void html_search(struct content *c, void *context, + search_flags_t flags, const char *string) +{ + html_content *html = (html_content *)c; + + assert(c != NULL); + + if (string != NULL && html->search_string != NULL && + strcmp(string, html->search_string) == 0 && + html->search != NULL) { + /* Continue prev. search */ + search_step(html->search, flags, string); + + } else if (string != NULL) { + /* New search */ + free(html->search_string); + html->search_string = strdup(string); + if (html->search_string == NULL) + return; + + if (html->search != NULL) { + search_destroy_context(html->search); + html->search = NULL; + } + + html->search = search_create_context(c, CONTENT_HTML, context); + + if (html->search == NULL) + return; + + search_step(html->search, flags, string); + + } else { + /* Clear search */ + html_search_clear(c); + + free(html->search_string); + html->search_string = NULL; + } +} + + +/** + * Terminate a search. + * + * \param c content of type HTML + */ +void html_search_clear(struct content *c) +{ + html_content *html = (html_content *)c; + + assert(c != NULL); + + free(html->search_string); + html->search_string = NULL; + + if (html->search != NULL) { + search_destroy_context(html->search); + } + html->search = NULL; +} + + +/** + * Callback for in-page scrollbars. + */ +void html_overflow_scroll_callback(void *client_data, + struct scrollbar_msg_data *scrollbar_data) +{ + struct html_scrollbar_data *data = client_data; + html_content *html = (html_content *)data->c; + struct box *box = data->box; + union content_msg_data msg_data; + html_drag_type drag_type; + union html_drag_owner drag_owner; + + switch(scrollbar_data->msg) { + case SCROLLBAR_MSG_MOVED: + + if (html->reflowing == true) { + /* Can't redraw during layout, and it will + * be redrawn after layout anyway. */ + break; + } + + html__redraw_a_box(html, box); + break; + case SCROLLBAR_MSG_SCROLL_START: + { + struct rect rect = { + .x0 = scrollbar_data->x0, + .y0 = scrollbar_data->y0, + .x1 = scrollbar_data->x1, + .y1 = scrollbar_data->y1 + }; + drag_type = HTML_DRAG_SCROLLBAR; + drag_owner.scrollbar = scrollbar_data->scrollbar; + html_set_drag_type(html, drag_type, drag_owner, &rect); + } + break; + case SCROLLBAR_MSG_SCROLL_FINISHED: + drag_type = HTML_DRAG_NONE; + drag_owner.no_owner = true; + html_set_drag_type(html, drag_type, drag_owner, NULL); + + msg_data.pointer = BROWSER_POINTER_AUTO; + content_broadcast(data->c, CONTENT_MSG_POINTER, &msg_data); + break; + } +} + + +/** + * End overflow scroll scrollbar drags + * + * \param scrollbar scrollbar widget + * \param mouse state of mouse buttons and modifier keys + * \param x coordinate of mouse + * \param y coordinate of mouse + */ +void html_overflow_scroll_drag_end(struct scrollbar *scrollbar, + browser_mouse_state mouse, int x, int y) +{ + int scroll_mouse_x, scroll_mouse_y, box_x, box_y; + struct html_scrollbar_data *data = scrollbar_get_data(scrollbar); + struct box *box; + + box = data->box; + box_coords(box, &box_x, &box_y); + + if (scrollbar_is_horizontal(scrollbar)) { + scroll_mouse_x = x - box_x; + scroll_mouse_y = y - (box_y + box->padding[TOP] + + box->height + box->padding[BOTTOM] - + SCROLLBAR_WIDTH); + scrollbar_mouse_drag_end(scrollbar, mouse, + scroll_mouse_x, scroll_mouse_y); + } else { + scroll_mouse_x = x - (box_x + box->padding[LEFT] + + box->width + box->padding[RIGHT] - + SCROLLBAR_WIDTH); + scroll_mouse_y = y - box_y; + scrollbar_mouse_drag_end(scrollbar, mouse, + scroll_mouse_x, scroll_mouse_y); + } +} + +/* Documented in html_internal.h */ +void html_set_drag_type(html_content *html, html_drag_type drag_type, + union html_drag_owner drag_owner, const struct rect *rect) +{ + union content_msg_data msg_data; + + assert(html != NULL); + + html->drag_type = drag_type; + html->drag_owner = drag_owner; + + switch (drag_type) { + case HTML_DRAG_NONE: + assert(drag_owner.no_owner == true); + msg_data.drag.type = CONTENT_DRAG_NONE; + break; + + case HTML_DRAG_SCROLLBAR: + case HTML_DRAG_TEXTAREA_SCROLLBAR: + case HTML_DRAG_CONTENT_SCROLL: + msg_data.drag.type = CONTENT_DRAG_SCROLL; + break; + + case HTML_DRAG_SELECTION: + assert(drag_owner.no_owner == true); + /* Fall through */ + case HTML_DRAG_TEXTAREA_SELECTION: + case HTML_DRAG_CONTENT_SELECTION: + msg_data.drag.type = CONTENT_DRAG_SELECTION; + break; + } + msg_data.drag.rect = rect; + + /* Inform of the content's drag status change */ + content_broadcast((struct content *)html, CONTENT_MSG_DRAG, &msg_data); +} + +/* Documented in html_internal.h */ +void html_set_focus(html_content *html, html_focus_type focus_type, + union html_focus_owner focus_owner, bool hide_caret, + int x, int y, int height, const struct rect *clip) +{ + union content_msg_data msg_data; + int x_off = 0; + int y_off = 0; + struct rect cr; + bool textarea_lost_focus = html->focus_type == HTML_FOCUS_TEXTAREA && + focus_type != HTML_FOCUS_TEXTAREA; + + assert(html != NULL); + + switch (focus_type) { + case HTML_FOCUS_SELF: + assert(focus_owner.self == true); + if (html->focus_type == HTML_FOCUS_SELF) + /* Don't need to tell anyone anything */ + return; + break; + + case HTML_FOCUS_CONTENT: + box_coords(focus_owner.content, &x_off, &y_off); + break; + + case HTML_FOCUS_TEXTAREA: + box_coords(focus_owner.textarea, &x_off, &y_off); + break; + } + + html->focus_type = focus_type; + html->focus_owner = focus_owner; + + if (textarea_lost_focus) { + msg_data.caret.type = CONTENT_CARET_REMOVE; + } else if (focus_type != HTML_FOCUS_SELF && hide_caret) { + msg_data.caret.type = CONTENT_CARET_HIDE; + } else { + if (clip != NULL) { + cr = *clip; + cr.x0 += x_off; + cr.y0 += y_off; + cr.x1 += x_off; + cr.y1 += y_off; + } + + msg_data.caret.type = CONTENT_CARET_SET_POS; + msg_data.caret.pos.x = x + x_off; + msg_data.caret.pos.y = y + y_off; + msg_data.caret.pos.height = height; + msg_data.caret.pos.clip = (clip == NULL) ? NULL : &cr; + } + + /* Inform of the content's drag status change */ + content_broadcast((struct content *)html, CONTENT_MSG_CARET, &msg_data); +} + +/* Documented in html_internal.h */ +void html_set_selection(html_content *html, html_selection_type selection_type, + union html_selection_owner selection_owner, bool read_only) +{ + union content_msg_data msg_data; + struct box *box; + bool changed = false; + bool same_type = html->selection_type == selection_type; + + assert(html != NULL); + + if ((selection_type == HTML_SELECTION_NONE && + html->selection_type != HTML_SELECTION_NONE) || + (selection_type != HTML_SELECTION_NONE && + html->selection_type == HTML_SELECTION_NONE)) + /* Existance of selection has changed, and we'll need to + * inform our owner */ + changed = true; + + /* Clear any existing selection */ + if (html->selection_type != HTML_SELECTION_NONE) { + switch (html->selection_type) { + case HTML_SELECTION_SELF: + if (same_type) + break; + selection_clear(&html->sel, true); + break; + case HTML_SELECTION_TEXTAREA: + if (same_type && html->selection_owner.textarea == + selection_owner.textarea) + break; + box = html->selection_owner.textarea; + textarea_clear_selection(box->gadget->data.text.ta); + break; + case HTML_SELECTION_CONTENT: + if (same_type && html->selection_owner.content == + selection_owner.content) + break; + box = html->selection_owner.content; + content_clear_selection(box->object); + break; + default: + break; + } + } + + html->selection_type = selection_type; + html->selection_owner = selection_owner; + + if (!changed) + /* Don't need to report lack of change to owner */ + return; + + /* Prepare msg */ + switch (selection_type) { + case HTML_SELECTION_NONE: + assert(selection_owner.none == true); + msg_data.selection.selection = false; + break; + case HTML_SELECTION_SELF: + assert(selection_owner.none == false); + /* fall through */ + case HTML_SELECTION_TEXTAREA: + case HTML_SELECTION_CONTENT: + msg_data.selection.selection = true; + break; + default: + break; + } + msg_data.selection.read_only = read_only; + + /* Inform of the content's selection status change */ + content_broadcast((struct content *)html, CONTENT_MSG_SELECTION, + &msg_data); +} diff --git a/content/handlers/html/html_internal.h b/content/handlers/html/html_internal.h new file mode 100644 index 000000000..b9eca663e --- /dev/null +++ b/content/handlers/html/html_internal.h @@ -0,0 +1,409 @@ +/* + * Copyright 2004 James Bursa + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Private data for text/html content. + */ + +#ifndef NETSURF_HTML_HTML_INTERNAL_H +#define NETSURF_HTML_HTML_INTERNAL_H + +#include + +#include "content/handlers/css/utils.h" +#include "content/content_protected.h" +#include "desktop/selection.h" + +#include "html/html.h" + +struct gui_layout_table; + +typedef enum { + HTML_DRAG_NONE, /** No drag */ + HTML_DRAG_SELECTION, /** Own; Text selection */ + HTML_DRAG_SCROLLBAR, /** Not own; drag in scrollbar widget */ + HTML_DRAG_TEXTAREA_SELECTION, /** Not own; drag in textarea widget */ + HTML_DRAG_TEXTAREA_SCROLLBAR, /** Not own; drag in textarea widget */ + HTML_DRAG_CONTENT_SELECTION, /** Not own; drag in child content */ + HTML_DRAG_CONTENT_SCROLL /** Not own; drag in child content */ +} html_drag_type; + +union html_drag_owner { + bool no_owner; + struct box *content; + struct scrollbar *scrollbar; + struct box *textarea; +}; /**< For drags we don't own */ + +typedef enum { + HTML_SELECTION_NONE, /** No selection */ + HTML_SELECTION_TEXTAREA, /** Selection in one of our textareas */ + HTML_SELECTION_SELF, /** Selection in this html content */ + HTML_SELECTION_CONTENT /** Selection in child content */ +} html_selection_type; +union html_selection_owner { + bool none; + struct box *textarea; + struct box *content; +}; /**< For getting at selections in this content or things in this content */ + +typedef enum { + HTML_FOCUS_SELF, /** Focus is our own */ + HTML_FOCUS_CONTENT, /** Focus belongs to child content */ + HTML_FOCUS_TEXTAREA /** Focus belongs to textarea */ +} html_focus_type; +union html_focus_owner { + bool self; + struct box *textarea; + struct box *content; +}; /**< For directing input */ + +/** Data specific to CONTENT_HTML. */ +typedef struct html_content { + struct content base; + + dom_hubbub_parser *parser; /**< Parser object handle */ + bool parse_completed; /**< Whether the parse has been completed */ + + /** Document tree */ + dom_document *document; + /** Quirkyness of document */ + dom_document_quirks_mode quirks; + + /** Encoding of source, NULL if unknown. */ + char *encoding; + /** Source of encoding information. */ + dom_hubbub_encoding_source encoding_source; + + /** Base URL (may be a copy of content->url). */ + nsurl *base_url; + /** Base target */ + char *base_target; + + /** CSS length conversion context for document. */ + nscss_len_ctx len_ctx; + + /** Content has been aborted in the LOADING state */ + bool aborted; + + /** Whether a meta refresh has been handled */ + bool refresh; + + /** Whether a layout (reflow) is in progress */ + bool reflowing; + + /** Whether scripts are enabled for this content */ + bool enable_scripting; + + /* Title element node */ + dom_node *title; + + /** A talloc context purely for the render box tree */ + int *bctx; + /** Box tree, or NULL. */ + struct box *layout; + /** Document background colour. */ + colour background_colour; + + /** Font callback table */ + const struct gui_layout_table *font_func; + + /** Number of entries in scripts */ + unsigned int scripts_count; + /** Scripts */ + struct html_script *scripts; + /** javascript context */ + struct jscontext *jscontext; + + /** Number of entries in stylesheet_content. */ + unsigned int stylesheet_count; + /** Stylesheets. Each may be NULL. */ + struct html_stylesheet *stylesheets; + /**< Style selection context */ + css_select_ctx *select_ctx; + /**< Universal selector */ + lwc_string *universal; + + /** Number of entries in object_list. */ + unsigned int num_objects; + /** List of objects. */ + struct content_html_object *object_list; + /** Forms, in reverse order to document. */ + struct form *forms; + /** Hash table of imagemaps. */ + struct imagemap **imagemaps; + + /** Browser window containing this document, or NULL if not open. */ + struct browser_window *bw; + + /** Frameset information */ + struct content_html_frames *frameset; + + /** Inline frame information */ + struct content_html_iframe *iframe; + + /** Content of type CONTENT_HTML containing this, or NULL if not an + * object within a page. */ + struct html_content *page; + + /** Current drag type */ + html_drag_type drag_type; + /** Widget capturing all mouse events */ + union html_drag_owner drag_owner; + + /** Current selection state */ + html_selection_type selection_type; + /** Current selection owner */ + union html_selection_owner selection_owner; + + /** Current input focus target type */ + html_focus_type focus_type; + /** Current input focus target */ + union html_focus_owner focus_owner; + + /** HTML content's own text selection object */ + struct selection sel; + + /** Open core-handled form SELECT menu, + * or NULL if none currently open. */ + struct form_control *visible_select_menu; + + /** Context for free text search, or NULL if none */ + struct search_context *search; + /** Search string or NULL */ + char *search_string; + +} html_content; + +/** Render padding and margin box outlines in html_redraw(). */ +extern bool html_redraw_debug; + +void html__redraw_a_box(html_content *html, struct box *box); + +/** + * Set our drag status, and inform whatever owns the content + * + * \param html HTML content + * \param drag_type Type of drag + * \param drag_owner What owns the drag + * \param rect Pointer movement bounds + */ +void html_set_drag_type(html_content *html, html_drag_type drag_type, + union html_drag_owner drag_owner, const struct rect *rect); + +/** + * Set our selection status, and inform whatever owns the content + * + * \param html HTML content + * \param selection_type Type of selection + * \param selection_owner What owns the selection + * \param read_only True iff selection is read only + */ +void html_set_selection(html_content *html, html_selection_type selection_type, + union html_selection_owner selection_owner, bool read_only); + +/** + * Set our input focus, and inform whatever owns the content + * + * \param html HTML content + * \param focus_type Type of input focus + * \param focus_owner What owns the focus + * \param hide_caret True iff caret to be hidden + * \param x Carret x-coord rel to owner + * \param y Carret y-coord rel to owner + * \param height Carret height + * \param clip Carret clip rect + */ +void html_set_focus(html_content *html, html_focus_type focus_type, + union html_focus_owner focus_owner, bool hide_caret, + int x, int y, int height, const struct rect *clip); + + +struct browser_window *html_get_browser_window(struct content *c); + +/** + * Complete conversion of an HTML document + * + * \param htmlc Content to convert + */ +void html_finish_conversion(html_content *htmlc); + +/** + * Test if an HTML content conversion can begin + * + * \param htmlc html content to test + * \return true iff the html content conversion can begin + */ +bool html_can_begin_conversion(html_content *htmlc); + +/** + * Begin conversion of an HTML document + * + * \param htmlc Content to convert + */ +bool html_begin_conversion(html_content *htmlc); + +/* in html/html_redraw.c */ +bool html_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx); + +/* in html/html_redraw_border.c */ +bool html_redraw_borders(struct box *box, int x_parent, int y_parent, + int p_width, int p_height, const struct rect *clip, float scale, + const struct redraw_context *ctx); + +bool html_redraw_inline_borders(struct box *box, struct rect b, + const struct rect *clip, float scale, bool first, bool last, + const struct redraw_context *ctx); + +/* in html/html_interaction.c */ +void html_mouse_track(struct content *c, struct browser_window *bw, + browser_mouse_state mouse, int x, int y); +void html_mouse_action(struct content *c, struct browser_window *bw, + browser_mouse_state mouse, int x, int y); +bool html_keypress(struct content *c, uint32_t key); +void html_overflow_scroll_callback(void *client_data, + struct scrollbar_msg_data *scrollbar_data); +void html_search(struct content *c, void *context, + search_flags_t flags, const char *string); +void html_search_clear(struct content *c); + + +/* in html/html_script.c */ +dom_hubbub_error html_process_script(void *ctx, dom_node *node); + +/** + * Attempt script execution for defer and async scripts + * + * execute scripts using algorithm found in: + * http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#the-script-element + * + * \param htmlc html content. + * \return NSERROR_OK error code. + */ +nserror html_script_exec(html_content *htmlc); + +/** + * Free all script resources and references for a html content. + * + * \param htmlc html content. + * \return NSERROR_OK or error code. + */ +nserror html_script_free(html_content *htmlc); + +/** + * Ensure the html content javascript context is invalidated. + * + * \param htmlc html content. + * \return NSERROR_OK or error code. + */ +nserror html_script_invalidate_ctx(html_content *htmlc); + +/* in html/html_forms.c */ +struct form *html_forms_get_forms(const char *docenc, dom_html_document *doc); +struct form_control *html_forms_get_control_for_node(struct form *forms, + dom_node *node); + +/* in html/html_css.c */ +nserror html_css_init(void); +void html_css_fini(void); + +/** + * Initialise core stylesheets for a content + * + * \param c content structure to update + * \return nserror + */ +nserror html_css_new_stylesheets(html_content *c); +nserror html_css_quirks_stylesheets(html_content *c); +nserror html_css_free_stylesheets(html_content *html); + +bool html_css_process_link(html_content *htmlc, dom_node *node); +bool html_css_process_style(html_content *htmlc, dom_node *node); +bool html_css_update_style(html_content *c, dom_node *style); + +nserror html_css_new_selection_context(html_content *c, + css_select_ctx **ret_select_ctx); + +/* in html/html_css_fetcher.c */ +/** + * Register the fetcher for the pseudo x-ns-css scheme. + * + * \return NSERROR_OK on successful registration or error code on failure. + */ +nserror html_css_fetcher_register(void); +nserror html_css_fetcher_add_item(dom_string *data, nsurl *base_url, + uint32_t *key); + +/* in html/html_object.c */ + +/** + * Start a fetch for an object required by a page. + * + * \param c content of type CONTENT_HTML + * \param url URL of object to fetch (copied) + * \param box box that will contain the object + * \param permitted_types bitmap of acceptable types + * \param available_width estimate of width of object + * \param available_height estimate of height of object + * \param background this is a background image + * \return true on success, false on memory exhaustion + */ +bool html_fetch_object(html_content *c, nsurl *url, struct box *box, + content_type permitted_types, + int available_width, int available_height, + bool background); + +nserror html_object_free_objects(html_content *html); +nserror html_object_close_objects(html_content *html); +nserror html_object_open_objects(html_content *html, struct browser_window *bw); +nserror html_object_abort_objects(html_content *html); + +/* Events */ +/** + * Construct an event and fire it at the DOM + * + */ +bool fire_dom_event(dom_string *type, dom_node *target, + bool bubbles, bool cancelable); + +/* Useful dom_string pointers */ +struct dom_string; + +extern struct dom_string *html_dom_string_map; +extern struct dom_string *html_dom_string_id; +extern struct dom_string *html_dom_string_name; +extern struct dom_string *html_dom_string_area; +extern struct dom_string *html_dom_string_a; +extern struct dom_string *html_dom_string_nohref; +extern struct dom_string *html_dom_string_href; +extern struct dom_string *html_dom_string_target; +extern struct dom_string *html_dom_string_shape; +extern struct dom_string *html_dom_string_default; +extern struct dom_string *html_dom_string_rect; +extern struct dom_string *html_dom_string_rectangle; +extern struct dom_string *html_dom_string_coords; +extern struct dom_string *html_dom_string_circle; +extern struct dom_string *html_dom_string_poly; +extern struct dom_string *html_dom_string_polygon; +extern struct dom_string *html_dom_string_text_javascript; +extern struct dom_string *html_dom_string_type; +extern struct dom_string *html_dom_string_src; + +#endif diff --git a/content/handlers/html/html_object.c b/content/handlers/html/html_object.c new file mode 100644 index 000000000..c8715e3fb --- /dev/null +++ b/content/handlers/html/html_object.c @@ -0,0 +1,730 @@ +/* + * Copyright 2013 Vincent Sanders + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Processing for html content object operations. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "utils/corestrings.h" +#include "utils/config.h" +#include "utils/log.h" +#include "utils/nsoption.h" +#include "netsurf/content.h" +#include "netsurf/misc.h" +#include "content/hlcache.h" +#include "css/utils.h" +#include "desktop/scrollbar.h" +#include "desktop/gui_internal.h" + +#include "html/box.h" +#include "html/html_internal.h" + +/* break reference loop */ +static void html_object_refresh(void *p); + +/** + * Retrieve objects used by HTML document + * + * \param h Content to retrieve objects from + * \param n Pointer to location to receive number of objects + * \return Pointer to list of objects + */ +struct content_html_object *html_get_objects(hlcache_handle *h, unsigned int *n) +{ + html_content *c = (html_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + assert(n != NULL); + + *n = c->num_objects; + + return c->object_list; +} + +/** + * Handle object fetching or loading failure. + * + * \param box box containing object which failed to load + * \param content document of type CONTENT_HTML + * \param background the object was the background image for the box + */ + +static void +html_object_failed(struct box *box, html_content *content, bool background) +{ + /* Nothing to do */ + return; +} + +/** + * Update a box whose content has completed rendering. + */ + +static void +html_object_done(struct box *box, + hlcache_handle *object, + bool background) +{ + struct box *b; + + if (background) { + box->background = object; + return; + } + + box->object = object; + + /* Normalise the box type, now it has been replaced. */ + switch (box->type) { + case BOX_TABLE: + box->type = BOX_BLOCK; + break; + default: + /* TODO: Any other box types need mapping? */ + break; + } + + if (!(box->flags & REPLACE_DIM)) { + /* invalidate parent min, max widths */ + for (b = box; b; b = b->parent) + b->max_width = UNKNOWN_MAX_WIDTH; + + /* delete any clones of this box */ + while (box->next && (box->next->flags & CLONE)) { + /* box_free_box(box->next); */ + box->next = box->next->next; + } + } +} + +/** + * Callback for hlcache_handle_retrieve() for objects. + */ + +static nserror +html_object_callback(hlcache_handle *object, + const hlcache_event *event, + void *pw) +{ + struct content_html_object *o = pw; + html_content *c = (html_content *) o->parent; + int x, y; + struct box *box; + + box = o->box; + if (box == NULL && + event->type != CONTENT_MSG_ERROR && + event->type != CONTENT_MSG_ERRORCODE) { + return NSERROR_OK; + } + + switch (event->type) { + case CONTENT_MSG_LOADING: + if (c->base.status != CONTENT_STATUS_LOADING && c->bw != NULL) + content_open(object, + c->bw, &c->base, + box->object_params); + break; + + case CONTENT_MSG_READY: + if (content_can_reformat(object)) { + /* TODO: avoid knowledge of box internals here */ + content_reformat(object, false, + box->max_width != UNKNOWN_MAX_WIDTH ? + box->width : 0, + box->max_width != UNKNOWN_MAX_WIDTH ? + box->height : 0); + + /* Adjust parent content for new object size */ + html_object_done(box, object, o->background); + if (c->base.status == CONTENT_STATUS_READY || + c->base.status == CONTENT_STATUS_DONE) + content__reformat(&c->base, false, + c->base.available_width, + c->base.height); + } + break; + + case CONTENT_MSG_DONE: + c->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + + html_object_done(box, object, o->background); + + if (c->base.status != CONTENT_STATUS_LOADING && + box->flags & REPLACE_DIM) { + union content_msg_data data; + + if (!box_visible(box)) + break; + + box_coords(box, &x, &y); + + data.redraw.x = x + box->padding[LEFT]; + data.redraw.y = y + box->padding[TOP]; + data.redraw.width = box->width; + data.redraw.height = box->height; + data.redraw.full_redraw = true; + + content_broadcast(&c->base, CONTENT_MSG_REDRAW, &data); + } + break; + + case CONTENT_MSG_ERRORCODE: + case CONTENT_MSG_ERROR: + hlcache_handle_release(object); + + o->content = NULL; + + if (box != NULL) { + c->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", + c->base.active); + + content_add_error(&c->base, "?", 0); + html_object_failed(box, c, o->background); + } + break; + + case CONTENT_MSG_REDRAW: + if (c->base.status != CONTENT_STATUS_LOADING) { + union content_msg_data data = event->data; + + if (!box_visible(box)) + break; + + box_coords(box, &x, &y); + + if (object == box->background) { + /* Redraw request is for background */ + css_fixed hpos = 0, vpos = 0; + css_unit hunit = CSS_UNIT_PX; + css_unit vunit = CSS_UNIT_PX; + int width = box->padding[LEFT] + box->width + + box->padding[RIGHT]; + int height = box->padding[TOP] + box->height + + box->padding[BOTTOM]; + int t, h, l, w; + + /* Need to know background-position */ + css_computed_background_position(box->style, + &hpos, &hunit, &vpos, &vunit); + + w = content_get_width(box->background); + if (hunit == CSS_UNIT_PCT) { + l = (width - w) * hpos / INTTOFIX(100); + } else { + l = FIXTOINT(nscss_len2px(&c->len_ctx, + hpos, hunit, + box->style)); + } + + h = content_get_height(box->background); + if (vunit == CSS_UNIT_PCT) { + t = (height - h) * vpos / INTTOFIX(100); + } else { + t = FIXTOINT(nscss_len2px(&c->len_ctx, + vpos, vunit, + box->style)); + } + + /* Redraw area depends on background-repeat */ + switch (css_computed_background_repeat( + box->style)) { + case CSS_BACKGROUND_REPEAT_REPEAT: + data.redraw.x = 0; + data.redraw.y = 0; + data.redraw.width = box->width; + data.redraw.height = box->height; + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_X: + data.redraw.x = 0; + data.redraw.y += t; + data.redraw.width = box->width; + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_Y: + data.redraw.x += l; + data.redraw.y = 0; + data.redraw.height = box->height; + break; + + case CSS_BACKGROUND_REPEAT_NO_REPEAT: + data.redraw.x += l; + data.redraw.y += t; + break; + + default: + break; + } + + data.redraw.object_width = box->width; + data.redraw.object_height = box->height; + + /* Add offset to box */ + data.redraw.x += x; + data.redraw.y += y; + data.redraw.object_x += x; + data.redraw.object_y += y; + + content_broadcast(&c->base, + CONTENT_MSG_REDRAW, &data); + break; + + } else { + /* Non-background case */ + if (hlcache_handle_get_content(object) == + event->data.redraw.object) { + + int w = content_get_width(object); + int h = content_get_height(object); + + if (w != 0) { + data.redraw.x = + data.redraw.x * + box->width / w; + data.redraw.width = + data.redraw.width * + box->width / w; + } + + if (h != 0) { + data.redraw.y = + data.redraw.y * + box->height / h; + data.redraw.height = + data.redraw.height * + box->height / h; + } + + data.redraw.object_width = box->width; + data.redraw.object_height = box->height; + } + + data.redraw.x += x + box->padding[LEFT]; + data.redraw.y += y + box->padding[TOP]; + data.redraw.object_x += x + box->padding[LEFT]; + data.redraw.object_y += y + box->padding[TOP]; + } + + content_broadcast(&c->base, CONTENT_MSG_REDRAW, &data); + } + break; + + case CONTENT_MSG_REFRESH: + if (content_get_type(object) == CONTENT_HTML) { + /* only for HTML objects */ + guit->misc->schedule(event->data.delay * 1000, + html_object_refresh, o); + } + + break; + + case CONTENT_MSG_LINK: + /* Don't care about favicons that aren't on top level content */ + break; + + case CONTENT_MSG_GETCTX: + *(event->data.jscontext) = NULL; + break; + + case CONTENT_MSG_SCROLL: + if (box->scroll_x != NULL) + scrollbar_set(box->scroll_x, event->data.scroll.x0, + false); + if (box->scroll_y != NULL) + scrollbar_set(box->scroll_y, event->data.scroll.y0, + false); + break; + + case CONTENT_MSG_DRAGSAVE: + { + union content_msg_data msg_data; + if (event->data.dragsave.content == NULL) + msg_data.dragsave.content = object; + else + msg_data.dragsave.content = + event->data.dragsave.content; + + content_broadcast(&c->base, CONTENT_MSG_DRAGSAVE, &msg_data); + } + break; + + case CONTENT_MSG_SAVELINK: + case CONTENT_MSG_POINTER: + case CONTENT_MSG_SELECTMENU: + case CONTENT_MSG_GADGETCLICK: + /* These messages are for browser window layer. + * we're not interested, so pass them on. */ + content_broadcast(&c->base, event->type, &event->data); + break; + + case CONTENT_MSG_CARET: + { + union html_focus_owner focus_owner; + focus_owner.content = box; + + switch (event->data.caret.type) { + case CONTENT_CARET_REMOVE: + case CONTENT_CARET_HIDE: + html_set_focus(c, HTML_FOCUS_CONTENT, focus_owner, + true, 0, 0, 0, NULL); + break; + case CONTENT_CARET_SET_POS: + html_set_focus(c, HTML_FOCUS_CONTENT, focus_owner, + false, event->data.caret.pos.x, + event->data.caret.pos.y, + event->data.caret.pos.height, + event->data.caret.pos.clip); + break; + } + } + break; + + case CONTENT_MSG_DRAG: + { + html_drag_type drag_type = HTML_DRAG_NONE; + union html_drag_owner drag_owner; + drag_owner.content = box; + + switch (event->data.drag.type) { + case CONTENT_DRAG_NONE: + drag_type = HTML_DRAG_NONE; + drag_owner.no_owner = true; + break; + case CONTENT_DRAG_SCROLL: + drag_type = HTML_DRAG_CONTENT_SCROLL; + break; + case CONTENT_DRAG_SELECTION: + drag_type = HTML_DRAG_CONTENT_SELECTION; + break; + } + html_set_drag_type(c, drag_type, drag_owner, + event->data.drag.rect); + } + break; + + case CONTENT_MSG_SELECTION: + { + html_selection_type sel_type; + union html_selection_owner sel_owner; + + if (event->data.selection.selection) { + sel_type = HTML_SELECTION_CONTENT; + sel_owner.content = box; + } else { + sel_type = HTML_SELECTION_NONE; + sel_owner.none = true; + } + html_set_selection(c, sel_type, sel_owner, + event->data.selection.read_only); + } + break; + + default: + break; + } + + if (c->base.status == CONTENT_STATUS_READY && + c->base.active == 0 && + (event->type == CONTENT_MSG_LOADING || + event->type == CONTENT_MSG_DONE || + event->type == CONTENT_MSG_ERROR || + event->type == CONTENT_MSG_ERRORCODE)) { + /* all objects have arrived */ + content__reformat(&c->base, false, c->base.available_width, + c->base.height); + content_set_done(&c->base); + } else if (nsoption_bool(incremental_reflow) && + event->type == CONTENT_MSG_DONE && + box != NULL && + !(box->flags & REPLACE_DIM) && + (c->base.status == CONTENT_STATUS_READY || + c->base.status == CONTENT_STATUS_DONE)) { + /* 1) the configuration option to reflow pages while + * objects are fetched is set + * 2) an object is newly fetched & converted, + * 3) the box's dimensions need to change due to being replaced + * 4) the object's parent HTML is ready for reformat, + */ + uint64_t ms_now; + nsu_getmonotonic_ms(&ms_now); + if (ms_now > c->base.reformat_time) { + /* The time since the previous reformat is + * more than the configured minimum time + * between reformats so reformat the page to + * display newly fetched objects + */ + content__reformat(&c->base, + false, + c->base.available_width, + c->base.height); + } + } + + return NSERROR_OK; +} + +/** + * Start a fetch for an object required by a page, replacing an existing object. + * + * \param object Object to replace + * \param url URL of object to fetch (copied) + * \return true on success, false on memory exhaustion + */ + +static bool html_replace_object(struct content_html_object *object, nsurl *url) +{ + html_content *c; + hlcache_child_context child; + html_content *page; + nserror error; + + assert(object != NULL); + assert(object->box != NULL); + + c = (html_content *) object->parent; + + child.charset = c->encoding; + child.quirks = c->base.quirks; + + if (object->content != NULL) { + /* remove existing object */ + if (content_get_status(object->content) != CONTENT_STATUS_DONE) { + c->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", + c->base.active); + } + + hlcache_handle_release(object->content); + object->content = NULL; + + object->box->object = NULL; + } + + /* initialise fetch */ + error = hlcache_handle_retrieve(url, HLCACHE_RETRIEVE_SNIFF_TYPE, + content_get_url(&c->base), NULL, + html_object_callback, object, &child, + object->permitted_types, + &object->content); + + if (error != NSERROR_OK) + return false; + + for (page = c; page != NULL; page = page->page) { + page->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + + page->base.status = CONTENT_STATUS_READY; + } + + return true; +} + +/** + * schedule callback for object refresh + */ + +static void html_object_refresh(void *p) +{ + struct content_html_object *object = p; + nsurl *refresh_url; + + assert(content_get_type(object->content) == CONTENT_HTML); + + refresh_url = content_get_refresh_url(object->content); + + /* Ignore if refresh URL has gone + * (may happen if fetch errored) */ + if (refresh_url == NULL) + return; + + content_invalidate_reuse_data(object->content); + + if (!html_replace_object(object, refresh_url)) { + /** \todo handle memory exhaustion */ + } +} + +nserror html_object_open_objects(html_content *html, struct browser_window *bw) +{ + struct content_html_object *object, *next; + + for (object = html->object_list; object != NULL; object = next) { + next = object->next; + + if (object->content == NULL || object->box == NULL) + continue; + + if (content_get_type(object->content) == CONTENT_NONE) + continue; + + content_open(object->content, + bw, + &html->base, + object->box->object_params); + } + return NSERROR_OK; +} + +nserror html_object_abort_objects(html_content *htmlc) +{ + struct content_html_object *object; + + for (object = htmlc->object_list; + object != NULL; + object = object->next) { + if (object->content == NULL) + continue; + + switch (content_get_status(object->content)) { + case CONTENT_STATUS_DONE: + /* already loaded: do nothing */ + break; + + case CONTENT_STATUS_READY: + hlcache_handle_abort(object->content); + /* Active count will be updated when + * html_object_callback receives + * CONTENT_MSG_DONE from this object + */ + break; + + default: + hlcache_handle_abort(object->content); + hlcache_handle_release(object->content); + object->content = NULL; + if (object->box != NULL) { + htmlc->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", + htmlc->base.active); + } + break; + + } + } + + return NSERROR_OK; +} + +nserror html_object_close_objects(html_content *html) +{ + struct content_html_object *object, *next; + + for (object = html->object_list; object != NULL; object = next) { + next = object->next; + + if (object->content == NULL || object->box == NULL) + continue; + + if (content_get_type(object->content) == CONTENT_NONE) + continue; + + if (content_get_type(object->content) == CONTENT_HTML) { + guit->misc->schedule(-1, html_object_refresh, object); + } + + content_close(object->content); + } + return NSERROR_OK; +} + +nserror html_object_free_objects(html_content *html) +{ + while (html->object_list != NULL) { + struct content_html_object *victim = html->object_list; + + if (victim->content != NULL) { + NSLOG(netsurf, INFO, "object %p", victim->content); + + if (content_get_type(victim->content) == CONTENT_HTML) { + guit->misc->schedule(-1, html_object_refresh, victim); + } + hlcache_handle_release(victim->content); + } + + html->object_list = victim->next; + free(victim); + } + return NSERROR_OK; +} + + + +/* exported interface documented in html/html_internal.h */ +bool html_fetch_object(html_content *c, nsurl *url, struct box *box, + content_type permitted_types, + int available_width, int available_height, + bool background) +{ + struct content_html_object *object; + hlcache_child_context child; + nserror error; + + /* If we've already been aborted, don't bother attempting the fetch */ + if (c->aborted) + return true; + + child.charset = c->encoding; + child.quirks = c->base.quirks; + + object = calloc(1, sizeof(struct content_html_object)); + if (object == NULL) { + return false; + } + + object->parent = (struct content *) c; + object->next = NULL; + object->content = NULL; + object->box = box; + object->permitted_types = permitted_types; + object->background = background; + + error = hlcache_handle_retrieve(url, + HLCACHE_RETRIEVE_SNIFF_TYPE, + content_get_url(&c->base), NULL, + html_object_callback, object, &child, + object->permitted_types, &object->content); + if (error != NSERROR_OK) { + free(object); + return error != NSERROR_NOMEM; + } + + /* add to content object list */ + object->next = c->object_list; + c->object_list = object; + + c->num_objects++; + if (box != NULL) { + c->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + } + + return true; +} diff --git a/content/handlers/html/html_redraw.c b/content/handlers/html/html_redraw.c new file mode 100644 index 000000000..d05df8753 --- /dev/null +++ b/content/handlers/html/html_redraw.c @@ -0,0 +1,1951 @@ +/* + * Copyright 2004-2008 James Bursa + * Copyright 2004-2007 John M Bell + * Copyright 2004-2007 Richard Wilson + * Copyright 2005-2006 Adrian Lees + * Copyright 2006 Rob Kendrick + * Copyright 2008 Michael Drake + * Copyright 2009 Paul Blokus + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * + * Redrawing CONTENT_HTML implementation. + */ + +#include "utils/config.h" +#include +#include +#include +#include +#include +#include + +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/utils.h" +#include "utils/nsoption.h" +#include "netsurf/content.h" +#include "netsurf/browser_window.h" +#include "netsurf/plotters.h" +#include "netsurf/bitmap.h" +#include "netsurf/layout.h" +#include "content/content_protected.h" +#include "css/utils.h" +#include "desktop/selection.h" +#include "desktop/print.h" +#include "desktop/scrollbar.h" +#include "desktop/textarea.h" +#include "desktop/gui_internal.h" + +#include "html/box.h" +#include "html/font.h" +#include "html/form_internal.h" +#include "html/html_internal.h" +#include "html/layout.h" +#include "html/search.h" + + +bool html_redraw_debug = false; + +/** + * Determine if a box has a background that needs drawing + * + * \param box Box to consider + * \return True if box has a background, false otherwise. + */ +static bool html_redraw_box_has_background(struct box *box) +{ + if (box->background != NULL) + return true; + + if (box->style != NULL) { + css_color colour; + + css_computed_background_color(box->style, &colour); + + if (nscss_color_is_transparent(colour) == false) + return true; + } + + return false; +} + +/** + * Find the background box for a box + * + * \param box Box to find background box for + * \return Pointer to background box, or NULL if there is none + */ +static struct box *html_redraw_find_bg_box(struct box *box) +{ + /* Thanks to backwards compatibility, CSS defines the following: + * + * + If the box is for the root element and it has a background, + * use that (and then process the body box with no special case) + * + If the box is for the root element and it has no background, + * then use the background (if any) from the body element as if + * it were specified on the root. Then, when the box for the body + * element is processed, ignore the background. + * + For any other box, just use its own styling. + */ + if (box->parent == NULL) { + /* Root box */ + if (html_redraw_box_has_background(box)) + return box; + + /* No background on root box: consider body box, if any */ + if (box->children != NULL) { + if (html_redraw_box_has_background(box->children)) + return box->children; + } + } else if (box->parent != NULL && box->parent->parent == NULL) { + /* Body box: only render background if root has its own */ + if (html_redraw_box_has_background(box) && + html_redraw_box_has_background(box->parent)) + return box; + } else { + /* Any other box */ + if (html_redraw_box_has_background(box)) + return box; + } + + return NULL; +} + +/** + * Redraw a short text string, complete with highlighting + * (for selection/search) + * + * \param utf8_text pointer to UTF-8 text string + * \param utf8_len length of string, in bytes + * \param offset byte offset within textual representation + * \param space width of space that follows string (0 = no space) + * \param fstyle text style to use (pass text size unscaled) + * \param x x ordinate at which to plot text + * \param y y ordinate at which to plot text + * \param clip pointer to current clip rectangle + * \param height height of text string + * \param scale current display scale (1.0 = 100%) + * \param excluded exclude this text string from the selection + * \param c Content being redrawn. + * \param sel Selection context + * \param search Search context + * \param ctx current redraw context + * \return true iff successful and redraw should proceed + */ + +static bool +text_redraw(const char *utf8_text, + size_t utf8_len, + size_t offset, + int space, + const plot_font_style_t *fstyle, + int x, + int y, + const struct rect *clip, + int height, + float scale, + bool excluded, + struct content *c, + const struct selection *sel, + struct search_context *search, + const struct redraw_context *ctx) +{ + bool highlighted = false; + plot_font_style_t plot_fstyle = *fstyle; + nserror res; + + /* Need scaled text size to pass to plotters */ + plot_fstyle.size *= scale; + + /* is this box part of a selection? */ + if (!excluded && ctx->interactive == true) { + unsigned len = utf8_len + (space ? 1 : 0); + unsigned start_idx; + unsigned end_idx; + + /* first try the browser window's current selection */ + if (selection_defined(sel) && selection_highlighted(sel, + offset, offset + len, + &start_idx, &end_idx)) { + highlighted = true; + } + + /* what about the current search operation, if any? */ + if (!highlighted && (search != NULL) && + search_term_highlighted(c, + offset, offset + len, + &start_idx, &end_idx, + search)) { + highlighted = true; + } + + /* \todo make search terms visible within selected text */ + if (highlighted) { + struct rect r; + unsigned endtxt_idx = end_idx; + bool clip_changed = false; + bool text_visible = true; + int startx, endx; + plot_style_t pstyle_fill_hback = *plot_style_fill_white; + plot_font_style_t fstyle_hback = plot_fstyle; + + if (end_idx > utf8_len) { + /* adjust for trailing space, not present in + * utf8_text */ + assert(end_idx == utf8_len + 1); + endtxt_idx = utf8_len; + } + + res = guit->layout->width(fstyle, + utf8_text, start_idx, + &startx); + if (res != NSERROR_OK) { + startx = 0; + } + + res = guit->layout->width(fstyle, + utf8_text, endtxt_idx, + &endx); + if (res != NSERROR_OK) { + endx = 0; + } + + /* is there a trailing space that should be highlighted + * as well? */ + if (end_idx > utf8_len) { + endx += space; + } + + if (scale != 1.0) { + startx *= scale; + endx *= scale; + } + + /* draw any text preceding highlighted portion */ + if ((start_idx > 0) && + (ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + start_idx) != NSERROR_OK)) + return false; + + pstyle_fill_hback.fill_colour = fstyle->foreground; + + /* highlighted portion */ + r.x0 = x + startx; + r.y0 = y; + r.x1 = x + endx; + r.y1 = y + height * scale; + res = ctx->plot->rectangle(ctx, &pstyle_fill_hback, &r); + if (res != NSERROR_OK) { + return false; + } + + if (start_idx > 0) { + int px0 = max(x + startx, clip->x0); + int px1 = min(x + endx, clip->x1); + + if (px0 < px1) { + r.x0 = px0; + r.y0 = clip->y0; + r.x1 = px1; + r.y1 = clip->y1; + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + clip_changed = true; + } else { + text_visible = false; + } + } + + fstyle_hback.background = + pstyle_fill_hback.fill_colour; + fstyle_hback.foreground = colour_to_bw_furthest( + pstyle_fill_hback.fill_colour); + + if (text_visible && + (ctx->plot->text(ctx, + &fstyle_hback, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + endtxt_idx) != NSERROR_OK)) { + return false; + } + + /* draw any text succeeding highlighted portion */ + if (endtxt_idx < utf8_len) { + int px0 = max(x + endx, clip->x0); + if (px0 < clip->x1) { + + r.x0 = px0; + r.y0 = clip->y0; + r.x1 = clip->x1; + r.y1 = clip->y1; + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + clip_changed = true; + + res = ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + utf8_len); + if (res != NSERROR_OK) { + return false; + } + } + } + + if (clip_changed && + (ctx->plot->clip(ctx, clip) != NSERROR_OK)) { + return false; + } + } + } + + if (!highlighted) { + res = ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int) (height * 0.75 * scale), + utf8_text, + utf8_len); + if (res != NSERROR_OK) { + return false; + } + } + return true; +} + + +/** + * Plot a checkbox. + * + * \param x left coordinate + * \param y top coordinate + * \param width dimensions of checkbox + * \param height dimensions of checkbox + * \param selected the checkbox is selected + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_checkbox(int x, int y, int width, int height, + bool selected, const struct redraw_context *ctx) +{ + double z; + nserror res; + struct rect rect; + + z = width * 0.15; + if (z == 0) { + z = 1; + } + + rect.x0 = x; + rect.y0 = y ; + rect.x1 = x + width; + rect.y1 = y + height; + res = ctx->plot->rectangle(ctx, plot_style_fill_wbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + /* dark line across top */ + rect.y1 = y; + res = ctx->plot->line(ctx, plot_style_stroke_darkwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + /* dark line across left */ + rect.x1 = x; + rect.y1 = y + height; + res = ctx->plot->line(ctx, plot_style_stroke_darkwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + /* light line across right */ + rect.x0 = x + width; + rect.x1 = x + width; + res = ctx->plot->line(ctx, plot_style_stroke_lightwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + /* light line across bottom */ + rect.x0 = x; + rect.y0 = y + height; + res = ctx->plot->line(ctx, plot_style_stroke_lightwbasec, &rect); + if (res != NSERROR_OK) { + return false; + } + + if (selected) { + if (width < 12 || height < 12) { + /* render a solid box instead of a tick */ + rect.x0 = x + z + z; + rect.y0 = y + z + z; + rect.x1 = x + width - z; + rect.y1 = y + height - z; + res = ctx->plot->rectangle(ctx, plot_style_fill_wblobc, &rect); + if (res != NSERROR_OK) { + return false; + } + } else { + /* render a tick, as it'll fit comfortably */ + rect.x0 = x + width - z; + rect.y0 = y + z; + rect.x1 = x + (z * 3); + rect.y1 = y + height - z; + res = ctx->plot->line(ctx, plot_style_stroke_wblobc, &rect); + if (res != NSERROR_OK) { + return false; + } + + rect.x0 = x + (z * 3); + rect.y0 = y + height - z; + rect.x1 = x + z + z; + rect.y1 = y + (height / 2); + res = ctx->plot->line(ctx, plot_style_stroke_wblobc, &rect); + if (res != NSERROR_OK) { + return false; + } + } + } + return true; +} + + +/** + * Plot a radio icon. + * + * \param x left coordinate + * \param y top coordinate + * \param width dimensions of radio icon + * \param height dimensions of radio icon + * \param selected the radio icon is selected + * \param ctx current redraw context + * \return true if successful, false otherwise + */ +static bool html_redraw_radio(int x, int y, int width, int height, + bool selected, const struct redraw_context *ctx) +{ + nserror res; + + /* plot background of radio button */ + res = ctx->plot->disc(ctx, + plot_style_fill_wbasec, + x + width * 0.5, + y + height * 0.5, + width * 0.5 - 1); + if (res != NSERROR_OK) { + return false; + } + + /* plot dark arc */ + res = ctx->plot->arc(ctx, + plot_style_fill_darkwbasec, + x + width * 0.5, + y + height * 0.5, + width * 0.5 - 1, + 45, + 225); + if (res != NSERROR_OK) { + return false; + } + + /* plot light arc */ + res = ctx->plot->arc(ctx, + plot_style_fill_lightwbasec, + x + width * 0.5, + y + height * 0.5, + width * 0.5 - 1, + 225, + 45); + if (res != NSERROR_OK) { + return false; + } + + if (selected) { + /* plot selection blob */ + res = ctx->plot->disc(ctx, + plot_style_fill_wblobc, + x + width * 0.5, + y + height * 0.5, + width * 0.3 - 1); + if (res != NSERROR_OK) { + return false; + } + } + + return true; +} + + +/** + * Plot a file upload input. + * + * \param x left coordinate + * \param y top coordinate + * \param width dimensions of input + * \param height dimensions of input + * \param box box of input + * \param scale scale for redraw + * \param background_colour current background colour + * \param len_ctx Length conversion context + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_file(int x, int y, int width, int height, + struct box *box, float scale, colour background_colour, + const nscss_len_ctx *len_ctx, + const struct redraw_context *ctx) +{ + int text_width; + const char *text; + size_t length; + plot_font_style_t fstyle; + nserror res; + + font_plot_style_from_css(len_ctx, box->style, &fstyle); + fstyle.background = background_colour; + + if (box->gadget->value) { + text = box->gadget->value; + } else { + text = messages_get("Form_Drop"); + } + length = strlen(text); + + res = guit->layout->width(&fstyle, text, length, &text_width); + if (res != NSERROR_OK) { + return false; + } + text_width *= scale; + if (width < text_width + 8) { + x = x + width - text_width - 4; + } else { + x = x + 4; + } + + res = ctx->plot->text(ctx, &fstyle, x, y + height * 0.75, text, length); + if (res != NSERROR_OK) { + return false; + } + return true; +} + + +/** + * Plot background images. + * + * The reason for the presence of \a background is the backwards compatibility + * mess that is backgrounds on <body>. The background will be drawn relative + * to \a box, using the background information contained within \a background. + * + * \param x coordinate of box + * \param y coordinate of box + * \param box box to draw background image of + * \param scale scale for redraw + * \param clip current clip rectangle + * \param background_colour current background colour + * \param background box containing background details (usually \a box) + * \param len_ctx Length conversion context + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_background(int x, int y, struct box *box, float scale, + const struct rect *clip, colour *background_colour, + struct box *background, + const nscss_len_ctx *len_ctx, + const struct redraw_context *ctx) +{ + bool repeat_x = false; + bool repeat_y = false; + bool plot_colour = true; + bool plot_content; + bool clip_to_children = false; + struct box *clip_box = box; + int ox = x, oy = y; + int width, height; + css_fixed hpos = 0, vpos = 0; + css_unit hunit = CSS_UNIT_PX, vunit = CSS_UNIT_PX; + struct box *parent; + struct rect r = *clip; + css_color bgcol; + plot_style_t pstyle_fill_bg = { + .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = *background_colour, + }; + nserror res; + + if (ctx->background_images == false) + return true; + + plot_content = (background->background != NULL); + + if (plot_content) { + if (!box->parent) { + /* Root element, special case: + * background origin calc. is based on margin box */ + x -= box->margin[LEFT] * scale; + y -= box->margin[TOP] * scale; + width = box->margin[LEFT] + box->padding[LEFT] + + box->width + box->padding[RIGHT] + + box->margin[RIGHT]; + height = box->margin[TOP] + box->padding[TOP] + + box->height + box->padding[BOTTOM] + + box->margin[BOTTOM]; + } else { + width = box->padding[LEFT] + box->width + + box->padding[RIGHT]; + height = box->padding[TOP] + box->height + + box->padding[BOTTOM]; + } + /* handle background-repeat */ + switch (css_computed_background_repeat(background->style)) { + case CSS_BACKGROUND_REPEAT_REPEAT: + repeat_x = repeat_y = true; + /* optimisation: only plot the colour if + * bitmap is not opaque */ + plot_colour = !content_get_opaque(background->background); + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_X: + repeat_x = true; + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_Y: + repeat_y = true; + break; + + case CSS_BACKGROUND_REPEAT_NO_REPEAT: + break; + + default: + break; + } + + /* handle background-position */ + css_computed_background_position(background->style, + &hpos, &hunit, &vpos, &vunit); + if (hunit == CSS_UNIT_PCT) { + x += (width - + content_get_width(background->background)) * + scale * FIXTOFLT(hpos) / 100.; + } else { + x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit, + background->style)) * scale); + } + + if (vunit == CSS_UNIT_PCT) { + y += (height - + content_get_height(background->background)) * + scale * FIXTOFLT(vpos) / 100.; + } else { + y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit, + background->style)) * scale); + } + } + + /* special case for table rows as their background needs + * to be clipped to all the cells */ + if (box->type == BOX_TABLE_ROW) { + css_fixed h = 0, v = 0; + css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; + + for (parent = box->parent; + ((parent) && (parent->type != BOX_TABLE)); + parent = parent->parent); + assert(parent && (parent->style)); + + css_computed_border_spacing(parent->style, &h, &hu, &v, &vu); + + clip_to_children = (h > 0) || (v > 0); + + if (clip_to_children) + clip_box = box->children; + } + + for (; clip_box; clip_box = clip_box->next) { + /* clip to child boxes if needed */ + if (clip_to_children) { + assert(clip_box->type == BOX_TABLE_CELL); + + /* update clip.* to the child cell */ + r.x0 = ox + (clip_box->x * scale); + r.y0 = oy + (clip_box->y * scale); + r.x1 = r.x0 + (clip_box->padding[LEFT] + + clip_box->width + + clip_box->padding[RIGHT]) * scale; + r.y1 = r.y0 + (clip_box->padding[TOP] + + clip_box->height + + clip_box->padding[BOTTOM]) * scale; + + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (r.x1 > clip->x1) r.x1 = clip->x1; + if (r.y1 > clip->y1) r.y1 = clip->y1; + + css_computed_background_color(clip_box->style, &bgcol); + + /* attributes override */ + /* if the background content is opaque there + * is no need to plot underneath it. + */ + if ((r.x0 >= r.x1) || + (r.y0 >= r.y1) || + (nscss_color_is_transparent(bgcol) == false) || + ((clip_box->background != NULL) && + content_get_opaque(clip_box->background))) + continue; + } + + /* plot the background colour */ + css_computed_background_color(background->style, &bgcol); + + if (nscss_color_is_transparent(bgcol) == false) { + *background_colour = nscss_color_to_ns(bgcol); + pstyle_fill_bg.fill_colour = *background_colour; + if (plot_colour) { + res = ctx->plot->rectangle(ctx, &pstyle_fill_bg, &r); + if (res != NSERROR_OK) { + return false; + } + } + } + /* and plot the image */ + if (plot_content) { + width = content_get_width(background->background); + height = content_get_height(background->background); + + /* ensure clip area only as large as required */ + if (!repeat_x) { + if (r.x0 < x) + r.x0 = x; + if (r.x1 > x + width * scale) + r.x1 = x + width * scale; + } + if (!repeat_y) { + if (r.y0 < y) + r.y0 = y; + if (r.y1 > y + height * scale) + r.y1 = y + height * scale; + } + /* valid clipping rectangles only */ + if ((r.x0 < r.x1) && (r.y0 < r.y1)) { + struct content_redraw_data bg_data; + + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + bg_data.x = x; + bg_data.y = y; + bg_data.width = ceilf(width * scale); + bg_data.height = ceilf(height * scale); + bg_data.background_colour = *background_colour; + bg_data.scale = scale; + bg_data.repeat_x = repeat_x; + bg_data.repeat_y = repeat_y; + + /* We just continue if redraw fails */ + content_redraw(background->background, + &bg_data, &r, ctx); + } + } + + /* only rows being clipped to child boxes loop */ + if (!clip_to_children) + return true; + } + return true; +} + + +/** + * Plot an inline's background and/or background image. + * + * \param x coordinate of box + * \param y coordinate of box + * \param box BOX_INLINE which created the background + * \param scale scale for redraw + * \param clip coordinates of clip rectangle + * \param b coordinates of border edge rectangle + * \param first true if this is the first rectangle associated with the inline + * \param last true if this is the last rectangle associated with the inline + * \param background_colour updated to current background colour if plotted + * \param len_ctx Length conversion context + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_inline_background(int x, int y, struct box *box, + float scale, const struct rect *clip, struct rect b, + bool first, bool last, colour *background_colour, + const nscss_len_ctx *len_ctx, + const struct redraw_context *ctx) +{ + struct rect r = *clip; + bool repeat_x = false; + bool repeat_y = false; + bool plot_colour = true; + bool plot_content; + css_fixed hpos = 0, vpos = 0; + css_unit hunit = CSS_UNIT_PX, vunit = CSS_UNIT_PX; + css_color bgcol; + plot_style_t pstyle_fill_bg = { + .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = *background_colour, + }; + nserror res; + + plot_content = (box->background != NULL); + + if (html_redraw_printing && nsoption_bool(remove_backgrounds)) + return true; + + if (plot_content) { + /* handle background-repeat */ + switch (css_computed_background_repeat(box->style)) { + case CSS_BACKGROUND_REPEAT_REPEAT: + repeat_x = repeat_y = true; + /* optimisation: only plot the colour if + * bitmap is not opaque + */ + plot_colour = !content_get_opaque(box->background); + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_X: + repeat_x = true; + break; + + case CSS_BACKGROUND_REPEAT_REPEAT_Y: + repeat_y = true; + break; + + case CSS_BACKGROUND_REPEAT_NO_REPEAT: + break; + + default: + break; + } + + /* handle background-position */ + css_computed_background_position(box->style, + &hpos, &hunit, &vpos, &vunit); + if (hunit == CSS_UNIT_PCT) { + x += (b.x1 - b.x0 - + content_get_width(box->background) * + scale) * FIXTOFLT(hpos) / 100.; + + if (!repeat_x && ((hpos < 2 && !first) || + (hpos > 98 && !last))){ + plot_content = false; + } + } else { + x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit, + box->style)) * scale); + } + + if (vunit == CSS_UNIT_PCT) { + y += (b.y1 - b.y0 - + content_get_height(box->background) * + scale) * FIXTOFLT(vpos) / 100.; + } else { + y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit, + box->style)) * scale); + } + } + + /* plot the background colour */ + css_computed_background_color(box->style, &bgcol); + + if (nscss_color_is_transparent(bgcol) == false) { + *background_colour = nscss_color_to_ns(bgcol); + pstyle_fill_bg.fill_colour = *background_colour; + + if (plot_colour) { + res = ctx->plot->rectangle(ctx, &pstyle_fill_bg, &r); + if (res != NSERROR_OK) { + return false; + } + } + } + /* and plot the image */ + if (plot_content) { + int width = content_get_width(box->background); + int height = content_get_height(box->background); + + if (!repeat_x) { + if (r.x0 < x) + r.x0 = x; + if (r.x1 > x + width * scale) + r.x1 = x + width * scale; + } + if (!repeat_y) { + if (r.y0 < y) + r.y0 = y; + if (r.y1 > y + height * scale) + r.y1 = y + height * scale; + } + /* valid clipping rectangles only */ + if ((r.x0 < r.x1) && (r.y0 < r.y1)) { + struct content_redraw_data bg_data; + + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + bg_data.x = x; + bg_data.y = y; + bg_data.width = ceilf(width * scale); + bg_data.height = ceilf(height * scale); + bg_data.background_colour = *background_colour; + bg_data.scale = scale; + bg_data.repeat_x = repeat_x; + bg_data.repeat_y = repeat_y; + + /* We just continue if redraw fails */ + content_redraw(box->background, &bg_data, &r, ctx); + } + } + + return true; +} + + +/** + * Plot text decoration for an inline box. + * + * \param box box to plot decorations for, of type BOX_INLINE + * \param x x coordinate of parent of box + * \param y y coordinate of parent of box + * \param scale scale for redraw + * \param colour colour for decorations + * \param ratio position of line as a ratio of line height + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool +html_redraw_text_decoration_inline(struct box *box, + int x, int y, + float scale, + colour colour, + float ratio, + const struct redraw_context *ctx) +{ + struct box *c; + plot_style_t plot_style_box = { + .stroke_type = PLOT_OP_TYPE_SOLID, + .stroke_colour = colour, + }; + nserror res; + struct rect rect; + + for (c = box->next; + c && c != box->inline_end; + c = c->next) { + if (c->type != BOX_TEXT) { + continue; + } + rect.x0 = (x + c->x) * scale; + rect.y0 = (y + c->y + c->height * ratio) * scale; + rect.x1 = (x + c->x + c->width) * scale; + rect.y1 = (y + c->y + c->height * ratio) * scale; + res = ctx->plot->line(ctx, &plot_style_box, &rect); + if (res != NSERROR_OK) { + return false; + } + } + return true; +} + + +/** + * Plot text decoration for an non-inline box. + * + * \param box box to plot decorations for, of type other than BOX_INLINE + * \param x x coordinate of box + * \param y y coordinate of box + * \param scale scale for redraw + * \param colour colour for decorations + * \param ratio position of line as a ratio of line height + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool +html_redraw_text_decoration_block(struct box *box, + int x, int y, + float scale, + colour colour, + float ratio, + const struct redraw_context *ctx) +{ + struct box *c; + plot_style_t plot_style_box = { + .stroke_type = PLOT_OP_TYPE_SOLID, + .stroke_colour = colour, + }; + nserror res; + struct rect rect; + + /* draw through text descendants */ + for (c = box->children; c; c = c->next) { + if (c->type == BOX_TEXT) { + rect.x0 = (x + c->x) * scale; + rect.y0 = (y + c->y + c->height * ratio) * scale; + rect.x1 = (x + c->x + c->width) * scale; + rect.y1 = (y + c->y + c->height * ratio) * scale; + res = ctx->plot->line(ctx, &plot_style_box, &rect); + if (res != NSERROR_OK) { + return false; + } + } else if ((c->type == BOX_INLINE_CONTAINER) || (c->type == BOX_BLOCK)) { + if (!html_redraw_text_decoration_block(c, + x + c->x, y + c->y, + scale, colour, ratio, ctx)) + return false; + } + } + return true; +} + + +/** + * Plot text decoration for a box. + * + * \param box box to plot decorations for + * \param x_parent x coordinate of parent of box + * \param y_parent y coordinate of parent of box + * \param scale scale for redraw + * \param background_colour current background colour + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_text_decoration(struct box *box, + int x_parent, int y_parent, float scale, + colour background_colour, const struct redraw_context *ctx) +{ + static const enum css_text_decoration_e decoration[] = { + CSS_TEXT_DECORATION_UNDERLINE, CSS_TEXT_DECORATION_OVERLINE, + CSS_TEXT_DECORATION_LINE_THROUGH }; + static const float line_ratio[] = { 0.9, 0.1, 0.5 }; + colour fgcol; + unsigned int i; + css_color col; + + css_computed_color(box->style, &col); + fgcol = nscss_color_to_ns(col); + + /* antialias colour for under/overline */ + if (html_redraw_printing == false) + fgcol = blend_colour(background_colour, fgcol); + + if (box->type == BOX_INLINE) { + if (!box->inline_end) + return true; + for (i = 0; i != NOF_ELEMENTS(decoration); i++) + if (css_computed_text_decoration(box->style) & + decoration[i]) + if (!html_redraw_text_decoration_inline(box, + x_parent, y_parent, scale, + fgcol, line_ratio[i], ctx)) + return false; + } else { + for (i = 0; i != NOF_ELEMENTS(decoration); i++) + if (css_computed_text_decoration(box->style) & + decoration[i]) + if (!html_redraw_text_decoration_block(box, + x_parent + box->x, + y_parent + box->y, + scale, + fgcol, line_ratio[i], ctx)) + return false; + } + + return true; +} + + +/** + * Redraw the text content of a box, possibly partially highlighted + * because the text has been selected, or matches a search operation. + * + * \param html The html content to redraw text within. + * \param box box with text content + * \param x x co-ord of box + * \param y y co-ord of box + * \param clip current clip rectangle + * \param scale current scale setting (1.0 = 100%) + * \param current_background_color + * \param ctx current redraw context + * \return true iff successful and redraw should proceed + */ + +static bool html_redraw_text_box(const html_content *html, struct box *box, + int x, int y, const struct rect *clip, float scale, + colour current_background_color, + const struct redraw_context *ctx) +{ + bool excluded = (box->object != NULL); + plot_font_style_t fstyle; + + font_plot_style_from_css(&html->len_ctx, box->style, &fstyle); + fstyle.background = current_background_color; + + if (!text_redraw(box->text, box->length, box->byte_offset, + box->space, &fstyle, x, y, + clip, box->height, scale, excluded, + (struct content *)html, &html->sel, + html->search, ctx)) + return false; + + return true; +} + +bool html_redraw_box(const html_content *html, struct box *box, + int x_parent, int y_parent, + const struct rect *clip, float scale, + colour current_background_color, + const struct redraw_context *ctx); + +/** + * Draw the various children of a box. + * + * \param html html content + * \param box box to draw children of + * \param x_parent coordinate of parent box + * \param y_parent coordinate of parent box + * \param clip clip rectangle + * \param scale scale for redraw + * \param current_background_color background colour under this box + * \param ctx current redraw context + * \return true if successful, false otherwise + */ + +static bool html_redraw_box_children(const html_content *html, struct box *box, + int x_parent, int y_parent, + const struct rect *clip, float scale, + colour current_background_color, + const struct redraw_context *ctx) +{ + struct box *c; + + for (c = box->children; c; c = c->next) { + + if (c->type != BOX_FLOAT_LEFT && c->type != BOX_FLOAT_RIGHT) + if (!html_redraw_box(html, c, + x_parent + box->x - + scrollbar_get_offset(box->scroll_x), + y_parent + box->y - + scrollbar_get_offset(box->scroll_y), + clip, scale, current_background_color, + ctx)) + return false; + } + for (c = box->float_children; c; c = c->next_float) + if (!html_redraw_box(html, c, + x_parent + box->x - + scrollbar_get_offset(box->scroll_x), + y_parent + box->y - + scrollbar_get_offset(box->scroll_y), + clip, scale, current_background_color, + ctx)) + return false; + + return true; +} + +/** + * Recursively draw a box. + * + * \param html html content + * \param box box to draw + * \param x_parent coordinate of parent box + * \param y_parent coordinate of parent box + * \param clip clip rectangle + * \param scale scale for redraw + * \param current_background_color background colour under this box + * \param ctx current redraw context + * \return true if successful, false otherwise + * + * x, y, clip_[xy][01] are in target coordinates. + */ + +bool html_redraw_box(const html_content *html, struct box *box, + int x_parent, int y_parent, + const struct rect *clip, const float scale, + colour current_background_color, + const struct redraw_context *ctx) +{ + const struct plotter_table *plot = ctx->plot; + int x, y; + int width, height; + int padding_left, padding_top, padding_width, padding_height; + int border_left, border_top, border_right, border_bottom; + struct rect r; + struct rect rect; + int x_scrolled, y_scrolled; + struct box *bg_box = NULL; + bool has_x_scroll, has_y_scroll; + css_computed_clip_rect css_rect; + enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; + enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; + + if (html_redraw_printing && (box->flags & PRINTED)) + return true; + + if (box->style != NULL) { + overflow_x = css_computed_overflow_x(box->style); + overflow_y = css_computed_overflow_y(box->style); + } + + /* avoid trivial FP maths */ + if (scale == 1.0) { + x = x_parent + box->x; + y = y_parent + box->y; + width = box->width; + height = box->height; + padding_left = box->padding[LEFT]; + padding_top = box->padding[TOP]; + padding_width = padding_left + box->width + box->padding[RIGHT]; + padding_height = padding_top + box->height + + box->padding[BOTTOM]; + border_left = box->border[LEFT].width; + border_top = box->border[TOP].width; + border_right = box->border[RIGHT].width; + border_bottom = box->border[BOTTOM].width; + } else { + x = (x_parent + box->x) * scale; + y = (y_parent + box->y) * scale; + width = box->width * scale; + height = box->height * scale; + /* left and top padding values are normally zero, + * so avoid trivial FP maths */ + padding_left = box->padding[LEFT] ? box->padding[LEFT] * scale + : 0; + padding_top = box->padding[TOP] ? box->padding[TOP] * scale + : 0; + padding_width = (box->padding[LEFT] + box->width + + box->padding[RIGHT]) * scale; + padding_height = (box->padding[TOP] + box->height + + box->padding[BOTTOM]) * scale; + border_left = box->border[LEFT].width * scale; + border_top = box->border[TOP].width * scale; + border_right = box->border[RIGHT].width * scale; + border_bottom = box->border[BOTTOM].width * scale; + } + + /* calculate rectangle covering this box and descendants */ + if (box->style && overflow_x != CSS_OVERFLOW_VISIBLE && + box->parent != NULL) { + /* box contents clipped to box size */ + r.x0 = x - border_left; + r.x1 = x + padding_width + border_right; + } else { + /* box contents can hang out of the box; use descendant box */ + if (scale == 1.0) { + r.x0 = x + box->descendant_x0; + r.x1 = x + box->descendant_x1 + 1; + } else { + r.x0 = x + box->descendant_x0 * scale; + r.x1 = x + box->descendant_x1 * scale + 1; + } + if (!box->parent) { + /* root element */ + int margin_left, margin_right; + if (scale == 1.0) { + margin_left = box->margin[LEFT]; + margin_right = box->margin[RIGHT]; + } else { + margin_left = box->margin[LEFT] * scale; + margin_right = box->margin[RIGHT] * scale; + } + r.x0 = x - border_left - margin_left < r.x0 ? + x - border_left - margin_left : r.x0; + r.x1 = x + padding_width + border_right + + margin_right > r.x1 ? + x + padding_width + border_right + + margin_right : r.x1; + } + } + + /* calculate rectangle covering this box and descendants */ + if (box->style && overflow_y != CSS_OVERFLOW_VISIBLE && + box->parent != NULL) { + /* box contents clipped to box size */ + r.y0 = y - border_top; + r.y1 = y + padding_height + border_bottom; + } else { + /* box contents can hang out of the box; use descendant box */ + if (scale == 1.0) { + r.y0 = y + box->descendant_y0; + r.y1 = y + box->descendant_y1 + 1; + } else { + r.y0 = y + box->descendant_y0 * scale; + r.y1 = y + box->descendant_y1 * scale + 1; + } + if (!box->parent) { + /* root element */ + int margin_top, margin_bottom; + if (scale == 1.0) { + margin_top = box->margin[TOP]; + margin_bottom = box->margin[BOTTOM]; + } else { + margin_top = box->margin[TOP] * scale; + margin_bottom = box->margin[BOTTOM] * scale; + } + r.y0 = y - border_top - margin_top < r.y0 ? + y - border_top - margin_top : r.y0; + r.y1 = y + padding_height + border_bottom + + margin_bottom > r.y1 ? + y + padding_height + border_bottom + + margin_bottom : r.y1; + } + } + + /* return if the rectangle is completely outside the clip rectangle */ + if (clip->y1 < r.y0 || r.y1 < clip->y0 || + clip->x1 < r.x0 || r.x1 < clip->x0) + return true; + + /*if the rectangle is under the page bottom but it can fit in a page, + don't print it now*/ + if (html_redraw_printing) { + if (r.y1 > html_redraw_printing_border) { + if (r.y1 - r.y0 <= html_redraw_printing_border && + (box->type == BOX_TEXT || + box->type == BOX_TABLE_CELL + || box->object || box->gadget)) { + /*remember the highest of all points from the + not printed elements*/ + if (r.y0 < html_redraw_printing_top_cropped) + html_redraw_printing_top_cropped = r.y0; + return true; + } + } + else box->flags |= PRINTED; /*it won't be printed anymore*/ + } + + /* if visibility is hidden render children only */ + if (box->style && css_computed_visibility(box->style) == + CSS_VISIBILITY_HIDDEN) { + if ((ctx->plot->group_start) && + (ctx->plot->group_start(ctx, "hidden box") != NSERROR_OK)) + return false; + if (!html_redraw_box_children(html, box, x_parent, y_parent, + &r, scale, current_background_color, ctx)) + return false; + return ((!ctx->plot->group_end) || (ctx->plot->group_end(ctx) == NSERROR_OK)); + } + + if ((ctx->plot->group_start) && + (ctx->plot->group_start(ctx,"vis box") != NSERROR_OK)) { + return false; + } + + if (box->style != NULL && + css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE && + css_computed_clip(box->style, &css_rect) == + CSS_CLIP_RECT) { + /* We have an absolutly positioned box with a clip rect */ + if (css_rect.left_auto == false) + r.x0 = x - border_left + FIXTOINT(nscss_len2px( + &html->len_ctx, + css_rect.left, css_rect.lunit, + box->style)); + + if (css_rect.top_auto == false) + r.y0 = y - border_top + FIXTOINT(nscss_len2px( + &html->len_ctx, + css_rect.top, css_rect.tunit, + box->style)); + + if (css_rect.right_auto == false) + r.x1 = x - border_left + FIXTOINT(nscss_len2px( + &html->len_ctx, + css_rect.right, css_rect.runit, + box->style)); + + if (css_rect.bottom_auto == false) + r.y1 = y - border_top + FIXTOINT(nscss_len2px( + &html->len_ctx, + css_rect.bottom, css_rect.bunit, + box->style)); + + /* find intersection of clip rectangle and box */ + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (clip->x1 < r.x1) r.x1 = clip->x1; + if (clip->y1 < r.y1) r.y1 = clip->y1; + /* Nothing to do for invalid rectangles */ + if (r.x0 >= r.x1 || r.y0 >= r.y1) + /* not an error */ + return ((!ctx->plot->group_end) || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + /* clip to it */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + + } else if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || + box->type == BOX_TABLE_CELL || box->object) { + /* find intersection of clip rectangle and box */ + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (clip->x1 < r.x1) r.x1 = clip->x1; + if (clip->y1 < r.y1) r.y1 = clip->y1; + /* no point trying to draw 0-width/height boxes */ + if (r.x0 == r.x1 || r.y0 == r.y1) + /* not an error */ + return ((!ctx->plot->group_end) || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + /* clip to it */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + } else { + /* clip box is fine, clip to it */ + r = *clip; + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + } + + /* background colour and image for block level content and replaced + * inlines */ + + bg_box = html_redraw_find_bg_box(box); + + /* bg_box == NULL implies that this box should not have + * its background rendered. Otherwise filter out linebreaks, + * optimize away non-differing inlines, only plot background + * for BOX_TEXT it's in an inline */ + if (bg_box && bg_box->type != BOX_BR && + bg_box->type != BOX_TEXT && + bg_box->type != BOX_INLINE_END && + (bg_box->type != BOX_INLINE || bg_box->object || + bg_box->flags & IFRAME || box->flags & REPLACE_DIM || + (bg_box->gadget != NULL && + (bg_box->gadget->type == GADGET_TEXTAREA || + bg_box->gadget->type == GADGET_TEXTBOX || + bg_box->gadget->type == GADGET_PASSWORD)))) { + /* find intersection of clip box and border edge */ + struct rect p; + p.x0 = x - border_left < r.x0 ? r.x0 : x - border_left; + p.y0 = y - border_top < r.y0 ? r.y0 : y - border_top; + p.x1 = x + padding_width + border_right < r.x1 ? + x + padding_width + border_right : r.x1; + p.y1 = y + padding_height + border_bottom < r.y1 ? + y + padding_height + border_bottom : r.y1; + if (!box->parent) { + /* Root element, special case: + * background covers margins too */ + int m_left, m_top, m_right, m_bottom; + if (scale == 1.0) { + m_left = box->margin[LEFT]; + m_top = box->margin[TOP]; + m_right = box->margin[RIGHT]; + m_bottom = box->margin[BOTTOM]; + } else { + m_left = box->margin[LEFT] * scale; + m_top = box->margin[TOP] * scale; + m_right = box->margin[RIGHT] * scale; + m_bottom = box->margin[BOTTOM] * scale; + } + p.x0 = p.x0 - m_left < r.x0 ? r.x0 : p.x0 - m_left; + p.y0 = p.y0 - m_top < r.y0 ? r.y0 : p.y0 - m_top; + p.x1 = p.x1 + m_right < r.x1 ? p.x1 + m_right : r.x1; + p.y1 = p.y1 + m_bottom < r.y1 ? p.y1 + m_bottom : r.y1; + } + /* valid clipping rectangles only */ + if ((p.x0 < p.x1) && (p.y0 < p.y1)) { + /* plot background */ + if (!html_redraw_background(x, y, box, scale, &p, + ¤t_background_color, bg_box, + &html->len_ctx, ctx)) + return false; + /* restore previous graphics window */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + } + } + + /* borders for block level content and replaced inlines */ + if (box->style && + box->type != BOX_TEXT && + box->type != BOX_INLINE_END && + (box->type != BOX_INLINE || box->object || + box->flags & IFRAME || box->flags & REPLACE_DIM || + (box->gadget != NULL && + (box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_TEXTBOX || + box->gadget->type == GADGET_PASSWORD))) && + (border_top || border_right || border_bottom || border_left)) { + if (!html_redraw_borders(box, x_parent, y_parent, + padding_width, padding_height, &r, + scale, ctx)) + return false; + } + + /* backgrounds and borders for non-replaced inlines */ + if (box->style && box->type == BOX_INLINE && box->inline_end && + (html_redraw_box_has_background(box) || + border_top || border_right || + border_bottom || border_left)) { + /* inline backgrounds and borders span other boxes and may + * wrap onto separate lines */ + struct box *ib; + struct rect b; /* border edge rectangle */ + struct rect p; /* clipped rect */ + bool first = true; + int ib_x; + int ib_y = y; + int ib_p_width; + int ib_b_left, ib_b_right; + + b.x0 = x - border_left; + b.x1 = x + padding_width + border_right; + b.y0 = y - border_top; + b.y1 = y + padding_height + border_bottom; + + p.x0 = b.x0 < r.x0 ? r.x0 : b.x0; + p.x1 = b.x1 < r.x1 ? b.x1 : r.x1; + p.y0 = b.y0 < r.y0 ? r.y0 : b.y0; + p.y1 = b.y1 < r.y1 ? b.y1 : r.y1; + for (ib = box; ib; ib = ib->next) { + /* to get extents of rectangle(s) associated with + * inline, cycle though all boxes in inline, skipping + * over floats */ + if (ib->type == BOX_FLOAT_LEFT || + ib->type == BOX_FLOAT_RIGHT) + continue; + if (scale == 1.0) { + ib_x = x_parent + ib->x; + ib_y = y_parent + ib->y; + ib_p_width = ib->padding[LEFT] + ib->width + + ib->padding[RIGHT]; + ib_b_left = ib->border[LEFT].width; + ib_b_right = ib->border[RIGHT].width; + } else { + ib_x = (x_parent + ib->x) * scale; + ib_y = (y_parent + ib->y) * scale; + ib_p_width = (ib->padding[LEFT] + ib->width + + ib->padding[RIGHT]) * scale; + ib_b_left = ib->border[LEFT].width * scale; + ib_b_right = ib->border[RIGHT].width * scale; + } + + if ((ib->flags & NEW_LINE) && ib != box) { + /* inline element has wrapped, plot background + * and borders */ + if (!html_redraw_inline_background( + x, y, box, scale, &p, b, + first, false, + ¤t_background_color, + &html->len_ctx, ctx)) + return false; + /* restore previous graphics window */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + if (!html_redraw_inline_borders(box, b, &r, + scale, first, false, ctx)) + return false; + /* reset coords */ + b.x0 = ib_x - ib_b_left; + b.y0 = ib_y - border_top - padding_top; + b.y1 = ib_y + padding_height - padding_top + + border_bottom; + + p.x0 = b.x0 < r.x0 ? r.x0 : b.x0; + p.y0 = b.y0 < r.y0 ? r.y0 : b.y0; + p.y1 = b.y1 < r.y1 ? b.y1 : r.y1; + + first = false; + } + + /* increase width for current box */ + b.x1 = ib_x + ib_p_width + ib_b_right; + p.x1 = b.x1 < r.x1 ? b.x1 : r.x1; + + if (ib == box->inline_end) + /* reached end of BOX_INLINE span */ + break; + } + /* plot background and borders for last rectangle of + * the inline */ + if (!html_redraw_inline_background(x, ib_y, box, scale, &p, b, + first, true, ¤t_background_color, + &html->len_ctx, ctx)) + return false; + /* restore previous graphics window */ + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + if (!html_redraw_inline_borders(box, b, &r, scale, first, true, + ctx)) + return false; + + } + + /* Debug outlines */ + if (html_redraw_debug) { + int margin_left, margin_right; + int margin_top, margin_bottom; + if (scale == 1.0) { + /* avoid trivial fp maths */ + margin_left = box->margin[LEFT]; + margin_top = box->margin[TOP]; + margin_right = box->margin[RIGHT]; + margin_bottom = box->margin[BOTTOM]; + } else { + margin_left = box->margin[LEFT] * scale; + margin_top = box->margin[TOP] * scale; + margin_right = box->margin[RIGHT] * scale; + margin_bottom = box->margin[BOTTOM] * scale; + } + /* Content edge -- blue */ + rect.x0 = x + padding_left; + rect.y0 = y + padding_top; + rect.x1 = x + padding_left + width; + rect.y1 = y + padding_top + height; + if (ctx->plot->rectangle(ctx, plot_style_content_edge, &rect) != NSERROR_OK) + return false; + + /* Padding edge -- red */ + rect.x0 = x; + rect.y0 = y; + rect.x1 = x + padding_width; + rect.y1 = y + padding_height; + if (ctx->plot->rectangle(ctx, plot_style_padding_edge, &rect) != NSERROR_OK) + return false; + + /* Margin edge -- yellow */ + rect.x0 = x - border_left - margin_left; + rect.y0 = y - border_top - margin_top; + rect.x1 = x + padding_width + border_right + margin_right; + rect.y1 = y + padding_height + border_bottom + margin_bottom; + if (ctx->plot->rectangle(ctx, plot_style_margin_edge, &rect) != NSERROR_OK) + return false; + } + + /* clip to the padding edge for objects, or boxes with overflow hidden + * or scroll, unless it's the root element */ + if (box->parent != NULL) { + bool need_clip = false; + if (box->object || box->flags & IFRAME || + (overflow_x != CSS_OVERFLOW_VISIBLE && + overflow_y != CSS_OVERFLOW_VISIBLE)) { + r.x0 = x; + r.y0 = y; + r.x1 = x + padding_width; + r.y1 = y + padding_height; + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (clip->x1 < r.x1) r.x1 = clip->x1; + if (clip->y1 < r.y1) r.y1 = clip->y1; + if (r.x1 <= r.x0 || r.y1 <= r.y0) { + return (!ctx->plot->group_end || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + } + need_clip = true; + + } else if (overflow_x != CSS_OVERFLOW_VISIBLE) { + r.x0 = x; + r.y0 = clip->y0; + r.x1 = x + padding_width; + r.y1 = clip->y1; + if (r.x0 < clip->x0) r.x0 = clip->x0; + if (clip->x1 < r.x1) r.x1 = clip->x1; + if (r.x1 <= r.x0) { + return (!ctx->plot->group_end || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + } + need_clip = true; + + } else if (overflow_y != CSS_OVERFLOW_VISIBLE) { + r.x0 = clip->x0; + r.y0 = y; + r.x1 = clip->x1; + r.y1 = y + padding_height; + if (r.y0 < clip->y0) r.y0 = clip->y0; + if (clip->y1 < r.y1) r.y1 = clip->y1; + if (r.y1 <= r.y0) { + return (!ctx->plot->group_end || + (ctx->plot->group_end(ctx) == NSERROR_OK)); + } + need_clip = true; + } + + if (need_clip && + (box->type == BOX_BLOCK || + box->type == BOX_INLINE_BLOCK || + box->type == BOX_TABLE_CELL || box->object)) { + if (ctx->plot->clip(ctx, &r) != NSERROR_OK) + return false; + } + } + + /* text decoration */ + if ((box->type != BOX_TEXT) && + box->style && + css_computed_text_decoration(box->style) != CSS_TEXT_DECORATION_NONE) { + if (!html_redraw_text_decoration(box, x_parent, y_parent, + scale, current_background_color, ctx)) + return false; + } + + if (box->object && width != 0 && height != 0) { + struct content_redraw_data obj_data; + + x_scrolled = x - scrollbar_get_offset(box->scroll_x) * scale; + y_scrolled = y - scrollbar_get_offset(box->scroll_y) * scale; + + obj_data.x = x_scrolled + padding_left; + obj_data.y = y_scrolled + padding_top; + obj_data.width = width; + obj_data.height = height; + obj_data.background_colour = current_background_color; + obj_data.scale = scale; + obj_data.repeat_x = false; + obj_data.repeat_y = false; + + if (content_get_type(box->object) == CONTENT_HTML) { + obj_data.x /= scale; + obj_data.y /= scale; + } + + if (!content_redraw(box->object, &obj_data, &r, ctx)) { + /* Show image fail */ + /* Unicode (U+FFFC) 'OBJECT REPLACEMENT CHARACTER' */ + const char *obj = "\xef\xbf\xbc"; + int obj_width; + int obj_x = x + padding_left; + nserror res; + + rect.x0 = x + padding_left; + rect.y0 = y + padding_top; + rect.x1 = x + padding_left + width - 1; + rect.y1 = y + padding_top + height - 1; + res = ctx->plot->rectangle(ctx, plot_style_broken_object, &rect); + if (res != NSERROR_OK) { + return false; + } + + res = guit->layout->width(plot_fstyle_broken_object, + obj, + sizeof(obj) - 1, + &obj_width); + if (res != NSERROR_OK) { + obj_x += 1; + } else { + obj_x += width / 2 - obj_width / 2; + } + + if (ctx->plot->text(ctx, + plot_fstyle_broken_object, + obj_x, y + padding_top + (int)(height * 0.75), + obj, sizeof(obj) - 1) != NSERROR_OK) + return false; + } + + } else if (box->iframe) { + /* Offset is passed to browser window redraw unscaled */ + browser_window_redraw(box->iframe, + (x + padding_left) / scale, + (y + padding_top) / scale, &r, ctx); + + } else if (box->gadget && box->gadget->type == GADGET_CHECKBOX) { + if (!html_redraw_checkbox(x + padding_left, y + padding_top, + width, height, box->gadget->selected, ctx)) + return false; + + } else if (box->gadget && box->gadget->type == GADGET_RADIO) { + if (!html_redraw_radio(x + padding_left, y + padding_top, + width, height, box->gadget->selected, ctx)) + return false; + + } else if (box->gadget && box->gadget->type == GADGET_FILE) { + if (!html_redraw_file(x + padding_left, y + padding_top, + width, height, box, scale, + current_background_color, &html->len_ctx, ctx)) + return false; + + } else if (box->gadget && + (box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_TEXTBOX)) { + textarea_redraw(box->gadget->data.text.ta, x, y, + current_background_color, scale, &r, ctx); + + } else if (box->text) { + if (!html_redraw_text_box(html, box, x, y, &r, scale, + current_background_color, ctx)) + return false; + + } else { + if (!html_redraw_box_children(html, box, x_parent, y_parent, &r, + scale, current_background_color, ctx)) + return false; + } + + if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || + box->type == BOX_TABLE_CELL || box->type == BOX_INLINE) + if (ctx->plot->clip(ctx, clip) != NSERROR_OK) + return false; + + /* list marker */ + if (box->list_marker) { + if (!html_redraw_box(html, box->list_marker, + x_parent + box->x - + scrollbar_get_offset(box->scroll_x), + y_parent + box->y - + scrollbar_get_offset(box->scroll_y), + clip, scale, current_background_color, ctx)) + return false; + } + + /* scrollbars */ + if (((box->style && box->type != BOX_BR && + box->type != BOX_TABLE && box->type != BOX_INLINE && + (overflow_x == CSS_OVERFLOW_SCROLL || + overflow_x == CSS_OVERFLOW_AUTO || + overflow_y == CSS_OVERFLOW_SCROLL || + overflow_y == CSS_OVERFLOW_AUTO)) || + (box->object && content_get_type(box->object) == + CONTENT_HTML)) && box->parent != NULL) { + + has_x_scroll = box_hscrollbar_present(box); + has_y_scroll = box_vscrollbar_present(box); + + if (!box_handle_scrollbars((struct content *)html, + box, has_x_scroll, has_y_scroll)) + return false; + + if (box->scroll_x != NULL) + scrollbar_redraw(box->scroll_x, + x_parent + box->x, + y_parent + box->y + box->padding[TOP] + + box->height + box->padding[BOTTOM] - + SCROLLBAR_WIDTH, clip, scale, ctx); + if (box->scroll_y != NULL) + scrollbar_redraw(box->scroll_y, + x_parent + box->x + box->padding[LEFT] + + box->width + box->padding[RIGHT] - + SCROLLBAR_WIDTH, + y_parent + box->y, clip, scale, ctx); + } + + if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || + box->type == BOX_TABLE_CELL || box->type == BOX_INLINE) { + if (ctx->plot->clip(ctx, clip) != NSERROR_OK) + return false; + } + + return ((!plot->group_end) || (ctx->plot->group_end(ctx) == NSERROR_OK)); +} + +/** + * Draw a CONTENT_HTML using the current set of plotters (plot). + * + * \param c content of type CONTENT_HTML + * \param data redraw data for this content redraw + * \param clip current clip region + * \param ctx current redraw context + * \return true if successful, false otherwise + * + * x, y, clip_[xy][01] are in target coordinates. + */ + +bool html_redraw(struct content *c, struct content_redraw_data *data, + const struct rect *clip, const struct redraw_context *ctx) +{ + html_content *html = (html_content *) c; + struct box *box; + bool result = true; + bool select, select_only; + plot_style_t pstyle_fill_bg = { + .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = data->background_colour, + }; + + box = html->layout; + assert(box); + + /* The select menu needs special treating because, when opened, it + * reaches beyond its layout box. + */ + select = false; + select_only = false; + if (ctx->interactive && html->visible_select_menu != NULL) { + struct form_control *control = html->visible_select_menu; + select = true; + /* check if the redraw rectangle is completely inside of the + select menu */ + select_only = form_clip_inside_select_menu(control, + data->scale, clip); + } + + if (!select_only) { + /* clear to background colour */ + result = (ctx->plot->clip(ctx, clip) == NSERROR_OK); + + if (html->background_colour != NS_TRANSPARENT) + pstyle_fill_bg.fill_colour = html->background_colour; + + result &= (ctx->plot->rectangle(ctx, &pstyle_fill_bg, clip) == NSERROR_OK); + + result &= html_redraw_box(html, box, data->x, data->y, clip, + data->scale, pstyle_fill_bg.fill_colour, ctx); + } + + if (select) { + int menu_x, menu_y; + box = html->visible_select_menu->box; + box_coords(box, &menu_x, &menu_y); + + menu_x -= box->border[LEFT].width; + menu_y += box->height + box->border[BOTTOM].width + + box->padding[BOTTOM] + box->padding[TOP]; + result &= form_redraw_select_menu(html->visible_select_menu, + data->x + menu_x, data->y + menu_y, + data->scale, clip, ctx); + } + + return result; + +} diff --git a/content/handlers/html/html_redraw_border.c b/content/handlers/html/html_redraw_border.c new file mode 100644 index 000000000..2a849e853 --- /dev/null +++ b/content/handlers/html/html_redraw_border.c @@ -0,0 +1,928 @@ +/* + * Copyright 2017 Vincent Sanders + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * + * Redrawing CONTENT_HTML borders implementation. + */ + +#include +#include + +#include "utils/log.h" +#include "netsurf/plotters.h" +#include "netsurf/css.h" + +#include "html/box.h" +#include "html/html_internal.h" + + +static plot_style_t plot_style_bdr = { + .stroke_type = PLOT_OP_TYPE_DASH, +}; +static plot_style_t plot_style_fillbdr = { + .fill_type = PLOT_OP_TYPE_SOLID, +}; +static plot_style_t plot_style_fillbdr_dark = { + .fill_type = PLOT_OP_TYPE_SOLID, +}; +static plot_style_t plot_style_fillbdr_light = { + .fill_type = PLOT_OP_TYPE_SOLID, +}; +static plot_style_t plot_style_fillbdr_ddark = { + .fill_type = PLOT_OP_TYPE_SOLID, +}; +static plot_style_t plot_style_fillbdr_dlight = { + .fill_type = PLOT_OP_TYPE_SOLID, +}; + + +static inline nserror +plot_clipped_rectangle(const struct redraw_context *ctx, + const plot_style_t *style, + const struct rect *clip, + struct rect *rect) +{ + nserror res; + + rect->x0 = (clip->x0 > rect->x0) ? clip->x0 : rect->x0; + rect->y0 = (clip->y0 > rect->y0) ? clip->y0 : rect->y0; + rect->x1 = (clip->x1 < rect->x1) ? clip->x1 : rect->x1; + rect->y1 = (clip->y1 < rect->y1) ? clip->y1 : rect->y1; + if ((rect->x0 < rect->x1) && (rect->y0 < rect->y1)) { + /* valid clip rectangles only */ + res = ctx->plot->rectangle(ctx, style, rect); + } else { + res = NSERROR_OK; + } + return res; +} + + +/** + * Draw one border. + * + * \param side index of border side (TOP, RIGHT, BOTTOM, LEFT) + * \param p array of precomputed border vertices + * \param c colour for border + * \param style border line style + * \param thickness border thickness + * \param rectangular whether border is rectangular + * \param clip cliping area for redrawing border. + * \param ctx current redraw context + * \return NSERROR_OK if successful otherwise appropriate error code + */ +static nserror +html_redraw_border_plot(const int side, + const int *p, + colour c, + enum css_border_style_e style, + int thickness, + bool rectangular, + const struct rect *clip, + const struct redraw_context *ctx) +{ + int z[8]; /* Vertices of border part */ + unsigned int light = side; + plot_style_t *plot_style_bdr_in; + plot_style_t *plot_style_bdr_out; + nserror res = NSERROR_OK; + struct rect rect; + + if (c == NS_TRANSPARENT) { + return res; + } + + plot_style_bdr.stroke_type = PLOT_OP_TYPE_DASH; + plot_style_bdr.stroke_colour = c; + plot_style_bdr.stroke_width = thickness; + plot_style_fillbdr.fill_colour = c; + plot_style_fillbdr_dark.fill_colour = darken_colour(c); + plot_style_fillbdr_light.fill_colour = lighten_colour(c); + plot_style_fillbdr_ddark.fill_colour = double_darken_colour(c); + plot_style_fillbdr_dlight.fill_colour = double_lighten_colour(c); + + switch (style) { + case CSS_BORDER_STYLE_DOTTED: + plot_style_bdr.stroke_type = PLOT_OP_TYPE_DOT; + /* fall through */ + case CSS_BORDER_STYLE_DASHED: + rect.x0 = (p[0] + p[2]) / 2; + rect.y0 = (p[1] + p[3]) / 2; + rect.x1 = (p[4] + p[6]) / 2; + rect.y1 = (p[5] + p[7]) / 2; + res = ctx->plot->line(ctx, &plot_style_bdr, &rect); + break; + + case CSS_BORDER_STYLE_SOLID: + /* fall through to default */ + default: + if (rectangular || thickness == 1) { + + if (side == TOP || side == RIGHT) { + rect.x0 = p[2]; + rect.y0 = p[3]; + if ((side == TOP) && + (p[4] - p[6] != 0)) { + rect.x1 = p[4]; + } else { + rect.x1 = p[6]; + } + rect.y1 = p[7]; + } else { + rect.x0 = p[6]; + rect.y0 = p[7]; + rect.x1 = p[2]; + if ((side == LEFT) && + (p[1] - p[3] != 0)) { + rect.y1 = p[1]; + } else { + rect.y1 = p[3]; + } + } + res = plot_clipped_rectangle(ctx, + &plot_style_fillbdr, + clip, + &rect); + } else { + res = ctx->plot->polygon(ctx, &plot_style_fillbdr, p, 4); + } + break; + + case CSS_BORDER_STYLE_DOUBLE: + z[0] = p[0]; + z[1] = p[1]; + z[2] = (p[0] * 2 + p[2]) / 3; + z[3] = (p[1] * 2 + p[3]) / 3; + z[4] = (p[6] * 2 + p[4]) / 3; + z[5] = (p[7] * 2 + p[5]) / 3; + z[6] = p[6]; + z[7] = p[7]; + res = ctx->plot->polygon(ctx, &plot_style_fillbdr, z, 4); + if (res == NSERROR_OK) { + z[0] = p[2]; + z[1] = p[3]; + z[2] = (p[2] * 2 + p[0]) / 3; + z[3] = (p[3] * 2 + p[1]) / 3; + z[4] = (p[4] * 2 + p[6]) / 3; + z[5] = (p[5] * 2 + p[7]) / 3; + z[6] = p[4]; + z[7] = p[5]; + res = ctx->plot->polygon(ctx, &plot_style_fillbdr, z, 4); + } + break; + + case CSS_BORDER_STYLE_GROOVE: + light = 3 - light; + /* fall through */ + case CSS_BORDER_STYLE_RIDGE: + /* choose correct colours for each part of the border line */ + if (light <= 1) { + plot_style_bdr_in = &plot_style_fillbdr_dark; + plot_style_bdr_out = &plot_style_fillbdr_light; + } else { + plot_style_bdr_in = &plot_style_fillbdr_light; + plot_style_bdr_out = &plot_style_fillbdr_dark; + } + + /* Render border */ + if ((rectangular || thickness == 2) && thickness != 1) { + /* Border made up from two parts and can be + * plotted with rectangles + */ + + /* First part */ + if (side == TOP || side == RIGHT) { + rect.x0 = (p[0] + p[2]) / 2; + rect.y0 = (p[1] + p[3]) / 2; + rect.x1 = p[6]; + rect.y1 = p[7]; + } else { + rect.x0 = p[6]; + rect.y0 = p[7]; + rect.x1 = (p[0] + p[2]) / 2; + rect.y1 = (p[1] + p[3]) / 2; + } + res = plot_clipped_rectangle(ctx, + plot_style_bdr_in, + clip, + &rect); + if (res != NSERROR_OK) { + return res; + } + + /* Second part */ + if (side == TOP || side == RIGHT) { + rect.x0 = p[2]; + rect.y0 = p[3]; + rect.x1 = (p[6] + p[4]) / 2; + rect.y1 = (p[7] + p[5]) / 2; + } else { + rect.x0 = (p[6] + p[4]) / 2; + rect.y0 = (p[7] + p[5]) / 2; + rect.x1 = p[2]; + rect.y1 = p[3]; + } + res = plot_clipped_rectangle(ctx, + plot_style_bdr_out, + clip, + &rect); + } else if (thickness == 1) { + /* Border made up from one part which can be + * plotted as a rectangle + */ + + if (side == TOP || side == RIGHT) { + rect.x0 = p[2]; + rect.y0 = p[3]; + rect.x1 = p[6]; + rect.y1 = p[7]; + rect.x1 = ((side == TOP) && (p[4] - p[6] != 0)) ? + rect.x1 + p[4] - p[6] : rect.x1; + + res = plot_clipped_rectangle(ctx, + plot_style_bdr_in, + clip, + &rect); + } else { + rect.x0 = p[6]; + rect.y0 = p[7]; + rect.x1 = p[2]; + rect.y1 = p[3]; + rect.y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ? + rect.y1 + p[1] - p[3] : rect.y1; + res = plot_clipped_rectangle(ctx, + plot_style_bdr_out, + clip, + &rect); + } + } else { + /* Border made up from two parts and can't be + * plotted with rectangles + */ + z[0] = p[0]; + z[1] = p[1]; + z[2] = (p[0] + p[2]) / 2; + z[3] = (p[1] + p[3]) / 2; + z[4] = (p[6] + p[4]) / 2; + z[5] = (p[7] + p[5]) / 2; + z[6] = p[6]; + z[7] = p[7]; + res = ctx->plot->polygon(ctx, plot_style_bdr_in, z, 4); + if (res == NSERROR_OK) { + z[0] = p[2]; + z[1] = p[3]; + z[6] = p[4]; + z[7] = p[5]; + res = ctx->plot->polygon(ctx, + plot_style_bdr_out, + z, + 4); + } + } + break; + + case CSS_BORDER_STYLE_INSET: + light = (light + 2) % 4; + /* fall through */ + case CSS_BORDER_STYLE_OUTSET: + /* choose correct colours for each part of the border line */ + switch (light) { + case 0: + plot_style_bdr_in = &plot_style_fillbdr_light; + plot_style_bdr_out = &plot_style_fillbdr_dlight; + break; + case 1: + plot_style_bdr_in = &plot_style_fillbdr_ddark; + plot_style_bdr_out = &plot_style_fillbdr_dark; + break; + case 2: + plot_style_bdr_in = &plot_style_fillbdr_dark; + plot_style_bdr_out = &plot_style_fillbdr_ddark; + break; + case 3: + plot_style_bdr_in = &plot_style_fillbdr_dlight; + plot_style_bdr_out = &plot_style_fillbdr_light; + break; + default: + plot_style_bdr_in = &plot_style_fillbdr; + plot_style_bdr_out = &plot_style_fillbdr; + break; + } + + /* Render border */ + if ((rectangular || thickness == 2) && thickness != 1) { + /* Border made up from two parts and can be + * plotted with rectangles + */ + + /* First part */ + if (side == TOP || side == RIGHT) { + rect.x0 = (p[0] + p[2]) / 2; + rect.y0 = (p[1] + p[3]) / 2; + rect.x1 = p[6]; + rect.y1 = p[7]; + } else { + rect.x0 = p[6]; + rect.y0 = p[7]; + rect.x1 = (p[0] + p[2]) / 2; + rect.y1 = (p[1] + p[3]) / 2; + } + res = plot_clipped_rectangle(ctx, + plot_style_bdr_in, + clip, + &rect); + if (res != NSERROR_OK) { + return res; + } + + /* Second part */ + if (side == TOP || side == RIGHT) { + rect.x0 = p[2]; + rect.y0 = p[3]; + rect.x1 = (p[6] + p[4]) / 2; + rect.y1 = (p[7] + p[5]) / 2; + } else { + rect.x0 = (p[6] + p[4]) / 2; + rect.y0 = (p[7] + p[5]) / 2; + rect.x1 = p[2]; + rect.y1 = p[3]; + } + res = plot_clipped_rectangle(ctx, + plot_style_bdr_out, + clip, + &rect); + } else if (thickness == 1) { + /* Border made up from one part which can be + * plotted as a rectangle + */ + + if (side == TOP || side == RIGHT) { + rect.x0 = p[2]; + rect.y0 = p[3]; + rect.x1 = p[6]; + rect.y1 = p[7]; + rect.x1 = ((side == TOP) && (p[4] - p[6] != 0)) ? + rect.x1 + p[4] - p[6] : rect.x1; + res = plot_clipped_rectangle(ctx, + plot_style_bdr_in, + clip, + &rect); + } else { + rect.x0 = p[6]; + rect.y0 = p[7]; + rect.x1 = p[2]; + rect.y1 = p[3]; + rect.y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ? + rect.y1 + p[1] - p[3] : rect.y1; + res = plot_clipped_rectangle(ctx, + plot_style_bdr_out, + clip, + &rect); + } + } else { + /* Border made up from two parts and can't be + * plotted with rectangles + */ + + z[0] = p[0]; + z[1] = p[1]; + z[2] = (p[0] + p[2]) / 2; + z[3] = (p[1] + p[3]) / 2; + z[4] = (p[6] + p[4]) / 2; + z[5] = (p[7] + p[5]) / 2; + z[6] = p[6]; + z[7] = p[7]; + res = ctx->plot->polygon(ctx, plot_style_bdr_in, z, 4); + if (res != NSERROR_OK) { + return res; + } + z[0] = p[2]; + z[1] = p[3]; + z[6] = p[4]; + z[7] = p[5]; + res = ctx->plot->polygon(ctx, plot_style_bdr_out, z, 4); + } + break; + } + + return res; +} + + +/** + * Draw borders for a box. + * + * \param box box to draw + * \param x_parent coordinate of left padding edge of parent of box + * \param y_parent coordinate of top padding edge of parent of box + * \param p_width width of padding box + * \param p_height height of padding box + * \param clip cliping area for redrawing border. + * \param scale scale for redraw + * \param ctx current redraw context + * \return true if successful, false otherwise + */ +bool +html_redraw_borders(struct box *box, + int x_parent, + int y_parent, + int p_width, + int p_height, + const struct rect *clip, + float scale, + const struct redraw_context *ctx) +{ + unsigned int sides[] = { LEFT, RIGHT, TOP, BOTTOM }; + int top = box->border[TOP].width; + int right = box->border[RIGHT].width; + int bottom = box->border[BOTTOM].width; + int left = box->border[LEFT].width; + int x, y; + unsigned int i, side; + int p[8]; /* Box border vertices */ + int z[8]; /* Border vertices */ + bool square_end_1 = false; + bool square_end_2 = false; + nserror res; + + x = x_parent + box->x; + y = y_parent + box->y; + + if (scale != 1.0) { + top *= scale; + right *= scale; + bottom *= scale; + left *= scale; + x *= scale; + y *= scale; + } + + assert(box->style); + + /* Calculate border vertices + * + * A----------------------+ + * | \ / | + * | B--------------+ | + * | | | | + * | +--------------C | + * | / \ | + * +----------------------D + */ + p[0] = x - left; p[1] = y - top; /* A */ + p[2] = x; p[3] = y; /* B */ + p[4] = x + p_width; p[5] = y + p_height; /* C */ + p[6] = x + p_width + right; p[7] = y + p_height + bottom; /* D */ + + for (i = 0; i != 4; i++) { + colour col = 0; + side = sides[i]; /* plot order */ + + if (box->border[side].width == 0 || + nscss_color_is_transparent(box->border[side].c)) { + continue; + } + + switch (side) { + case LEFT: + square_end_1 = (top == 0); + square_end_2 = (bottom == 0); + + z[0] = p[0]; z[1] = p[7]; + z[2] = p[2]; z[3] = p[5]; + z[4] = p[2]; z[5] = p[3]; + z[6] = p[0]; z[7] = p[1]; + + if (nscss_color_is_transparent(box->border[TOP].c) == false && + box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) { + /* make border overhang top corner fully, + * if top border is opaque + */ + z[5] -= top; + square_end_1 = true; + } + if (nscss_color_is_transparent(box->border[BOTTOM].c) == false && + box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) { + /* make border overhang bottom corner fully, + * if bottom border is opaque + */ + z[3] += bottom; + square_end_2 = true; + } + + col = nscss_color_to_ns(box->border[side].c); + + res = html_redraw_border_plot(side, + z, + col, + box->border[side].style, + box->border[side].width * scale, + square_end_1 && square_end_2, + clip, + ctx); + if (res != NSERROR_OK) { + return false; + } + break; + + case RIGHT: + square_end_1 = (top == 0); + square_end_2 = (bottom == 0); + + z[0] = p[6]; z[1] = p[1]; + z[2] = p[4]; z[3] = p[3]; + z[4] = p[4]; z[5] = p[5]; + z[6] = p[6]; z[7] = p[7]; + + if (nscss_color_is_transparent(box->border[TOP].c) == false && + box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) { + /* make border overhang top corner fully, + * if top border is opaque + */ + z[3] -= top; + square_end_1 = true; + } + if (nscss_color_is_transparent(box->border[BOTTOM].c) == false && + box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) { + /* make border overhang bottom corner fully, + * if bottom border is opaque + */ + z[5] += bottom; + square_end_2 = true; + } + + col = nscss_color_to_ns(box->border[side].c); + + res = html_redraw_border_plot(side, + z, + col, + box->border[side].style, + box->border[side].width * scale, + square_end_1 && square_end_2, + clip, + ctx); + if (res != NSERROR_OK) { + return false; + } + break; + + case TOP: + if (clip->y0 > p[3]) { + /* clip rectangle is below border; nothing to + * plot + */ + continue; + } + + square_end_1 = (left == 0); + square_end_2 = (right == 0); + + z[0] = p[2]; z[1] = p[3]; + z[2] = p[0]; z[3] = p[1]; + z[4] = p[6]; z[5] = p[1]; + z[6] = p[4]; z[7] = p[3]; + + if (box->border[TOP].style == CSS_BORDER_STYLE_SOLID && + box->border[TOP].c == box->border[LEFT].c) { + /* don't bother overlapping left corner if + * it's the same colour anyway + */ + z[2] += left; + square_end_1 = true; + } + if (box->border[TOP].style == CSS_BORDER_STYLE_SOLID && + box->border[TOP].c == box->border[RIGHT].c) { + /* don't bother overlapping right corner if + * it's the same colour anyway + */ + z[4] -= right; + square_end_2 = true; + } + + col = nscss_color_to_ns(box->border[side].c); + + res = html_redraw_border_plot(side, + z, + col, + box->border[side].style, + box->border[side].width * scale, + square_end_1 && square_end_2, + clip, + ctx); + if (res != NSERROR_OK) { + return false; + } + break; + + case BOTTOM: + if (clip->y1 < p[5]) { + /* clip rectangle is above border; nothing to + * plot + */ + continue; + } + + square_end_1 = (left == 0); + square_end_2 = (right == 0); + + z[0] = p[4]; z[1] = p[5]; + z[2] = p[6]; z[3] = p[7]; + z[4] = p[0]; z[5] = p[7]; + z[6] = p[2]; z[7] = p[5]; + + if (box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID && + box->border[BOTTOM].c == box->border[LEFT].c) { + /* don't bother overlapping left corner if + * it's the same colour anyway + */ + z[4] += left; + square_end_1 = true; + } + if (box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID && + box->border[BOTTOM].c == box->border[RIGHT].c) { + /* don't bother overlapping right corner if + * it's the same colour anyway + */ + z[2] -= right; + square_end_2 = true; + } + + col = nscss_color_to_ns(box->border[side].c); + + res = html_redraw_border_plot(side, + z, + col, + box->border[side].style, + box->border[side].width * scale, + square_end_1 && square_end_2, + clip, + ctx); + if (res != NSERROR_OK) { + return false; + } + break; + + default: + assert(side == TOP || side == BOTTOM || + side == LEFT || side == RIGHT); + break; + } + } + + return true; +} + + +/** + * Draw an inline's borders. + * + * \param box BOX_INLINE which created the border + * \param b coordinates of border edge rectangle + * \param clip cliping area for redrawing border. + * \param scale scale for redraw + * \param first true if this is the first rectangle associated with the inline + * \param last true if this is the last rectangle associated with the inline + * \param ctx current redraw context + * \return true if successful, false otherwise + */ +bool +html_redraw_inline_borders(struct box *box, + struct rect b, + const struct rect *clip, + float scale, + bool first, + bool last, + const struct redraw_context *ctx) +{ + int top = box->border[TOP].width; + int right = box->border[RIGHT].width; + int bottom = box->border[BOTTOM].width; + int left = box->border[LEFT].width; + colour col; + int p[8]; /* Box border vertices */ + int z[8]; /* Border vertices */ + bool square_end_1; + bool square_end_2; + nserror res; + + if (scale != 1.0) { + top *= scale; + right *= scale; + bottom *= scale; + left *= scale; + } + + /* Calculate border vertices + * + * A----------------------+ + * | \ / | + * | B--------------+ | + * | | | | + * | +--------------C | + * | / \ | + * +----------------------D + */ + p[0] = b.x0; p[1] = b.y0; /* A */ + p[2] = first ? b.x0 + left : b.x0; p[3] = b.y0 + top; /* B */ + p[4] = last ? b.x1 - right : b.x1; p[5] = b.y1 - bottom; /* C */ + p[6] = b.x1; p[7] = b.y1; /* D */ + + assert(box->style); + + /* Left */ + square_end_1 = (top == 0); + square_end_2 = (bottom == 0); + if (left != 0 && + first && + nscss_color_is_transparent(box->border[LEFT].c) == false) { + col = nscss_color_to_ns(box->border[LEFT].c); + + z[0] = p[0]; z[1] = p[7]; + z[2] = p[2]; z[3] = p[5]; + z[4] = p[2]; z[5] = p[3]; + z[6] = p[0]; z[7] = p[1]; + + if (nscss_color_is_transparent(box->border[TOP].c) == false && + box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) { + /* make border overhang top corner fully, + * if top border is opaque + */ + z[5] -= top; + square_end_1 = true; + } + + if (nscss_color_is_transparent(box->border[BOTTOM].c) == false && + box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) { + /* make border overhang bottom corner fully, + * if bottom border is opaque + */ + z[3] += bottom; + square_end_2 = true; + } + + res = html_redraw_border_plot(LEFT, + z, + col, + box->border[LEFT].style, + left, + square_end_1 && square_end_2, + clip, + ctx); + if (res != NSERROR_OK) { + return false; + } + } + + /* Right */ + square_end_1 = (top == 0); + square_end_2 = (bottom == 0); + if (right != 0 && + last && + nscss_color_is_transparent(box->border[RIGHT].c) == false) { + col = nscss_color_to_ns(box->border[RIGHT].c); + + z[0] = p[6]; z[1] = p[1]; + z[2] = p[4]; z[3] = p[3]; + z[4] = p[4]; z[5] = p[5]; + z[6] = p[6]; z[7] = p[7]; + + if (nscss_color_is_transparent(box->border[TOP].c) == false && + box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) { + /* make border overhang top corner fully, + * if top border is opaque + */ + z[3] -= top; + square_end_1 = true; + } + + if (nscss_color_is_transparent(box->border[BOTTOM].c) == false && + box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) { + /* make border overhang bottom corner fully, + * if bottom border is opaque + */ + z[5] += bottom; + square_end_2 = true; + } + + res = html_redraw_border_plot(RIGHT, + z, + col, + box->border[RIGHT].style, + right, + square_end_1 && square_end_2, + clip, + ctx); + if (res != NSERROR_OK) { + return false; + } + } + + /* Top */ + square_end_1 = (left == 0); + square_end_2 = (right == 0); + if (top != 0 && + nscss_color_is_transparent(box->border[TOP].c) == false) { + col = nscss_color_to_ns(box->border[TOP].c); + + z[0] = p[2]; z[1] = p[3]; + z[2] = p[0]; z[3] = p[1]; + z[4] = p[6]; z[5] = p[1]; + z[6] = p[4]; z[7] = p[3]; + + if (first && + box->border[TOP].style == CSS_BORDER_STYLE_SOLID && + box->border[TOP].c == box->border[LEFT].c) { + /* don't bother overlapping left corner if + * it's the same colour anyway + */ + z[2] += left; + square_end_1 = true; + } + + if (last && + box->border[TOP].style == CSS_BORDER_STYLE_SOLID && + box->border[TOP].c == box->border[RIGHT].c) { + /* don't bother overlapping right corner if + * it's the same colour anyway + */ + z[4] -= right; + square_end_2 = true; + } + + res = html_redraw_border_plot(TOP, + z, + col, + box->border[TOP].style, + top, + square_end_1 && square_end_2, + clip, + ctx); + if (res != NSERROR_OK) { + return false; + } + } + + /* Bottom */ + square_end_1 = (left == 0); + square_end_2 = (right == 0); + if (bottom != 0 && + nscss_color_is_transparent(box->border[BOTTOM].c) == false) { + col = nscss_color_to_ns(box->border[BOTTOM].c); + + z[0] = p[4]; z[1] = p[5]; + z[2] = p[6]; z[3] = p[7]; + z[4] = p[0]; z[5] = p[7]; + z[6] = p[2]; z[7] = p[5]; + + if (first && + box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID && + box->border[BOTTOM].c == box->border[LEFT].c) { + /* don't bother overlapping left corner if + * it's the same colour anyway + */ + z[4] += left; + square_end_1 = true; + } + + if (last && + box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID && + box->border[BOTTOM].c == box->border[RIGHT].c) { + /* don't bother overlapping right corner if + * it's the same colour anyway + */ + z[2] -= right; + square_end_2 = true; + } + + res = html_redraw_border_plot(BOTTOM, + z, + col, + box->border[BOTTOM].style, + bottom, + square_end_1 && square_end_2, + clip, + ctx); + if (res != NSERROR_OK) { + return false; + } + } + + return true; +} diff --git a/content/handlers/html/html_script.c b/content/handlers/html/html_script.c new file mode 100644 index 000000000..e18a0caa0 --- /dev/null +++ b/content/handlers/html/html_script.c @@ -0,0 +1,604 @@ +/* + * Copyright 2012 Vincent Sanders + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * implementation of content handling for text/html scripts. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "utils/config.h" +#include "utils/corestrings.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "netsurf/content.h" +#include "javascript/js.h" +#include "content/content_protected.h" +#include "content/fetch.h" +#include "content/hlcache.h" + +#include "html/html_internal.h" + +typedef bool (script_handler_t)(struct jscontext *jscontext, const char *data, size_t size) ; + + +static script_handler_t *select_script_handler(content_type ctype) +{ + if (ctype == CONTENT_JS) { + return js_exec; + } + return NULL; +} + + +/* exported internal interface documented in html/html_internal.h */ +nserror html_script_exec(html_content *c) +{ + unsigned int i; + struct html_script *s; + script_handler_t *script_handler; + + if (c->jscontext == NULL) { + return NSERROR_BAD_PARAMETER; + } + + for (i = 0, s = c->scripts; i != c->scripts_count; i++, s++) { + if (s->already_started) { + continue; + } + + if ((s->type == HTML_SCRIPT_ASYNC) || + (s->type == HTML_SCRIPT_DEFER)) { + /* ensure script content is present */ + if (s->data.handle == NULL) + continue; + + /* ensure script content fetch status is not an error */ + if (content_get_status(s->data.handle) == + CONTENT_STATUS_ERROR) + continue; + + /* ensure script handler for content type */ + script_handler = select_script_handler( + content_get_type(s->data.handle)); + if (script_handler == NULL) + continue; /* unsupported type */ + + if (content_get_status(s->data.handle) == + CONTENT_STATUS_DONE) { + /* external script is now available */ + const char *data; + unsigned long size; + data = content_get_source_data( + s->data.handle, &size ); + script_handler(c->jscontext, data, size); + + s->already_started = true; + + } + } + } + + return NSERROR_OK; +} + +/* create new html script entry */ +static struct html_script * +html_process_new_script(html_content *c, + dom_string *mimetype, + enum html_script_type type) +{ + struct html_script *nscript; + /* add space for new script entry */ + nscript = realloc(c->scripts, + sizeof(struct html_script) * (c->scripts_count + 1)); + if (nscript == NULL) { + return NULL; + } + + c->scripts = nscript; + + /* increment script entry count */ + nscript = &c->scripts[c->scripts_count]; + c->scripts_count++; + + nscript->already_started = false; + nscript->parser_inserted = false; + nscript->force_async = true; + nscript->ready_exec = false; + nscript->async = false; + nscript->defer = false; + + nscript->type = type; + + nscript->mimetype = dom_string_ref(mimetype); /* reference mimetype */ + + return nscript; +} + +/** + * Callback for asyncronous scripts + */ +static nserror +convert_script_async_cb(hlcache_handle *script, + const hlcache_event *event, + void *pw) +{ + html_content *parent = pw; + unsigned int i; + struct html_script *s; + + /* Find script */ + for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) { + if (s->type == HTML_SCRIPT_ASYNC && s->data.handle == script) + break; + } + + assert(i != parent->scripts_count); + + switch (event->type) { + case CONTENT_MSG_LOADING: + break; + + case CONTENT_MSG_READY: + break; + + case CONTENT_MSG_DONE: + NSLOG(netsurf, INFO, "script %d done '%s'", i, + nsurl_access(hlcache_handle_get_url(script))); + parent->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); + + break; + + case CONTENT_MSG_ERROR: + NSLOG(netsurf, INFO, "script %s failed: %s", + nsurl_access(hlcache_handle_get_url(script)), + event->data.error); + /* fall through */ + + case CONTENT_MSG_ERRORCODE: + hlcache_handle_release(script); + s->data.handle = NULL; + parent->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); + content_add_error(&parent->base, "?", 0); + + break; + + default: + break; + } + + /* if there are no active fetches remaining begin post parse + * conversion + */ + if (html_can_begin_conversion(parent)) { + html_begin_conversion(parent); + } + + return NSERROR_OK; +} + +/** + * Callback for defer scripts + */ +static nserror +convert_script_defer_cb(hlcache_handle *script, + const hlcache_event *event, + void *pw) +{ + html_content *parent = pw; + unsigned int i; + struct html_script *s; + + /* Find script */ + for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) { + if (s->type == HTML_SCRIPT_DEFER && s->data.handle == script) + break; + } + + assert(i != parent->scripts_count); + + switch (event->type) { + + case CONTENT_MSG_DONE: + NSLOG(netsurf, INFO, "script %d done '%s'", i, + nsurl_access(hlcache_handle_get_url(script))); + parent->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); + + break; + + case CONTENT_MSG_ERROR: + NSLOG(netsurf, INFO, "script %s failed: %s", + nsurl_access(hlcache_handle_get_url(script)), + event->data.error); + /* fall through */ + + case CONTENT_MSG_ERRORCODE: + hlcache_handle_release(script); + s->data.handle = NULL; + parent->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); + content_add_error(&parent->base, "?", 0); + + break; + + default: + break; + } + + /* if there are no active fetches remaining begin post parse + * conversion + */ + if (html_can_begin_conversion(parent)) { + html_begin_conversion(parent); + } + + return NSERROR_OK; +} + +/** + * Callback for syncronous scripts + */ +static nserror +convert_script_sync_cb(hlcache_handle *script, + const hlcache_event *event, + void *pw) +{ + html_content *parent = pw; + unsigned int i; + struct html_script *s; + script_handler_t *script_handler; + dom_hubbub_error err; + + /* Find script */ + for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) { + if (s->type == HTML_SCRIPT_SYNC && s->data.handle == script) + break; + } + + assert(i != parent->scripts_count); + + switch (event->type) { + case CONTENT_MSG_DONE: + NSLOG(netsurf, INFO, "script %d done '%s'", i, + nsurl_access(hlcache_handle_get_url(script))); + parent->base.active--; + NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); + + s->already_started = true; + + /* attempt to execute script */ + script_handler = select_script_handler(content_get_type(s->data.handle)); + if (script_handler != NULL && parent->jscontext != NULL) { + /* script has a handler */ + const char *data; + unsigned long size; + data = content_get_source_data(s->data.handle, &size ); + script_handler(parent->jscontext, data, size); + } + + /* continue parse */ + err = dom_hubbub_parser_pause(parent->parser, false); + if (err != DOM_HUBBUB_OK) { + NSLOG(netsurf, INFO, "unpause returned 0x%x", err); + } + + break; + + case CONTENT_MSG_ERROR: + NSLOG(netsurf, INFO, "script %s failed: %s", + nsurl_access(hlcache_handle_get_url(script)), + event->data.error); + /* fall through */ + + case CONTENT_MSG_ERRORCODE: + hlcache_handle_release(script); + s->data.handle = NULL; + parent->base.active--; + + NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); + content_add_error(&parent->base, "?", 0); + + s->already_started = true; + + /* continue parse */ + err = dom_hubbub_parser_pause(parent->parser, false); + if (err != DOM_HUBBUB_OK) { + NSLOG(netsurf, INFO, "unpause returned 0x%x", err); + } + + break; + + default: + break; + } + + /* if there are no active fetches remaining begin post parse + * conversion + */ + if (html_can_begin_conversion(parent)) { + html_begin_conversion(parent); + } + + return NSERROR_OK; +} + +/** + * process a script with a src tag + */ +static dom_hubbub_error +exec_src_script(html_content *c, + dom_node *node, + dom_string *mimetype, + dom_string *src) +{ + nserror ns_error; + nsurl *joined; + hlcache_child_context child; + struct html_script *nscript; + bool async; + bool defer; + enum html_script_type script_type; + hlcache_handle_callback script_cb; + dom_hubbub_error ret = DOM_HUBBUB_OK; + dom_exception exc; /* returned by libdom functions */ + + /* src url */ + ns_error = nsurl_join(c->base_url, dom_string_data(src), &joined); + if (ns_error != NSERROR_OK) { + content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + return DOM_HUBBUB_NOMEM; + } + + NSLOG(netsurf, INFO, "script %i '%s'", c->scripts_count, + nsurl_access(joined)); + + /* there are three ways to process the script tag at this point: + * + * Syncronously pause the parent parse and continue after + * the script has downloaded and executed. (default) + * Async Start the script downloading and execute it when it + * becomes available. + * Defered Start the script downloading and execute it when + * the page has completed parsing, may be set along + * with async where it is ignored. + */ + + /* we interpret the presence of the async and defer attribute + * as true and ignore its value, technically only the empty + * value or the attribute name itself are valid. However + * various browsers interpret this in various ways the most + * compatible approach is to be liberal and accept any + * value. Note setting the values to "false" still makes them true! + */ + exc = dom_element_has_attribute(node, corestring_dom_async, &async); + if (exc != DOM_NO_ERR) { + return DOM_HUBBUB_OK; /* dom error */ + } + + if (async) { + /* asyncronous script */ + script_type = HTML_SCRIPT_ASYNC; + script_cb = convert_script_async_cb; + + } else { + exc = dom_element_has_attribute(node, + corestring_dom_defer, &defer); + if (exc != DOM_NO_ERR) { + return DOM_HUBBUB_OK; /* dom error */ + } + + if (defer) { + /* defered script */ + script_type = HTML_SCRIPT_DEFER; + script_cb = convert_script_defer_cb; + } else { + /* syncronous script */ + script_type = HTML_SCRIPT_SYNC; + script_cb = convert_script_sync_cb; + } + } + + nscript = html_process_new_script(c, mimetype, script_type); + if (nscript == NULL) { + nsurl_unref(joined); + content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + return DOM_HUBBUB_NOMEM; + } + + /* set up child fetch encoding and quirks */ + child.charset = c->encoding; + child.quirks = c->base.quirks; + + ns_error = hlcache_handle_retrieve(joined, + 0, + content_get_url(&c->base), + NULL, + script_cb, + c, + &child, + CONTENT_SCRIPT, + &nscript->data.handle); + + + nsurl_unref(joined); + + if (ns_error != NSERROR_OK) { + /* @todo Deal with fetch error better. currently assume + * fetch never became active + */ + /* mark duff script fetch as already started */ + nscript->already_started = true; + NSLOG(netsurf, INFO, "Fetch failed with error %d", ns_error); + } else { + /* update base content active fetch count */ + c->base.active++; + NSLOG(netsurf, INFO, "%d fetches active", c->base.active); + + switch (script_type) { + case HTML_SCRIPT_SYNC: + ret = DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED; + + case HTML_SCRIPT_ASYNC: + break; + + case HTML_SCRIPT_DEFER: + break; + + default: + assert(0); + } + } + + return ret; +} + +static dom_hubbub_error +exec_inline_script(html_content *c, dom_node *node, dom_string *mimetype) +{ + dom_string *script; + dom_exception exc; /* returned by libdom functions */ + struct lwc_string_s *lwcmimetype; + script_handler_t *script_handler; + struct html_script *nscript; + + /* does not appear to be a src so script is inline content */ + exc = dom_node_get_text_content(node, &script); + if ((exc != DOM_NO_ERR) || (script == NULL)) { + return DOM_HUBBUB_OK; /* no contents, skip */ + } + + nscript = html_process_new_script(c, mimetype, HTML_SCRIPT_INLINE); + if (nscript == NULL) { + dom_string_unref(script); + + content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + return DOM_HUBBUB_NOMEM; + + } + + nscript->data.string = script; + nscript->already_started = true; + + /* ensure script handler for content type */ + dom_string_intern(mimetype, &lwcmimetype); + script_handler = select_script_handler(content_factory_type_from_mime_type(lwcmimetype)); + lwc_string_unref(lwcmimetype); + + if (script_handler != NULL) { + script_handler(c->jscontext, + dom_string_data(script), + dom_string_byte_length(script)); + } + return DOM_HUBBUB_OK; +} + + +/** + * process script node parser callback + * + * + */ +dom_hubbub_error +html_process_script(void *ctx, dom_node *node) +{ + html_content *c = (html_content *)ctx; + dom_exception exc; /* returned by libdom functions */ + dom_string *src, *mimetype; + dom_hubbub_error err = DOM_HUBBUB_OK; + + /* ensure javascript context is available */ + /* We should only ever be here if scripting was enabled for this + * content so it's correct to make a javascript context if there + * isn't one already. */ + if (c->jscontext == NULL) { + union content_msg_data msg_data; + + msg_data.jscontext = &c->jscontext; + content_broadcast(&c->base, CONTENT_MSG_GETCTX, &msg_data); + NSLOG(netsurf, INFO, "javascript context %p ", c->jscontext); + if (c->jscontext == NULL) { + /* no context and it could not be created, abort */ + return DOM_HUBBUB_OK; + } + } + + NSLOG(netsurf, INFO, "content %p parser %p node %p", c, c->parser, + node); + + exc = dom_element_get_attribute(node, corestring_dom_type, &mimetype); + if (exc != DOM_NO_ERR || mimetype == NULL) { + mimetype = dom_string_ref(corestring_dom_text_javascript); + } + + exc = dom_element_get_attribute(node, corestring_dom_src, &src); + if (exc != DOM_NO_ERR || src == NULL) { + err = exec_inline_script(c, node, mimetype); + } else { + err = exec_src_script(c, node, mimetype, src); + dom_string_unref(src); + } + + dom_string_unref(mimetype); + + return err; +} + +/* exported internal interface documented in html/html_internal.h */ +nserror html_script_free(html_content *html) +{ + unsigned int i; + + for (i = 0; i != html->scripts_count; i++) { + if (html->scripts[i].mimetype != NULL) { + dom_string_unref(html->scripts[i].mimetype); + } + + if ((html->scripts[i].type == HTML_SCRIPT_INLINE) && + (html->scripts[i].data.string != NULL)) { + + dom_string_unref(html->scripts[i].data.string); + + } else if ((html->scripts[i].type == HTML_SCRIPT_SYNC) && + (html->scripts[i].data.handle != NULL)) { + + hlcache_handle_release(html->scripts[i].data.handle); + + } + } + free(html->scripts); + + return NSERROR_OK; +} + +/* exported internal interface documented in html/html_internal.h */ +nserror html_script_invalidate_ctx(html_content *htmlc) +{ + htmlc->jscontext = NULL; + return NSERROR_OK; +} diff --git a/content/handlers/html/imagemap.c b/content/handlers/html/imagemap.c new file mode 100644 index 000000000..5f4dd54b3 --- /dev/null +++ b/content/handlers/html/imagemap.c @@ -0,0 +1,804 @@ +/* + * Copyright 2004 John M Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Implementation of HTML image maps + * + * \todo should this should use the general hashmap instead of its own + */ + +#include +#include +#include +#include + +#include + +#include "utils/log.h" +#include "utils/corestrings.h" +#include "content/content_protected.h" +#include "content/hlcache.h" + +#include "html/box.h" +#include "html/html_internal.h" +#include "html/imagemap.h" + +#define HASH_SIZE 31 /* fixed size hash table */ + +typedef enum { + IMAGEMAP_DEFAULT, + IMAGEMAP_RECT, + IMAGEMAP_CIRCLE, + IMAGEMAP_POLY +} imagemap_entry_type; + +struct mapentry { + imagemap_entry_type type; /**< type of shape */ + nsurl *url; /**< absolute url to go to */ + char *target; /**< target frame (if any) */ + union { + struct { + int x; /**< x coordinate of centre */ + int y; /**< y coordinate of center */ + int r; /**< radius of circle */ + } circle; + struct { + int x0; /**< left hand edge */ + int y0; /**< top edge */ + int x1; /**< right hand edge */ + int y1; /**< bottom edge */ + } rect; + struct { + int num; /**< number of points */ + float *xcoords; /**< x coordinates */ + float *ycoords; /**< y coordinates */ + } poly; + } bounds; + struct mapentry *next; /**< next entry in list */ +}; + +struct imagemap { + char *key; /**< key for this entry */ + struct mapentry *list; /**< pointer to linked list of entries */ + struct imagemap *next; /**< next entry in this hash chain */ +}; + +/** + * Create hashtable of imagemaps + * + * \param c The containing content + * \return true on success, false otherwise + */ +static bool imagemap_create(html_content *c) +{ + assert(c != NULL); + + if (c->imagemaps == NULL) { + c->imagemaps = calloc(HASH_SIZE, sizeof(struct imagemap *)); + if (c->imagemaps == NULL) { + return false; + } + } + + return true; +} + +/** + * Hash function. + * + * \param key The key to hash. + * \return The hashed value. + */ +static unsigned int imagemap_hash(const char *key) +{ + unsigned int z = 0; + + if (key == 0) return 0; + + for (; *key != 0; key++) { + z += *key & 0x1f; + } + + return (z % (HASH_SIZE - 1)) + 1; +} + +/** + * Add an imagemap to the hashtable, creating it if it doesn't exist + * + * \param c The containing content + * \param key The name of the imagemap + * \param list List of map regions + * \return true on succes, false otherwise + */ +static bool +imagemap_add(html_content *c, dom_string *key, struct mapentry *list) +{ + struct imagemap *map; + unsigned int slot; + + assert(c != NULL); + assert(key != NULL); + assert(list != NULL); + + if (imagemap_create(c) == false) + return false; + + map = calloc(1, sizeof(*map)); + if (map == NULL) + return false; + + /* \todo Stop relying on NULL termination of dom_string */ + map->key = strdup(dom_string_data(key)); + if (map->key == NULL) { + free(map); + return false; + } + + map->list = list; + + slot = imagemap_hash(map->key); + + map->next = c->imagemaps[slot]; + c->imagemaps[slot] = map; + + return true; +} + +/** + * Free list of imagemap entries + * + * \param list Pointer to head of list + */ +static void imagemap_freelist(struct mapentry *list) +{ + struct mapentry *entry, *prev; + + assert(list != NULL); + + entry = list; + + while (entry != NULL) { + prev = entry; + + nsurl_unref(entry->url); + + if (entry->target) + free(entry->target); + + if (entry->type == IMAGEMAP_POLY) { + free(entry->bounds.poly.xcoords); + free(entry->bounds.poly.ycoords); + } + + entry = entry->next; + free(prev); + } +} + +/** + * Destroy hashtable of imagemaps + * + * \param c The containing content + */ +void imagemap_destroy(html_content *c) +{ + unsigned int i; + + assert(c != NULL); + + /* no imagemaps -> return */ + if (c->imagemaps == NULL) + return; + + for (i = 0; i != HASH_SIZE; i++) { + struct imagemap *map, *next; + + map = c->imagemaps[i]; + while (map != NULL) { + next = map->next; + imagemap_freelist(map->list); + free(map->key); + free(map); + map = next; + } + } + + free(c->imagemaps); +} + +/** + * Dump imagemap data to the log + * + * \param c The containing content + */ +void imagemap_dump(html_content *c) +{ + unsigned int i; + + int j; + + assert(c != NULL); + + if (c->imagemaps == NULL) + return; + + for (i = 0; i != HASH_SIZE; i++) { + struct imagemap *map; + struct mapentry *entry; + + map = c->imagemaps[i]; + while (map != NULL) { + NSLOG(netsurf, INFO, "Imagemap: %s", map->key); + + for (entry = map->list; entry; entry = entry->next) { + switch (entry->type) { + case IMAGEMAP_DEFAULT: + NSLOG(netsurf, INFO, "\tDefault: %s", + nsurl_access(entry->url)); + break; + case IMAGEMAP_RECT: + NSLOG(netsurf, INFO, + "\tRectangle: %s: [(%d,%d),(%d,%d)]", + nsurl_access(entry->url), + entry->bounds.rect.x0, + entry->bounds.rect.y0, + entry->bounds.rect.x1, + entry->bounds.rect.y1); + break; + case IMAGEMAP_CIRCLE: + NSLOG(netsurf, INFO, + "\tCircle: %s: [(%d,%d),%d]", + nsurl_access(entry->url), + entry->bounds.circle.x, + entry->bounds.circle.y, + entry->bounds.circle.r); + break; + case IMAGEMAP_POLY: + NSLOG(netsurf, INFO, + "\tPolygon: %s:", + nsurl_access(entry->url)); + for (j = 0; j != entry->bounds.poly.num; + j++) { + fprintf(stderr, "(%d,%d) ", + (int)entry->bounds.poly.xcoords[j], + (int)entry->bounds.poly.ycoords[j]); + } + fprintf(stderr,"\n"); + break; + } + } + map = map->next; + } + } +} + +/** + * Adds an imagemap entry to the list + * + * \param c The html content that the imagemap belongs to + * \param n The xmlNode representing the entry to add + * \param base_url Base URL for resolving relative URLs + * \param entry Pointer to list of entries + * \param tagtype The type of tag + * \return false on memory exhaustion, true otherwise + */ +static bool +imagemap_addtolist(const struct html_content *c, dom_node *n, nsurl *base_url, + struct mapentry **entry, dom_string *tagtype) +{ + dom_exception exc; + dom_string *href = NULL, *target = NULL, *shape = NULL; + dom_string *coords = NULL; + struct mapentry *new_map, *temp; + bool ret = true; + + if (dom_string_caseless_isequal(tagtype, corestring_dom_area)) { + bool nohref = false; + exc = dom_element_has_attribute(n, + corestring_dom_nohref, &nohref); + if ((exc != DOM_NO_ERR) || nohref) + /* Skip */ + goto ok_out; + } + + exc = dom_element_get_attribute(n, corestring_dom_href, &href); + if (exc != DOM_NO_ERR || href == NULL) { + /* No href="" attribute, skip this element */ + goto ok_out; + } + + exc = dom_element_get_attribute(n, corestring_dom_target, &target); + if (exc != DOM_NO_ERR) { + goto ok_out; + } + + exc = dom_element_get_attribute(n, corestring_dom_shape, &shape); + if (exc != DOM_NO_ERR) { + goto ok_out; + } + + /* If there's no shape, we default to rectangles */ + if (shape == NULL) + shape = dom_string_ref(corestring_dom_rect); + + if (!dom_string_caseless_lwc_isequal(shape, corestring_lwc_default)) { + /* If not 'default' and there's no 'coords' give up */ + exc = dom_element_get_attribute(n, corestring_dom_coords, + &coords); + if (exc != DOM_NO_ERR || coords == NULL) { + goto ok_out; + } + } + + new_map = calloc(1, sizeof(*new_map)); + if (new_map == NULL) { + goto bad_out; + } + + if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_rect) || + dom_string_caseless_lwc_isequal(shape, corestring_lwc_rectangle)) + new_map->type = IMAGEMAP_RECT; + else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_circle)) + new_map->type = IMAGEMAP_CIRCLE; + else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_poly) || + dom_string_caseless_lwc_isequal(shape, corestring_lwc_polygon)) + new_map->type = IMAGEMAP_POLY; + else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_default)) + new_map->type = IMAGEMAP_DEFAULT; + else + goto bad_out; + + if (box_extract_link(c, href, base_url, &new_map->url) == false) + goto bad_out; + + if (new_map->url == NULL) { + /* non-fatal error -> ignore this */ + goto ok_free_map_out; + } + + if (target != NULL) { + /* Copy target into the map */ + new_map->target = malloc(dom_string_byte_length(target) + 1); + if (new_map->target == NULL) + goto bad_out; + /* Safe, but relies on dom_strings being NULL terminated */ + /* \todo Do this better */ + strcpy(new_map->target, dom_string_data(target)); + } + + if (new_map->type != IMAGEMAP_DEFAULT) { + int x, y; + float *xcoords, *ycoords; + /* coordinates are a comma-separated list of values */ + char *val = strtok((char *)dom_string_data(coords), ","); + int num = 1; + + switch (new_map->type) { + case IMAGEMAP_RECT: + /* (left, top, right, bottom) */ + while (val != NULL && num <= 4) { + switch (num) { + case 1: + new_map->bounds.rect.x0 = atoi(val); + break; + case 2: + new_map->bounds.rect.y0 = atoi(val); + break; + case 3: + new_map->bounds.rect.x1 = atoi(val); + break; + case 4: + new_map->bounds.rect.y1 = atoi(val); + break; + } + + num++; + val = strtok(NULL, ","); + } + break; + case IMAGEMAP_CIRCLE: + /* (x, y, radius ) */ + while (val != NULL && num <= 3) { + switch (num) { + case 1: + new_map->bounds.circle.x = atoi(val); + break; + case 2: + new_map->bounds.circle.y = atoi(val); + break; + case 3: + new_map->bounds.circle.r = atoi(val); + break; + } + + num++; + val = strtok(NULL, ","); + } + break; + case IMAGEMAP_POLY: + new_map->bounds.poly.xcoords = NULL; + new_map->bounds.poly.ycoords = NULL; + + while (val != NULL) { + x = atoi(val); + + val = strtok(NULL, ","); + if (val == NULL) + break; + + y = atoi(val); + + xcoords = realloc(new_map->bounds.poly.xcoords, + num * sizeof(float)); + if (xcoords == NULL) { + goto bad_out; + } + new_map->bounds.poly.xcoords = xcoords; + + ycoords = realloc(new_map->bounds.poly.ycoords, + num * sizeof(float)); + if (ycoords == NULL) { + goto bad_out; + } + new_map->bounds.poly.ycoords = ycoords; + + new_map->bounds.poly.xcoords[num - 1] = x; + new_map->bounds.poly.ycoords[num - 1] = y; + + num++; + val = strtok(NULL, ","); + } + + new_map->bounds.poly.num = num - 1; + + break; + default: + break; + } + } + + new_map->next = NULL; + + if (*entry) { + /* add to END of list */ + for (temp = (*entry); temp->next != NULL; temp = temp->next) + ; + temp->next = new_map; + } else { + (*entry) = new_map; + } + + /* All good, linked in, let's clean up */ + goto ok_out; + +bad_out: + ret = false; +ok_free_map_out: + if (new_map != NULL) { + if (new_map->url != NULL) + nsurl_unref(new_map->url); + if (new_map->type == IMAGEMAP_POLY && + new_map->bounds.poly.ycoords != NULL) + free(new_map->bounds.poly.ycoords); + if (new_map->type == IMAGEMAP_POLY && + new_map->bounds.poly.xcoords != NULL) + free(new_map->bounds.poly.xcoords); + if (new_map->target != NULL) + free(new_map->target); + + free(new_map); + } +ok_out: + if (href != NULL) + dom_string_unref(href); + if (target != NULL) + dom_string_unref(target); + if (shape != NULL) + dom_string_unref(shape); + if (coords != NULL) + dom_string_unref(coords); + + return ret; +} + +/** + * Extract an imagemap from html source + * + * \param node XML node containing map + * \param c Content containing document + * \param entry List of map entries + * \param tname The sub-tags to consider on this pass + * \return false on memory exhaustion, true otherwise + */ +static bool +imagemap_extract_map_entries(dom_node *node, html_content *c, + struct mapentry **entry, dom_string *tname) +{ + dom_nodelist *nlist; + dom_exception exc; + unsigned long ent; + uint32_t tag_count; + + exc = dom_element_get_elements_by_tag_name(node, tname, &nlist); + if (exc != DOM_NO_ERR) { + return false; + } + + exc = dom_nodelist_get_length(nlist, &tag_count); + if (exc != DOM_NO_ERR) { + dom_nodelist_unref(nlist); + return false; + } + + for (ent = 0; ent < tag_count; ++ent) { + dom_node *subnode; + + exc = dom_nodelist_item(nlist, ent, &subnode); + if (exc != DOM_NO_ERR) { + dom_nodelist_unref(nlist); + return false; + } + if (imagemap_addtolist(c, subnode, c->base_url, + entry, tname) == false) { + dom_node_unref(subnode); + dom_nodelist_unref(nlist); + return false; + } + dom_node_unref(subnode); + } + + dom_nodelist_unref(nlist); + + return true; +} + +/** + * Extract an imagemap from html source + * + * \param node XML node containing map + * \param c Content containing document + * \param entry List of map entries + * \return false on memory exhaustion, true otherwise + */ +static bool imagemap_extract_map(dom_node *node, html_content *c, + struct mapentry **entry) +{ + if (imagemap_extract_map_entries(node, c, entry, + corestring_dom_area) == false) + return false; + return imagemap_extract_map_entries(node, c, entry, + corestring_dom_a); +} + +/** + * Extract all imagemaps from a document tree + * + * \param c The content to extract imagemaps from. + * \return false on memory exhaustion, true otherwise + */ +nserror +imagemap_extract(html_content *c) +{ + dom_nodelist *nlist; + dom_exception exc; + unsigned long mapnr; + uint32_t maybe_maps; + nserror ret = NSERROR_OK; + + exc = dom_document_get_elements_by_tag_name(c->document, + corestring_dom_map, + &nlist); + if (exc != DOM_NO_ERR) { + return NSERROR_DOM; + } + + exc = dom_nodelist_get_length(nlist, &maybe_maps); + if (exc != DOM_NO_ERR) { + ret = NSERROR_DOM; + goto out_nlist; + } + + for (mapnr = 0; mapnr < maybe_maps; ++mapnr) { + dom_node *node; + dom_string *name; + exc = dom_nodelist_item(nlist, mapnr, &node); + if (exc != DOM_NO_ERR) { + ret = NSERROR_DOM; + goto out_nlist; + } + + exc = dom_element_get_attribute(node, corestring_dom_id, + &name); + if (exc != DOM_NO_ERR) { + dom_node_unref(node); + ret = NSERROR_DOM; + goto out_nlist; + } + + if (name == NULL) { + exc = dom_element_get_attribute(node, + corestring_dom_name, + &name); + if (exc != DOM_NO_ERR) { + dom_node_unref(node); + ret = NSERROR_DOM; + goto out_nlist; + } + } + + if (name != NULL) { + struct mapentry *entry = NULL; + if (imagemap_extract_map(node, c, &entry) == false) { + if (entry != NULL) { + imagemap_freelist(entry); + } + + dom_string_unref(name); + dom_node_unref(node); + ret = NSERROR_NOMEM; /** @todo check this */ + goto out_nlist; + } + + /* imagemap_extract_map may not extract anything, + * so entry can still be NULL here. This isn't an + * error as it just means that we've encountered + * an incorrectly defined ... block + */ + if ((entry != NULL) && + (imagemap_add(c, name, entry) == false)) { + imagemap_freelist(entry); + + dom_string_unref(name); + dom_node_unref(node); + ret = NSERROR_NOMEM; /** @todo check this */ + goto out_nlist; + } + } + + dom_string_unref(name); + dom_node_unref(node); + } + +out_nlist: + + dom_nodelist_unref(nlist); + + return ret; +} + +/** + * Test if a point lies within an arbitrary polygon + * Modified from comp.graphics.algorithms FAQ 2.03 + * + * \param num Number of vertices + * \param xpt Array of x coordinates + * \param ypt Array of y coordinates + * \param x Left hand edge of containing box + * \param y Top edge of containing box + * \param click_x X coordinate of click + * \param click_y Y coordinate of click + * \return 1 if point is in polygon, 0 if outside. 0 or 1 if on boundary + */ +static int +imagemap_point_in_poly(int num, float *xpt, float *ypt, unsigned long x, + unsigned long y, unsigned long click_x, unsigned long click_y) +{ + int i, j, c = 0; + + assert(xpt != NULL); + assert(ypt != NULL); + + for (i = 0, j = num - 1; i < num; j = i++) { + if ((((ypt[i] + y <= click_y) && (click_y < ypt[j] + y)) || + ((ypt[j] + y <= click_y) && (click_y < ypt[i] + y))) && + (click_x < (xpt[j] - xpt[i]) * + (click_y - (ypt[i] + y)) / (ypt[j] - ypt[i]) + xpt[i] + x)) + c = !c; + } + + return c; +} + +/** + * Retrieve url associated with imagemap entry + * + * \param c The containing content + * \param key The map name to search for + * \param x The left edge of the containing box + * \param y The top edge of the containing box + * \param click_x The horizontal location of the click + * \param click_y The vertical location of the click + * \param target Pointer to location to receive target pointer (if any) + * \return The url associated with this area, or NULL if not found + */ +nsurl *imagemap_get(struct html_content *c, const char *key, + unsigned long x, unsigned long y, + unsigned long click_x, unsigned long click_y, + const char **target) +{ + unsigned int slot = 0; + struct imagemap *map; + struct mapentry *entry; + unsigned long cx, cy; + + assert(c != NULL); + + if (key == NULL) + return NULL; + + if (c->imagemaps == NULL) + return NULL; + + slot = imagemap_hash(key); + + for (map = c->imagemaps[slot]; map != NULL; map = map->next) { + if (map->key != NULL && strcasecmp(map->key, key) == 0) + break; + } + + if (map == NULL || map->list == NULL) + return NULL; + + for (entry = map->list; entry; entry = entry->next) { + switch (entry->type) { + case IMAGEMAP_DEFAULT: + /* just return the URL. no checks required */ + if (target) + *target = entry->target; + return entry->url; + break; + case IMAGEMAP_RECT: + if (click_x >= x + entry->bounds.rect.x0 && + click_x <= x + entry->bounds.rect.x1 && + click_y >= y + entry->bounds.rect.y0 && + click_y <= y + entry->bounds.rect.y1) { + if (target) + *target = entry->target; + return entry->url; + } + break; + case IMAGEMAP_CIRCLE: + cx = x + entry->bounds.circle.x - click_x; + cy = y + entry->bounds.circle.y - click_y; + if ((cx * cx + cy * cy) <= + (unsigned long) (entry->bounds.circle.r * + entry->bounds.circle.r)) { + if (target) + *target = entry->target; + return entry->url; + } + break; + case IMAGEMAP_POLY: + if (imagemap_point_in_poly(entry->bounds.poly.num, + entry->bounds.poly.xcoords, + entry->bounds.poly.ycoords, x, y, + click_x, click_y)) { + if (target) + *target = entry->target; + return entry->url; + } + break; + } + } + + if (target) + *target = NULL; + + return NULL; +} diff --git a/content/handlers/html/imagemap.h b/content/handlers/html/imagemap.h new file mode 100644 index 000000000..8e189a072 --- /dev/null +++ b/content/handlers/html/imagemap.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004 John M Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Interface to HTML imagemap + */ + +#ifndef NETSURF_HTML_IMAGEMAP_H +#define NETSURF_HTML_IMAGEMAP_H + +#include + +struct html_content; +struct hlcache_handle; +struct nsurl; + +void imagemap_destroy(struct html_content *c); +void imagemap_dump(struct html_content *c); +nserror imagemap_extract(struct html_content *c); + +struct nsurl *imagemap_get(struct html_content *c, const char *key, + unsigned long x, unsigned long y, + unsigned long click_x, unsigned long click_y, + const char **target); + +#endif diff --git a/content/handlers/html/layout.c b/content/handlers/html/layout.c new file mode 100644 index 000000000..6941d6759 --- /dev/null +++ b/content/handlers/html/layout.c @@ -0,0 +1,5432 @@ +/* + * Copyright 2005 Richard Wilson + * Copyright 2006 James Bursa + * Copyright 2008 Michael Drake + * Copyright 2003 Phil Mellor + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * HTML layout implementation. + * + * Layout is carried out in two stages: + * + * 1. + calculation of minimum / maximum box widths, and + * + determination of whether block level boxes will have >zero height + * + * 2. + layout (position and dimensions) + * + * In most cases the functions for the two stages are a corresponding pair + * layout_minmax_X() and layout_X(). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils/log.h" +#include "utils/talloc.h" +#include "utils/utils.h" +#include "utils/nsoption.h" +#include "netsurf/inttypes.h" +#include "netsurf/content.h" +#include "netsurf/browser_window.h" +#include "netsurf/layout.h" +#include "content/content_protected.h" +#include "css/utils.h" +#include "desktop/scrollbar.h" +#include "desktop/textarea.h" + +#include "html/box.h" +#include "html/font.h" +#include "html/form_internal.h" +#include "html/html_internal.h" +#include "html/layout.h" +#include "html/table.h" + +#define AUTO INT_MIN + +/* Fixed point percentage (a) of an integer (b), to an integer */ +#define FPCT_OF_INT_TOINT(a, b) (FIXTOINT(FDIV((a * b), F_100))) + +typedef uint8_t (*css_len_func)( + const css_computed_style *style, + css_fixed *length, css_unit *unit); +typedef uint8_t (*css_border_style_func)( + const css_computed_style *style); +typedef uint8_t (*css_border_color_func)( + const css_computed_style *style, + css_color *color); + +/** Array of per-side access functions for computed style margins. */ +static const css_len_func margin_funcs[4] = { + [TOP] = css_computed_margin_top, + [RIGHT] = css_computed_margin_right, + [BOTTOM] = css_computed_margin_bottom, + [LEFT] = css_computed_margin_left, +}; + +/** Array of per-side access functions for computed style paddings. */ +static const css_len_func padding_funcs[4] = { + [TOP] = css_computed_padding_top, + [RIGHT] = css_computed_padding_right, + [BOTTOM] = css_computed_padding_bottom, + [LEFT] = css_computed_padding_left, +}; + +/** Array of per-side access functions for computed style border_widths. */ +static const css_len_func border_width_funcs[4] = { + [TOP] = css_computed_border_top_width, + [RIGHT] = css_computed_border_right_width, + [BOTTOM] = css_computed_border_bottom_width, + [LEFT] = css_computed_border_left_width, +}; + +/** Array of per-side access functions for computed style border styles. */ +static const css_border_style_func border_style_funcs[4] = { + [TOP] = css_computed_border_top_style, + [RIGHT] = css_computed_border_right_style, + [BOTTOM] = css_computed_border_bottom_style, + [LEFT] = css_computed_border_left_style, +}; + +/** Array of per-side access functions for computed style border colors. */ +static const css_border_color_func border_color_funcs[4] = { + [TOP] = css_computed_border_top_color, + [RIGHT] = css_computed_border_right_color, + [BOTTOM] = css_computed_border_bottom_color, + [LEFT] = css_computed_border_left_color, +}; + +/* forward declaration to break cycles */ +static bool layout_block_context( + struct box *block, + int viewport_height, + html_content *content); +static void layout_minmax_block( + struct box *block, + const struct gui_layout_table *font_func, + const html_content *content); + + +/** + * Compute the size of replaced boxes with auto dimensions, according to + * content. + * + * \param box Box with object + * \param width Width value in px or AUTO. If AUTO, updated to value in px. + * \param height Height value in px or AUTO. If AUTO, updated to value in px. + * \param min_width Box's min width, as given by layout_find_dimensions. + * \param max_width Box's max width, as given by layout_find_dimensions. + * \param min_height Box's min height, as given by layout_find_dimensions. + * \param max_height Box's max height, as given by layout_find_dimensions. + * + * See CSS 2.1 sections 10.3 and 10.6. + */ +static void +layout_get_object_dimensions(struct box *box, + int *width, int *height, + int min_width, int max_width, + int min_height, int max_height) +{ + assert(box->object != NULL); + assert(width != NULL && height != NULL); + + if (*width == AUTO && *height == AUTO) { + /* No given dimensions */ + + bool scaled = false; + int intrinsic_width = content_get_width(box->object); + int intrinsic_height = content_get_height(box->object); + + /* use intrinsic dimensions */ + *width = intrinsic_width; + *height = intrinsic_height; + + /* Deal with min/max-width first */ + if (min_width > 0 && min_width > *width) { + *width = min_width; + scaled = true; + } + if (max_width >= 0 && max_width < *width) { + *width = max_width; + scaled = true; + } + + if (scaled && (intrinsic_width != 0)) { + /* Update height */ + *height = (*width * intrinsic_height) / + intrinsic_width; + } + + scaled = false; + /* Deal with min/max-height */ + if (min_height > 0 && min_height > *height) { + *height = min_height; + scaled = true; + } + if (max_height >= 0 && max_height < *height) { + *height = max_height; + scaled = true; + } + + if (scaled && (intrinsic_height != 0)) { + /* Update width */ + *width = (*height * intrinsic_width) / + intrinsic_height; + } + + } else if (*width == AUTO) { + /* Have given height; width is calculated from the given height + * and ratio of intrinsic dimensions */ + int intrinsic_width = content_get_width(box->object); + int intrinsic_height = content_get_height(box->object); + + if (intrinsic_height != 0) + *width = (*height * intrinsic_width) / + intrinsic_height; + else + *width = intrinsic_width; + + if (min_width > 0 && min_width > *width) + *width = min_width; + if (max_width >= 0 && max_width < *width) + *width = max_width; + + } else if (*height == AUTO) { + /* Have given width; height is calculated from the given width + * and ratio of intrinsic dimensions */ + int intrinsic_width = content_get_width(box->object); + int intrinsic_height = content_get_height(box->object); + + if (intrinsic_width != 0) + *height = (*width * intrinsic_height) / + intrinsic_width; + else + *height = intrinsic_height; + } +} + + +/** + * Calculate the text-indent length. + * + * \param style style of block + * \param width width of containing block + * \return length of indent + */ +static int layout_text_indent( + const nscss_len_ctx *len_ctx, + const css_computed_style *style, int width) +{ + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + css_computed_text_indent(style, &value, &unit); + + if (unit == CSS_UNIT_PCT) { + return FPCT_OF_INT_TOINT(value, width); + } else { + return FIXTOINT(nscss_len2px(len_ctx, value, unit, style)); + } +} + + +/** + * Determine width of margin, borders, and padding on one side of a box. + * + * \param len_ctx CSS length conversion context for document + * \param style style to measure + * \param side side of box to measure + * \param margin whether margin width is required + * \param border whether border width is required + * \param padding whether padding width is required + * \param fixed increased by sum of fixed margin, border, and padding + * \param frac increased by sum of fractional margin and padding + */ +static void +calculate_mbp_width(const nscss_len_ctx *len_ctx, + const css_computed_style *style, + unsigned int side, + bool margin, + bool border, + bool padding, + int *fixed, + float *frac) +{ + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(style); + + /* margin */ + if (margin) { + enum css_margin_e type; + + type = margin_funcs[side](style, &value, &unit); + if (type == CSS_MARGIN_SET) { + if (unit == CSS_UNIT_PCT) { + *frac += FIXTOINT(FDIV(value, F_100)); + } else { + *fixed += FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } + } + + /* border */ + if (border) { + if (border_style_funcs[side](style) != + CSS_BORDER_STYLE_NONE) { + border_width_funcs[side](style, &value, &unit); + + *fixed += FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } + + /* padding */ + if (padding) { + padding_funcs[side](style, &value, &unit); + if (unit == CSS_UNIT_PCT) { + *frac += FIXTOINT(FDIV(value, F_100)); + } else { + *fixed += FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } +} + + +/** + * Calculate minimum and maximum width of a table. + * + * \param table box of type TABLE + * \param font_func Font functions + * \param content The HTML content we are laying out. + * \post table->min_width and table->max_width filled in, + * 0 <= table->min_width <= table->max_width + */ +static void layout_minmax_table(struct box *table, + const struct gui_layout_table *font_func, + const html_content *content) +{ + unsigned int i, j; + int border_spacing_h = 0; + int table_min = 0, table_max = 0; + int extra_fixed = 0; + float extra_frac = 0; + struct column *col; + struct box *row_group, *row, *cell; + enum css_width_e wtype; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + /* check if the widths have already been calculated */ + if (table->max_width != UNKNOWN_MAX_WIDTH) + return; + + if (table_calculate_column_types(&content->len_ctx, table) == false) { + NSLOG(netsurf, WARNING, + "Could not establish table column types."); + return; + } + col = table->col; + + /* start with 0 except for fixed-width columns */ + for (i = 0; i != table->columns; i++) { + if (col[i].type == COLUMN_WIDTH_FIXED) + col[i].min = col[i].max = col[i].width; + else + col[i].min = col[i].max = 0; + } + + /* border-spacing is used in the separated borders model */ + if (css_computed_border_collapse(table->style) == + CSS_BORDER_COLLAPSE_SEPARATE) { + css_fixed h = 0, v = 0; + css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; + + css_computed_border_spacing(table->style, &h, &hu, &v, &vu); + + border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx, + h, hu, table->style)); + } + + /* 1st pass: consider cells with colspan 1 only */ + for (row_group = table->children; row_group; row_group =row_group->next) + for (row = row_group->children; row; row = row->next) + for (cell = row->children; cell; cell = cell->next) { + assert(cell->type == BOX_TABLE_CELL); + assert(cell->style); + /** TODO: Handle colspan="0" correctly. + * It's currently converted to 1 in box normaisation */ + assert(cell->columns != 0); + + if (cell->columns != 1) + continue; + + layout_minmax_block(cell, font_func, content); + i = cell->start_column; + + if (col[i].positioned) + continue; + + /* update column min, max widths using cell widths */ + if (col[i].min < cell->min_width) + col[i].min = cell->min_width; + if (col[i].max < cell->max_width) + col[i].max = cell->max_width; + } + + /* 2nd pass: cells which span multiple columns */ + for (row_group = table->children; row_group; row_group =row_group->next) + for (row = row_group->children; row; row = row->next) + for (cell = row->children; cell; cell = cell->next) { + unsigned int flexible_columns = 0; + int min = 0, max = 0, fixed_width = 0, extra; + + if (cell->columns == 1) + continue; + + layout_minmax_block(cell, font_func, content); + i = cell->start_column; + + /* find min width so far of spanned columns, and count + * number of non-fixed spanned columns and total fixed width */ + for (j = 0; j != cell->columns; j++) { + min += col[i + j].min; + if (col[i + j].type == COLUMN_WIDTH_FIXED) + fixed_width += col[i + j].width; + else + flexible_columns++; + } + min += (cell->columns - 1) * border_spacing_h; + + /* distribute extra min to spanned columns */ + if (min < cell->min_width) { + if (flexible_columns == 0) { + extra = 1 + (cell->min_width - min) / + cell->columns; + for (j = 0; j != cell->columns; j++) { + col[i + j].min += extra; + if (col[i + j].max < col[i + j].min) + col[i + j].max = col[i + j].min; + } + } else { + extra = 1 + (cell->min_width - min) / + flexible_columns; + for (j = 0; j != cell->columns; j++) { + if (col[i + j].type != + COLUMN_WIDTH_FIXED) { + col[i + j].min += extra; + if (col[i + j].max < + col[i + j].min) + col[i + j].max = + col[i + j].min; + } + } + } + } + + /* find max width so far of spanned columns */ + for (j = 0; j != cell->columns; j++) + max += col[i + j].max; + max += (cell->columns - 1) * border_spacing_h; + + /* distribute extra max to spanned columns */ + if (max < cell->max_width && flexible_columns) { + extra = 1 + (cell->max_width - max) / flexible_columns; + for (j = 0; j != cell->columns; j++) + if (col[i + j].type != COLUMN_WIDTH_FIXED) + col[i + j].max += extra; + } + } + + for (i = 0; i != table->columns; i++) { + if (col[i].max < col[i].min) { + box_dump(stderr, table, 0, true); + assert(0); + } + table_min += col[i].min; + table_max += col[i].max; + } + + /* fixed width takes priority, unless it is too narrow */ + wtype = css_computed_width(table->style, &value, &unit); + if (wtype == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) { + int width = FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, table->style)); + if (table_min < width) + table_min = width; + if (table_max < width) + table_max = width; + } + + /* add margins, border, padding to min, max widths */ + calculate_mbp_width(&content->len_ctx, + table->style, LEFT, true, true, true, + &extra_fixed, &extra_frac); + calculate_mbp_width(&content->len_ctx, + table->style, RIGHT, true, true, true, + &extra_fixed, &extra_frac); + if (extra_fixed < 0) + extra_fixed = 0; + if (extra_frac < 0) + extra_frac = 0; + if (1.0 <= extra_frac) + extra_frac = 0.9; + table->min_width = (table_min + extra_fixed) / (1.0 - extra_frac); + table->max_width = (table_max + extra_fixed) / (1.0 - extra_frac); + table->min_width += (table->columns + 1) * border_spacing_h; + table->max_width += (table->columns + 1) * border_spacing_h; + + assert(0 <= table->min_width && table->min_width <= table->max_width); +} + +/** + * Helper to check if a box has percentage max width. + * + * \param[in] b Box to check. + * \return true iff box has percnetage max width. + */ +static inline bool box_has_percentage_max_width(struct box *b) +{ + css_unit unit = CSS_UNIT_PX; + enum css_max_width_e type; + css_fixed value = 0; + + assert(b != NULL); + + type = css_computed_max_width(b->style, &value, &unit); + return ((type == CSS_MAX_WIDTH_SET) && (unit == CSS_UNIT_PCT)); +} + +/** + * Calculate minimum and maximum width of a line. + * + * \param first a box in an inline container + * \param line_min updated to minimum width of line starting at first + * \param line_max updated to maximum width of line starting at first + * \param first_line true iff this is the first line in the inline container + * \param line_has_height updated to true or false, depending on line + * \param font_func Font functions. + * \return first box in next line, or 0 if no more lines + * \post 0 <= *line_min <= *line_max + */ +static struct box * +layout_minmax_line(struct box *first, + int *line_min, + int *line_max, + bool first_line, + bool *line_has_height, + const struct gui_layout_table *font_func, + const html_content *content) +{ + int min = 0, max = 0, width, height, fixed; + float frac; + size_t i, j; + struct box *b; + struct box *block; + plot_font_style_t fstyle; + bool no_wrap; + + assert(first->parent); + assert(first->parent->parent); + assert(first->parent->parent->style); + + block = first->parent->parent; + no_wrap = (css_computed_white_space(block->style) == + CSS_WHITE_SPACE_NOWRAP || + css_computed_white_space(block->style) == + CSS_WHITE_SPACE_PRE); + + *line_has_height = false; + + /* corresponds to the pass 1 loop in layout_line() */ + for (b = first; b; b = b->next) { + enum css_width_e wtype; + enum css_height_e htype; + enum css_box_sizing_e bs; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(b->type == BOX_INLINE || b->type == BOX_INLINE_BLOCK || + b->type == BOX_FLOAT_LEFT || + b->type == BOX_FLOAT_RIGHT || + b->type == BOX_BR || b->type == BOX_TEXT || + b->type == BOX_INLINE_END); + + NSLOG(layout, DEBUG, "%p: min %i, max %i", b, min, max); + + if (b->type == BOX_BR) { + b = b->next; + break; + } + + if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT) { + assert(b->children); + if (b->children->type == BOX_BLOCK) + layout_minmax_block(b->children, font_func, + content); + else + layout_minmax_table(b->children, font_func, + content); + b->min_width = b->children->min_width; + b->max_width = b->children->max_width; + if (min < b->min_width) + min = b->min_width; + max += b->max_width; + continue; + } + + if (b->type == BOX_INLINE_BLOCK) { + layout_minmax_block(b, font_func, content); + if (min < b->min_width) + min = b->min_width; + max += b->max_width; + + if (b->flags & HAS_HEIGHT) + *line_has_height = true; + continue; + } + + assert(b->style); + font_plot_style_from_css(&content->len_ctx, b->style, &fstyle); + + if (b->type == BOX_INLINE && !b->object && + !(b->flags & REPLACE_DIM) && + !(b->flags & IFRAME)) { + fixed = frac = 0; + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, true, true, true, + &fixed, &frac); + if (!b->inline_end) + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, true, true, + &fixed, &frac); + if (0 < fixed) + max += fixed; + *line_has_height = true; + /* \todo update min width, consider fractional extra */ + } else if (b->type == BOX_INLINE_END) { + fixed = frac = 0; + calculate_mbp_width(&content->len_ctx, + b->inline_end->style, RIGHT, + true, true, true, + &fixed, &frac); + if (0 < fixed) + max += fixed; + + if (b->next) { + if (b->space == UNKNOWN_WIDTH) { + font_func->width(&fstyle, " ", 1, + &b->space); + } + max += b->space; + } + + *line_has_height = true; + continue; + } + + if (!b->object && !(b->flags & IFRAME) && !b->gadget && + !(b->flags & REPLACE_DIM)) { + /* inline non-replaced, 10.3.1 and 10.6.1 */ + bool no_wrap_box; + if (!b->text) + continue; + + no_wrap_box = (css_computed_white_space(b->style) == + CSS_WHITE_SPACE_NOWRAP || + css_computed_white_space(b->style) == + CSS_WHITE_SPACE_PRE); + + if (b->width == UNKNOWN_WIDTH) { + /** \todo handle errors */ + + /* If it's a select element, we must use the + * width of the widest option text */ + if (b->parent->parent->gadget && + b->parent->parent->gadget->type + == GADGET_SELECT) { + int opt_maxwidth = 0; + struct form_option *o; + + for (o = b->parent->parent->gadget-> + data.select.items; o; + o = o->next) { + int opt_width; + font_func->width(&fstyle, + o->text, + strlen(o->text), + &opt_width); + + if (opt_maxwidth < opt_width) + opt_maxwidth =opt_width; + } + + b->width = opt_maxwidth; + if (nsoption_bool(core_select_menu)) + b->width += SCROLLBAR_WIDTH; + + } else { + font_func->width(&fstyle, b->text, + b->length, &b->width); + b->flags |= MEASURED; + } + } + max += b->width; + if (b->next) { + if (b->space == UNKNOWN_WIDTH) { + font_func->width(&fstyle, " ", 1, + &b->space); + } + max += b->space; + } + + if (no_wrap) { + /* Don't wrap due to block style, + * so min is the same as max */ + min = max; + + } else if (no_wrap_box) { + /* This inline box can't be wrapped, + * for min, consider box's width */ + if (min < b->width) + min = b->width; + + } else if (b->parent->flags & NEED_MIN) { + /* If we care what the minimum width is, + * calculate it. (It's only needed if we're + * shrinking-to-fit.) */ + /* min = widest single word */ + i = 0; + do { + for (j = i; j != b->length && + b->text[j] != ' '; j++) + ; + font_func->width(&fstyle, b->text + i, + j - i, &width); + if (min < width) + min = width; + i = j + 1; + } while (j != b->length); + } + + *line_has_height = true; + + continue; + } + + /* inline replaced, 10.3.2 and 10.6.2 */ + assert(b->style); + + /* calculate box width */ + wtype = css_computed_width(b->style, &value, &unit); + bs = css_computed_box_sizing(block->style); + if (wtype == CSS_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + width = AUTO; + } else { + width = FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, b->style)); + + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + fixed = frac = 0; + calculate_mbp_width(&content->len_ctx, + block->style, LEFT, + false, true, true, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + block->style, RIGHT, + false, true, true, + &fixed, &frac); + if (width < fixed) { + width = fixed; + } + } + if (width < 0) + width = 0; + } + } else { + width = AUTO; + } + + /* height */ + htype = css_computed_height(b->style, &value, &unit); + if (htype == CSS_HEIGHT_SET) { + height = FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, b->style)); + } else { + height = AUTO; + } + + if (b->object || (b->flags & REPLACE_DIM)) { + if (b->object) { + int temp_height = height; + layout_get_object_dimensions(b, + &width, &temp_height, + INT_MIN, INT_MAX, + INT_MIN, INT_MAX); + } + + fixed = frac = 0; + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, + true, false, false, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, false, false, + &fixed, &frac); + } else { + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, + true, true, true, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, true, true, + &fixed, &frac); + } + if (0 < width + fixed) + width += fixed; + } else if (b->flags & IFRAME) { + /* TODO: handle percentage widths properly */ + if (width == AUTO) + width = 400; + + fixed = frac = 0; + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, + true, false, false, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, false, false, + &fixed, &frac); + } else { + calculate_mbp_width(&content->len_ctx, + b->style, LEFT, + true, true, true, + &fixed, &frac); + calculate_mbp_width(&content->len_ctx, + b->style, RIGHT, + true, true, true, + &fixed, &frac); + } + + if (0 < width + fixed) + width += fixed; + + } else { + /* form control with no object */ + if (width == AUTO) + width = FIXTOINT(nscss_len2px( + &content->len_ctx, + INTTOFIX(1), CSS_UNIT_EM, + b->style)); + } + + if (min < width && !box_has_percentage_max_width(b)) + min = width; + if (width > 0) + max += width; + + *line_has_height = true; + } + + if (first_line) { + /* todo: handle percentage values properly */ + /* todo: handle text-indent interaction with floats */ + int text_indent = layout_text_indent(&content->len_ctx, + first->parent->parent->style, 100); + min = (min + text_indent < 0) ? 0 : min + text_indent; + max = (max + text_indent < 0) ? 0 : max + text_indent; + } + + *line_min = min; + *line_max = max; + + NSLOG(layout, DEBUG, "line_min %i, line_max %i", min, max); + + assert(b != first); + assert(0 <= *line_min); + assert(*line_min <= *line_max); + return b; +} + + +/** + * Calculate minimum and maximum width of an inline container. + * + * \param inline_container box of type INLINE_CONTAINER + * \param[out] has_height set to true if container has height + * \param font_func Font functions. + * \post inline_container->min_width and inline_container->max_width filled in, + * 0 <= inline_container->min_width <= inline_container->max_width + */ +static void +layout_minmax_inline_container(struct box *inline_container, + bool *has_height, + const struct gui_layout_table *font_func, + const html_content *content) +{ + struct box *child; + int line_min = 0, line_max = 0; + int min = 0, max = 0; + bool first_line = true; + bool line_has_height; + + assert(inline_container->type == BOX_INLINE_CONTAINER); + + /* check if the widths have already been calculated */ + if (inline_container->max_width != UNKNOWN_MAX_WIDTH) + return; + + *has_height = false; + + for (child = inline_container->children; child; ) { + child = layout_minmax_line(child, &line_min, &line_max, + first_line, &line_has_height, font_func, + content); + if (min < line_min) + min = line_min; + if (max < line_max) + max = line_max; + first_line = false; + *has_height |= line_has_height; + } + + inline_container->min_width = min; + inline_container->max_width = max; + + assert(0 <= inline_container->min_width && + inline_container->min_width <= + inline_container->max_width); +} + + +/** + * Calculate minimum and maximum width of a block. + * + * \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL + * \param font_func font functions + * \param content The HTML content being layed out. + * \post block->min_width and block->max_width filled in, + * 0 <= block->min_width <= block->max_width + */ +static void layout_minmax_block( + struct box *block, + const struct gui_layout_table *font_func, + const html_content *content) +{ + struct box *child; + int min = 0, max = 0; + int extra_fixed = 0; + float extra_frac = 0; + enum css_width_e wtype = CSS_WIDTH_AUTO; + css_fixed width = 0; + css_unit wunit = CSS_UNIT_PX; + enum css_height_e htype = CSS_HEIGHT_AUTO; + css_fixed height = 0; + css_unit hunit = CSS_UNIT_PX; + enum css_box_sizing_e bs = CSS_BOX_SIZING_CONTENT_BOX; + bool child_has_height = false; + + assert(block->type == BOX_BLOCK || + block->type == BOX_INLINE_BLOCK || + block->type == BOX_TABLE_CELL); + + /* check if the widths have already been calculated */ + if (block->max_width != UNKNOWN_MAX_WIDTH) + return; + + if (block->style != NULL) { + wtype = css_computed_width(block->style, &width, &wunit); + htype = css_computed_height(block->style, &height, &hunit); + bs = css_computed_box_sizing(block->style); + } + + /* set whether the minimum width is of any interest for this box */ + if (((block->parent && (block->parent->type == BOX_FLOAT_LEFT || + block->parent->type == BOX_FLOAT_RIGHT)) || + block->type == BOX_INLINE_BLOCK) && + wtype != CSS_WIDTH_SET) { + /* box shrinks to fit; need minimum width */ + block->flags |= NEED_MIN; + } else if (block->type == BOX_TABLE_CELL) { + /* box shrinks to fit; need minimum width */ + block->flags |= NEED_MIN; + } else if (block->parent && (block->parent->flags & NEED_MIN) && + wtype != CSS_WIDTH_SET) { + /* box inside shrink-to-fit context; need minimum width */ + block->flags |= NEED_MIN; + } + + if (block->gadget && (block->gadget->type == GADGET_TEXTBOX || + block->gadget->type == GADGET_PASSWORD || + block->gadget->type == GADGET_FILE || + block->gadget->type == GADGET_TEXTAREA) && + block->style && wtype == CSS_WIDTH_AUTO) { + css_fixed size = INTTOFIX(10); + css_unit unit = CSS_UNIT_EM; + + min = max = FIXTOINT(nscss_len2px(&content->len_ctx, + size, unit, block->style)); + + block->flags |= HAS_HEIGHT; + } + + if (block->gadget && (block->gadget->type == GADGET_RADIO || + block->gadget->type == GADGET_CHECKBOX) && + block->style && wtype == CSS_WIDTH_AUTO) { + css_fixed size = INTTOFIX(1); + css_unit unit = CSS_UNIT_EM; + + /* form checkbox or radio button + * if width is AUTO, set it to 1em */ + min = max = FIXTOINT(nscss_len2px(&content->len_ctx, + size, unit, block->style)); + + block->flags |= HAS_HEIGHT; + } + + if (block->object) { + if (content_get_type(block->object) == CONTENT_HTML) { + layout_minmax_block(html_get_box_tree(block->object), + font_func, content); + min = html_get_box_tree(block->object)->min_width; + max = html_get_box_tree(block->object)->max_width; + } else { + min = max = content_get_width(block->object); + } + + block->flags |= HAS_HEIGHT; + } else if (block->flags & IFRAME) { + /** \todo do we need to know the min/max width of the iframe's + * content? */ + block->flags |= HAS_HEIGHT; + } else { + /* recurse through children */ + for (child = block->children; child; child = child->next) { + switch (child->type) { + case BOX_BLOCK: + layout_minmax_block(child, font_func, + content); + if (child->flags & HAS_HEIGHT) + child_has_height = true; + break; + case BOX_INLINE_CONTAINER: + if (block->flags & NEED_MIN) + child->flags |= NEED_MIN; + + layout_minmax_inline_container(child, + &child_has_height, font_func, + content); + if (child_has_height && + child == + child->parent->children) { + block->flags |= MAKE_HEIGHT; + } + break; + case BOX_TABLE: + layout_minmax_table(child, font_func, + content); + /* todo: fix for zero height tables */ + child_has_height = true; + child->flags |= MAKE_HEIGHT; + break; + default: + assert(0); + } + assert(child->max_width != UNKNOWN_MAX_WIDTH); + + if (child->style && + (css_computed_position(child->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(child->style) == + CSS_POSITION_FIXED)) { + /* This child is positioned out of normal flow, + * so it will have no affect on width */ + continue; + } + + if (min < child->min_width) + min = child->min_width; + if (max < child->max_width) + max = child->max_width; + + if (child_has_height) + block->flags |= HAS_HEIGHT; + } + } + + if (max < min) { + box_dump(stderr, block, 0, true); + assert(0); + } + + /* fixed width takes priority */ + if (block->type != BOX_TABLE_CELL && wtype == CSS_WIDTH_SET && + wunit != CSS_UNIT_PCT) { + min = max = FIXTOINT(nscss_len2px(&content->len_ctx, + width, wunit, block->style)); + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + int border_box_fixed = 0; + float border_box_frac = 0; + calculate_mbp_width(&content->len_ctx, + block->style, LEFT, + false, true, true, + &border_box_fixed, &border_box_frac); + calculate_mbp_width(&content->len_ctx, + block->style, RIGHT, + false, true, true, + &border_box_fixed, &border_box_frac); + if (min < border_box_fixed) { + min = max = border_box_fixed; + } + } + } + + if (htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT && + height > INTTOFIX(0)) { + block->flags |= MAKE_HEIGHT; + block->flags |= HAS_HEIGHT; + } + + /* add margins, border, padding to min, max widths */ + /* Note: we don't know available width here so percentage margin + * and paddings are wrong. */ + if (bs == CSS_BOX_SIZING_BORDER_BOX && wtype == CSS_WIDTH_SET) { + /* Border and padding included in width, so just get margin */ + calculate_mbp_width(&content->len_ctx, + block->style, LEFT, true, false, false, + &extra_fixed, &extra_frac); + calculate_mbp_width(&content->len_ctx, + block->style, RIGHT, true, false, false, + &extra_fixed, &extra_frac); + } else { + calculate_mbp_width(&content->len_ctx, + block->style, LEFT, true, true, true, + &extra_fixed, &extra_frac); + calculate_mbp_width(&content->len_ctx, + block->style, RIGHT, true, true, true, + &extra_fixed, &extra_frac); + } + if (extra_fixed < 0) + extra_fixed = 0; + if (extra_frac < 0) + extra_frac = 0; + if (1.0 <= extra_frac) + extra_frac = 0.9; + if (block->style != NULL && + (css_computed_float(block->style) == CSS_FLOAT_LEFT || + css_computed_float(block->style) == CSS_FLOAT_RIGHT)) { + /* floated boxs */ + block->min_width = min + extra_fixed; + block->max_width = max + extra_fixed; + } else { + /* not floated */ + block->min_width = (min + extra_fixed) / (1.0 - extra_frac); + block->max_width = (max + extra_fixed) / (1.0 - extra_frac); + } + + assert(0 <= block->min_width && block->min_width <= block->max_width); +} + + +/** + * Adjust a specified width or height for the box-sizing property. + * + * This turns the specified dimension into a content-box dimension. + * + * \param len_ctx Length conversion context + * \param box gadget to adjust dimensions of + * \param available_width width of containing block + * \param setwidth set true if the dimension to be tweaked is a width, + * else set false for a height + * \param dimension current value for given width/height dimension. + * updated to new value after consideration of + * gadget properties. + */ +static void layout_handle_box_sizing( + const nscss_len_ctx *len_ctx, + struct box *box, + int available_width, + bool setwidth, + int *dimension) +{ + enum css_box_sizing_e bs; + + assert(box && box->style); + + bs = css_computed_box_sizing(box->style); + + if (bs == CSS_BOX_SIZING_BORDER_BOX) { + int orig = *dimension; + int fixed = 0; + float frac = 0; + + calculate_mbp_width(len_ctx, box->style, + setwidth ? LEFT : TOP, + false, true, true, &fixed, &frac); + calculate_mbp_width(len_ctx, box->style, + setwidth ? RIGHT : BOTTOM, + false, true, true, &fixed, &frac); + orig -= frac * available_width + fixed; + *dimension = orig > 0 ? orig : 0; + } +} + + +/** + * Calculate width, height, and thickness of margins, paddings, and borders. + * + * \param len_ctx Length conversion context + * \param available_width width of containing block + * \param viewport_height height of viewport in pixels or -ve if unknown + * \param box current box + * \param style style giving width, height, margins, paddings, + * and borders + * \param width updated to width, may be NULL + * \param height updated to height, may be NULL + * \param max_width updated to max-width, may be NULL + * \param min_width updated to min-width, may be NULL + * \param max_height updated to max-height, may be NULL + * \param min_height updated to min-height, may be NULL + * \param margin filled with margins, may be NULL + * \param padding filled with paddings, may be NULL + * \param border filled with border widths, may be NULL + */ +static void +layout_find_dimensions(const nscss_len_ctx *len_ctx, + int available_width, + int viewport_height, + struct box *box, + const css_computed_style *style, + int *width, + int *height, + int *max_width, + int *min_width, + int *max_height, + int *min_height, + int margin[4], + int padding[4], + struct box_border border[4]) +{ + struct box *containing_block = NULL; + unsigned int i; + + if (width) { + enum css_width_e wtype; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + wtype = css_computed_width(style, &value, &unit); + + if (wtype == CSS_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + *width = FPCT_OF_INT_TOINT( + value, available_width); + } else { + *width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + *width = AUTO; + } + + if (*width != AUTO) { + layout_handle_box_sizing(len_ctx, box, available_width, + true, width); + } + } + + if (height) { + enum css_height_e htype; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + htype = css_computed_height(style, &value, &unit); + + if (htype == CSS_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + enum css_height_e cbhtype; + + if (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE && + box->parent) { + /* Box is absolutely positioned */ + assert(box->float_container); + containing_block = box->float_container; + } else if (box->float_container && + css_computed_position(box->style) != + CSS_POSITION_ABSOLUTE && + (css_computed_float(box->style) == + CSS_FLOAT_LEFT || + css_computed_float(box->style) == + CSS_FLOAT_RIGHT)) { + /* Box is a float */ + assert(box->parent && + box->parent->parent && + box->parent->parent->parent); + + containing_block = + box->parent->parent->parent; + } else if (box->parent && box->parent->type != + BOX_INLINE_CONTAINER) { + /* Box is a block level element */ + containing_block = box->parent; + } else if (box->parent && box->parent->type == + BOX_INLINE_CONTAINER) { + /* Box is an inline block */ + assert(box->parent->parent); + containing_block = box->parent->parent; + } + + if (containing_block) { + css_fixed f = 0; + css_unit u = CSS_UNIT_PX; + + cbhtype = css_computed_height( + containing_block->style, + &f, &u); + } + + if (containing_block && + containing_block->height != AUTO && + (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE || + cbhtype == CSS_HEIGHT_SET)) { + /* Box is absolutely positioned or its + * containing block has a valid + * specified height. + * (CSS 2.1 Section 10.5) */ + *height = FPCT_OF_INT_TOINT(value, + containing_block->height); + } else if ((!box->parent || + !box->parent->parent) && + viewport_height >= 0) { + /* If root element or it's child + * (HTML or BODY) */ + *height = FPCT_OF_INT_TOINT(value, + viewport_height); + } else { + /* precentage height not permissible + * treat height as auto */ + *height = AUTO; + } + } else { + *height = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + *height = AUTO; + } + + if (*height != AUTO) { + layout_handle_box_sizing(len_ctx, box, available_width, + false, height); + } + } + + if (max_width) { + enum css_max_width_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = css_computed_max_width(style, &value, &unit); + + if (type == CSS_MAX_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + *max_width = FPCT_OF_INT_TOINT(value, + available_width); + } else { + *max_width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + /* Inadmissible */ + *max_width = -1; + } + + if (*max_width != -1) { + layout_handle_box_sizing(len_ctx, box, available_width, + true, max_width); + } + } + + if (min_width) { + enum css_min_width_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = ns_computed_min_width(style, &value, &unit); + + if (type == CSS_MIN_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + *min_width = FPCT_OF_INT_TOINT(value, + available_width); + } else { + *min_width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + /* Inadmissible */ + *min_width = 0; + } + + if (*min_width != 0) { + layout_handle_box_sizing(len_ctx, box, available_width, + true, min_width); + } + } + + if (max_height) { + enum css_max_height_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = css_computed_max_height(style, &value, &unit); + + if (type == CSS_MAX_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + /* TODO: handle percentage */ + *max_height = -1; + } else { + *max_height = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + /* Inadmissible */ + *max_height = -1; + } + } + + if (min_height) { + enum css_min_height_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = ns_computed_min_height(style, &value, &unit); + + if (type == CSS_MIN_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + /* TODO: handle percentage */ + *min_height = 0; + } else { + *min_height = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } else { + /* Inadmissible */ + *min_height = 0; + } + } + + for (i = 0; i != 4; i++) { + if (margin) { + enum css_margin_e type = CSS_MARGIN_AUTO; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + type = margin_funcs[i](style, &value, &unit); + + if (type == CSS_MARGIN_SET) { + if (unit == CSS_UNIT_PCT) { + margin[i] = FPCT_OF_INT_TOINT(value, + available_width); + } else { + margin[i] = FIXTOINT(nscss_len2px( + len_ctx, + value, unit, style)); + } + } else { + margin[i] = AUTO; + } + } + + if (padding) { + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + padding_funcs[i](style, &value, &unit); + + if (unit == CSS_UNIT_PCT) { + padding[i] = FPCT_OF_INT_TOINT(value, + available_width); + } else { + padding[i] = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + } + } + + /* Table cell borders are populated in table.c */ + if (border && box->type != BOX_TABLE_CELL) { + enum css_border_style_e bstyle = CSS_BORDER_STYLE_NONE; + css_color color = 0; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + border_width_funcs[i](style, &value, &unit); + bstyle = border_style_funcs[i](style); + border_color_funcs[i](style, &color); + + border[i].style = bstyle; + border[i].c = color; + + if (bstyle == CSS_BORDER_STYLE_HIDDEN || + bstyle == CSS_BORDER_STYLE_NONE) + /* spec unclear: following Mozilla */ + border[i].width = 0; + else + border[i].width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, style)); + + /* Special case for border-collapse: make all borders + * on table/table-row-group/table-row zero width. */ + if (css_computed_border_collapse(style) == + CSS_BORDER_COLLAPSE_COLLAPSE && + (box->type == BOX_TABLE || + box->type == BOX_TABLE_ROW_GROUP || + box->type == BOX_TABLE_ROW)) + border[i].width = 0; + } + } +} + + +/** + * Find next block that current margin collapses to. + * + * \param len_ctx Length conversion context + * \param box box to start tree-order search from (top margin is included) + * \param block box responsible for current block fromatting context + * \param viewport_height height of viewport in px + * \param max_pos_margin updated to to maximum positive margin encountered + * \param max_neg_margin updated to to maximum negative margin encountered + * \return next box that current margin collapses to, or NULL if none. + */ +static struct box* +layout_next_margin_block(const nscss_len_ctx *len_ctx, + struct box *box, + struct box *block, + int viewport_height, + int *max_pos_margin, + int *max_neg_margin) +{ + assert(block != NULL); + + while (box != NULL) { + + if (box->type == BOX_INLINE_CONTAINER || (box->style && + (css_computed_position(box->style) != + CSS_POSITION_ABSOLUTE && + css_computed_position(box->style) != + CSS_POSITION_FIXED))) { + /* Not positioned */ + + /* Get margins */ + if (box->style) { + layout_find_dimensions(len_ctx, + box->parent->width, + viewport_height, box, + box->style, + NULL, NULL, NULL, NULL, + NULL, NULL, box->margin, + box->padding, box->border); + + /* Apply top margin */ + if (*max_pos_margin < box->margin[TOP]) + *max_pos_margin = box->margin[TOP]; + else if (*max_neg_margin < -box->margin[TOP]) + *max_neg_margin = -box->margin[TOP]; + } + + /* Check whether box is the box current margin collapses + * to */ + if (box->flags & MAKE_HEIGHT || + box->border[TOP].width || + box->padding[TOP] || + (box->style && + css_computed_overflow_y(box->style) != + CSS_OVERFLOW_VISIBLE) || + (box->type == BOX_INLINE_CONTAINER && + box != box->parent->children)) { + /* Collapse to this box; return it */ + return box; + } + } + + + /* Find next box */ + if (box->type == BOX_BLOCK && !box->object && box->children && + box->style && + css_computed_overflow_y(box->style) == + CSS_OVERFLOW_VISIBLE) { + /* Down into children. */ + box = box->children; + } else { + if (!box->next) { + /* No more siblings: + * Go up to first ancestor with a sibling. */ + do { + /* Apply bottom margin */ + if (*max_pos_margin < + box->margin[BOTTOM]) + *max_pos_margin = + box->margin[BOTTOM]; + else if (*max_neg_margin < + -box->margin[BOTTOM]) + *max_neg_margin = + -box->margin[BOTTOM]; + + box = box->parent; + } while (box != block && !box->next); + + if (box == block) { + /* Margins don't collapse with stuff + * outside the block formatting context + */ + return block; + } + } + + /* Apply bottom margin */ + if (*max_pos_margin < box->margin[BOTTOM]) + *max_pos_margin = box->margin[BOTTOM]; + else if (*max_neg_margin < -box->margin[BOTTOM]) + *max_neg_margin = -box->margin[BOTTOM]; + + /* To next sibling. */ + box = box->next; + + /* Get margins */ + if (box->style) { + layout_find_dimensions(len_ctx, + box->parent->width, + viewport_height, box, + box->style, + NULL, NULL, NULL, NULL, + NULL, NULL, box->margin, + box->padding, box->border); + } + } + } + + return NULL; +} + + +/** + * Find y coordinate which clears all floats on left and/or right. + * + * \param fl first float in float list + * \param clear type of clear + * \return y coordinate relative to ancestor box for floats + */ +static int layout_clear(struct box *fl, enum css_clear_e clear) +{ + int y = 0; + for (; fl; fl = fl->next_float) { + if ((clear == CSS_CLEAR_LEFT || clear == CSS_CLEAR_BOTH) && + fl->type == BOX_FLOAT_LEFT) + if (y < fl->y + fl->height) + y = fl->y + fl->height; + if ((clear == CSS_CLEAR_RIGHT || clear == CSS_CLEAR_BOTH) && + fl->type == BOX_FLOAT_RIGHT) + if (y < fl->y + fl->height) + y = fl->y + fl->height; + } + return y; +} + + +/** + * Find left and right edges in a vertical range. + * + * \param fl first float in float list + * \param y0 start of y range to search + * \param y1 end of y range to search + * \param x0 start left edge, updated to available left edge + * \param x1 start right edge, updated to available right edge + * \param left returns float on left if present + * \param right returns float on right if present + */ +static void +find_sides(struct box *fl, + int y0, int y1, + int *x0, int *x1, + struct box **left, + struct box **right) +{ + int fy0, fy1, fx0, fx1; + + NSLOG(layout, DEBUG, "y0 %i, y1 %i, x0 %i, x1 %i", y0, y1, *x0, *x1); + + *left = *right = 0; + for (; fl; fl = fl->next_float) { + fy1 = fl->y + fl->height; + if (fy1 < y0) { + /* Floats are sorted in order of decreasing bottom pos. + * Past here, all floats will be too high to concern us. + */ + return; + } + fy0 = fl->y; + if (y0 < fy1 && fy0 <= y1) { + if (fl->type == BOX_FLOAT_LEFT) { + fx1 = fl->x + fl->width; + if (*x0 < fx1) { + *x0 = fx1; + *left = fl; + } + } else { + fx0 = fl->x; + if (fx0 < *x1) { + *x1 = fx0; + *right = fl; + } + } + } + } + + NSLOG(layout, DEBUG, "x0 %i, x1 %i, left %p, right %p", *x0, *x1, + *left, *right); +} + + + + +/** + * Solve the width constraint as given in CSS 2.1 section 10.3.3. + * + * \param box Box to solve constraint for + * \param available_width Max width available in pixels + * \param width Current box width + * \param lm Min left margin required to avoid floats in px. + * zero if not applicable + * \param rm Min right margin required to avoid floats in px. + * zero if not applicable + * \param max_width Box max-width ( -ve means no max-width to apply) + * \param min_width Box min-width ( <=0 means no min-width to apply) + * \return New box width + * + * \post \a box's left/right margins will be updated. + */ +static int +layout_solve_width(struct box *box, + int available_width, + int width, + int lm, + int rm, + int max_width, + int min_width) +{ + bool auto_width = false; + + /* Increase specified left/right margins */ + if (box->margin[LEFT] != AUTO && box->margin[LEFT] < lm && + box->margin[LEFT] >= 0) + box->margin[LEFT] = lm; + if (box->margin[RIGHT] != AUTO && box->margin[RIGHT] < rm && + box->margin[RIGHT] >= 0) + box->margin[RIGHT] = rm; + + /* Find width */ + if (width == AUTO) { + int margin_left = box->margin[LEFT]; + int margin_right = box->margin[RIGHT]; + + if (margin_left == AUTO) { + margin_left = lm; + } + if (margin_right == AUTO) { + margin_right = rm; + } + + width = available_width - + (margin_left + box->border[LEFT].width + + box->padding[LEFT] + box->padding[RIGHT] + + box->border[RIGHT].width + margin_right); + width = width < 0 ? 0 : width; + auto_width = true; + } + + if (max_width >= 0 && width > max_width) { + /* max-width is admissable and width exceeds max-width */ + width = max_width; + auto_width = false; + } + + if (min_width > 0 && width < min_width) { + /* min-width is admissable and width is less than max-width */ + width = min_width; + auto_width = false; + } + + /* Width was auto, and unconstrained by min/max width, so we're done */ + if (auto_width) { + /* any other 'auto' become 0 or the minimum required values */ + if (box->margin[LEFT] == AUTO) { + box->margin[LEFT] = lm; + } + if (box->margin[RIGHT] == AUTO) { + box->margin[RIGHT] = rm; + } + return width; + } + + /* Width was not auto, or was constrained by min/max width + * Need to compute left/right margins */ + + /* HTML alignment (only applies to over-constrained boxes) */ + if (box->margin[LEFT] != AUTO && box->margin[RIGHT] != AUTO && + box->parent != NULL && box->parent->style != NULL) { + switch (css_computed_text_align(box->parent->style)) { + case CSS_TEXT_ALIGN_LIBCSS_RIGHT: + box->margin[LEFT] = AUTO; + box->margin[RIGHT] = 0; + break; + case CSS_TEXT_ALIGN_LIBCSS_CENTER: + box->margin[LEFT] = box->margin[RIGHT] = AUTO; + break; + case CSS_TEXT_ALIGN_LIBCSS_LEFT: + box->margin[LEFT] = 0; + box->margin[RIGHT] = AUTO; + break; + default: + /* Leave it alone; no HTML alignment */ + break; + } + } + + if (box->margin[LEFT] == AUTO && box->margin[RIGHT] == AUTO) { + /* make the margins equal, centering the element */ + box->margin[LEFT] = box->margin[RIGHT] = + (available_width - lm - rm - + (box->border[LEFT].width + box->padding[LEFT] + + width + box->padding[RIGHT] + + box->border[RIGHT].width)) / 2; + + if (box->margin[LEFT] < 0) { + box->margin[RIGHT] += box->margin[LEFT]; + box->margin[LEFT] = 0; + } + + box->margin[LEFT] += lm; + + } else if (box->margin[LEFT] == AUTO) { + box->margin[LEFT] = available_width - lm - + (box->border[LEFT].width + box->padding[LEFT] + + width + box->padding[RIGHT] + + box->border[RIGHT].width + box->margin[RIGHT]); + box->margin[LEFT] = box->margin[LEFT] < lm + ? lm : box->margin[LEFT]; + } else { + /* margin-right auto or "over-constrained" */ + box->margin[RIGHT] = available_width - rm - + (box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + width + + box->padding[RIGHT] + + box->border[RIGHT].width); + } + + return width; +} + + +/** + * Compute dimensions of box, margins, paddings, and borders for a block-level + * element. + * + * \param len_ctx Length conversion context + * \param available_width Max width available in pixels + * \param viewport_height Height of viewport in pixels or -ve if unknown + * \param lm min left margin required to avoid floats in px. + * zero if not applicable + * \param rm min right margin required to avoid floats in px. + * zero if not applicable + * \param box box to find dimensions of. updated with new width, + * height, margins, borders and paddings + * + * See CSS 2.1 10.3.3, 10.3.4, 10.6.2, and 10.6.3. + */ +static void +layout_block_find_dimensions(const nscss_len_ctx *len_ctx, + int available_width, + int viewport_height, + int lm, + int rm, + struct box *box) +{ + int width, max_width, min_width; + int height, max_height, min_height; + int *margin = box->margin; + int *padding = box->padding; + struct box_border *border = box->border; + const css_computed_style *style = box->style; + + layout_find_dimensions(len_ctx, available_width, viewport_height, box, + style, &width, &height, &max_width, &min_width, + &max_height, &min_height, margin, padding, border); + + if (box->object && !(box->flags & REPLACE_DIM) && + content_get_type(box->object) != CONTENT_HTML) { + /* block-level replaced element, see 10.3.4 and 10.6.2 */ + layout_get_object_dimensions(box, &width, &height, + min_width, max_width, min_height, max_height); + } + + box->width = layout_solve_width(box, available_width, width, lm, rm, + max_width, min_width); + box->height = height; + + if (margin[TOP] == AUTO) + margin[TOP] = 0; + if (margin[BOTTOM] == AUTO) + margin[BOTTOM] = 0; +} + + +/** + * Manipulate a block's [RB]padding/height/width to accommodate scrollbars + * + * \param box Box to apply scrollbar space too. Must be BOX_BLOCK. + * \param which Which scrollbar to make space for. Must be RIGHT or BOTTOM. + */ +static void layout_block_add_scrollbar(struct box *box, int which) +{ + enum css_overflow_e overflow_x, overflow_y; + + assert(box->type == BOX_BLOCK && (which == RIGHT || which == BOTTOM)); + + if (box->style == NULL) + return; + + overflow_x = css_computed_overflow_x(box->style); + overflow_y = css_computed_overflow_y(box->style); + + if (which == BOTTOM && + (overflow_x == CSS_OVERFLOW_SCROLL || + overflow_x == CSS_OVERFLOW_AUTO || + (box->object && + content_get_type(box->object) == CONTENT_HTML))) { + /* make space for scrollbar, unless height is AUTO */ + if (box->height != AUTO && + (overflow_x == CSS_OVERFLOW_SCROLL || + box_hscrollbar_present(box))) { + box->padding[BOTTOM] += SCROLLBAR_WIDTH; + } + + } else if (which == RIGHT && + (overflow_y == CSS_OVERFLOW_SCROLL || + overflow_y == CSS_OVERFLOW_AUTO || + (box->object && + content_get_type(box->object) == CONTENT_HTML))) { + /* make space for scrollbars, unless width is AUTO */ + enum css_height_e htype; + css_fixed height = 0; + css_unit hunit = CSS_UNIT_PX; + htype = css_computed_height(box->style, &height, &hunit); + + if (which == RIGHT && box->width != AUTO && + htype == CSS_HEIGHT_SET && + (overflow_y == CSS_OVERFLOW_SCROLL || + box_vscrollbar_present(box))) { + box->width -= SCROLLBAR_WIDTH; + box->padding[RIGHT] += SCROLLBAR_WIDTH; + } + } +} + + +/** + * Moves the children of a box by a specified amount + * + * \param box top of tree of boxes + * \param x the amount to move children by horizontally + * \param y the amount to move children by vertically + */ +static void layout_move_children(struct box *box, int x, int y) +{ + assert(box); + + for (box = box->children; box; box = box->next) { + box->x += x; + box->y += y; + } +} + + +/** + * Layout a table. + * + * \param table table to layout + * \param available_width width of containing block + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool layout_table(struct box *table, int available_width, + html_content *content) +{ + unsigned int columns = table->columns; /* total columns */ + unsigned int i; + unsigned int *row_span; + int *excess_y; + int table_width, min_width = 0, max_width = 0; + int required_width = 0; + int x, remainder = 0, count = 0; + int table_height = 0; + int min_height = 0; + int *xs; /* array of column x positions */ + int auto_width; + int spare_width; + int relative_sum = 0; + int border_spacing_h = 0, border_spacing_v = 0; + int spare_height; + int positioned_columns = 0; + struct box *containing_block = NULL; + struct box *c; + struct box *row; + struct box *row_group; + struct box **row_span_cell; + struct column *col; + const css_computed_style *style = table->style; + enum css_width_e wtype; + enum css_height_e htype; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(table->type == BOX_TABLE); + assert(style); + assert(table->children && table->children->children); + assert(columns); + + /* allocate working buffers */ + col = malloc(columns * sizeof col[0]); + excess_y = malloc(columns * sizeof excess_y[0]); + row_span = malloc(columns * sizeof row_span[0]); + row_span_cell = malloc(columns * sizeof row_span_cell[0]); + xs = malloc((columns + 1) * sizeof xs[0]); + if (!col || !xs || !row_span || !excess_y || !row_span_cell) { + free(col); + free(excess_y); + free(row_span); + free(row_span_cell); + free(xs); + return false; + } + + memcpy(col, table->col, sizeof(col[0]) * columns); + + /* find margins, paddings, and borders for table and cells */ + layout_find_dimensions(&content->len_ctx, available_width, -1, table, + style, 0, 0, 0, 0, 0, 0, table->margin, table->padding, + table->border); + for (row_group = table->children; row_group; + row_group = row_group->next) { + for (row = row_group->children; row; row = row->next) { + for (c = row->children; c; c = c->next) { + enum css_overflow_e overflow_x; + enum css_overflow_e overflow_y; + + assert(c->style); + table_used_border_for_cell( + &content->len_ctx, c); + layout_find_dimensions(&content->len_ctx, + available_width, -1, c, + c->style, 0, 0, 0, 0, 0, 0, + 0, c->padding, c->border); + + overflow_x = css_computed_overflow_x(c->style); + overflow_y = css_computed_overflow_y(c->style); + + if (overflow_x == CSS_OVERFLOW_SCROLL || + overflow_x == + CSS_OVERFLOW_AUTO) { + c->padding[BOTTOM] += SCROLLBAR_WIDTH; + } + if (overflow_y == CSS_OVERFLOW_SCROLL || + overflow_y == + CSS_OVERFLOW_AUTO) { + c->padding[RIGHT] += SCROLLBAR_WIDTH; + } + } + } + } + + /* border-spacing is used in the separated borders model */ + if (css_computed_border_collapse(style) == + CSS_BORDER_COLLAPSE_SEPARATE) { + css_fixed h = 0, v = 0; + css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; + + css_computed_border_spacing(style, &h, &hu, &v, &vu); + + border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx, + h, hu, style)); + border_spacing_v = FIXTOINT(nscss_len2px(&content->len_ctx, + v, vu, style)); + } + + /* find specified table width, or available width if auto-width */ + wtype = css_computed_width(style, &value, &unit); + if (wtype == CSS_WIDTH_SET) { + if (unit == CSS_UNIT_PCT) { + table_width = FPCT_OF_INT_TOINT(value, available_width); + } else { + table_width = + FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, style)); + } + + /* specified width includes border */ + table_width -= table->border[LEFT].width + + table->border[RIGHT].width; + table_width = table_width < 0 ? 0 : table_width; + + auto_width = table_width; + } else { + table_width = AUTO; + auto_width = available_width - + ((table->margin[LEFT] == AUTO ? 0 : + table->margin[LEFT]) + + table->border[LEFT].width + + table->padding[LEFT] + + table->padding[RIGHT] + + table->border[RIGHT].width + + (table->margin[RIGHT] == AUTO ? 0 : + table->margin[RIGHT])); + } + + /* Find any table height specified within CSS/HTML */ + htype = css_computed_height(style, &value, &unit); + if (htype == CSS_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + /* This is the minimum height for the table + * (see 17.5.3) */ + if (css_computed_position(table->style) == + CSS_POSITION_ABSOLUTE) { + /* Table is absolutely positioned */ + assert(table->float_container); + containing_block = table->float_container; + } else if (table->float_container && + css_computed_position(table->style) != + CSS_POSITION_ABSOLUTE && + (css_computed_float(table->style) == + CSS_FLOAT_LEFT || + css_computed_float(table->style) == + CSS_FLOAT_RIGHT)) { + /* Table is a float */ + assert(table->parent && table->parent->parent && + table->parent->parent->parent); + containing_block = + table->parent->parent->parent; + } else if (table->parent && table->parent->type != + BOX_INLINE_CONTAINER) { + /* Table is a block level element */ + containing_block = table->parent; + } else if (table->parent && table->parent->type == + BOX_INLINE_CONTAINER) { + /* Table is an inline block */ + assert(table->parent->parent); + containing_block = table->parent->parent; + } + + if (containing_block) { + css_fixed ignored = 0; + + htype = css_computed_height( + containing_block->style, + &ignored, &unit); + } + + if (containing_block && + containing_block->height != AUTO && + (css_computed_position(table->style) == + CSS_POSITION_ABSOLUTE || + htype == CSS_HEIGHT_SET)) { + /* Table is absolutely positioned or its + * containing block has a valid specified + * height. (CSS 2.1 Section 10.5) */ + min_height = FPCT_OF_INT_TOINT(value, + containing_block->height); + } + } else { + /* This is the minimum height for the table + * (see 17.5.3) */ + min_height = FIXTOINT(nscss_len2px(&content->len_ctx, + value, unit, style)); + } + } + + /* calculate width required by cells */ + for (i = 0; i != columns; i++) { + + NSLOG(layout, DEBUG, + "table %p, column %u: type %s, width %i, min %i, max %i", + table, + i, + ((const char *[]){ + "UNKNOWN", + "FIXED", + "AUTO", + "PERCENT", + "RELATIVE", + })[col[i].type], + col[i].width, + col[i].min, + col[i].max); + + + if (col[i].positioned) { + positioned_columns++; + continue; + } else if (col[i].type == COLUMN_WIDTH_FIXED) { + if (col[i].width < col[i].min) + col[i].width = col[i].max = col[i].min; + else + col[i].min = col[i].max = col[i].width; + required_width += col[i].width; + } else if (col[i].type == COLUMN_WIDTH_PERCENT) { + int width = col[i].width * auto_width / 100; + required_width += col[i].min < width ? width : + col[i].min; + } else + required_width += col[i].min; + + NSLOG(layout, DEBUG, "required_width %i", required_width); + } + required_width += (columns + 1 - positioned_columns) * + border_spacing_h; + + NSLOG(layout, DEBUG, + "width %i, min %i, max %i, auto %i, required %i", table_width, + table->min_width, table->max_width, auto_width, required_width); + + if (auto_width < required_width) { + /* table narrower than required width for columns: + * treat percentage widths as maximums */ + for (i = 0; i != columns; i++) { + if (col[i].type == COLUMN_WIDTH_RELATIVE) + continue; + if (col[i].type == COLUMN_WIDTH_PERCENT) { + col[i].max = auto_width * col[i].width / 100; + if (col[i].max < col[i].min) + col[i].max = col[i].min; + } + min_width += col[i].min; + max_width += col[i].max; + } + } else { + /* take percentages exactly */ + for (i = 0; i != columns; i++) { + if (col[i].type == COLUMN_WIDTH_RELATIVE) + continue; + if (col[i].type == COLUMN_WIDTH_PERCENT) { + int width = auto_width * col[i].width / 100; + if (width < col[i].min) + width = col[i].min; + col[i].min = col[i].width = col[i].max = width; + col[i].type = COLUMN_WIDTH_FIXED; + } + min_width += col[i].min; + max_width += col[i].max; + } + } + + /* allocate relative widths */ + spare_width = auto_width; + for (i = 0; i != columns; i++) { + if (col[i].type == COLUMN_WIDTH_RELATIVE) + relative_sum += col[i].width; + else if (col[i].type == COLUMN_WIDTH_FIXED) + spare_width -= col[i].width; + else + spare_width -= col[i].min; + } + spare_width -= (columns + 1) * border_spacing_h; + if (relative_sum != 0) { + if (spare_width < 0) + spare_width = 0; + for (i = 0; i != columns; i++) { + if (col[i].type == COLUMN_WIDTH_RELATIVE) { + col[i].min = ceil(col[i].max = + (float) spare_width + * (float) col[i].width + / relative_sum); + min_width += col[i].min; + max_width += col[i].max; + } + } + } + min_width += (columns + 1) * border_spacing_h; + max_width += (columns + 1) * border_spacing_h; + + if (auto_width <= min_width) { + /* not enough space: minimise column widths */ + for (i = 0; i < columns; i++) { + col[i].width = col[i].min; + } + table_width = min_width; + } else if (max_width <= auto_width) { + /* more space than maximum width */ + if (table_width == AUTO) { + /* for auto-width tables, make columns max width */ + for (i = 0; i < columns; i++) { + col[i].width = col[i].max; + } + table_width = max_width; + } else { + /* for fixed-width tables, distribute the extra space + * too */ + unsigned int flexible_columns = 0; + for (i = 0; i != columns; i++) + if (col[i].type != COLUMN_WIDTH_FIXED) + flexible_columns++; + if (flexible_columns == 0) { + int extra = (table_width - max_width) / columns; + remainder = (table_width - max_width) - + (extra * columns); + for (i = 0; i != columns; i++) { + col[i].width = col[i].max + extra; + count -= remainder; + if (count < 0) { + col[i].width++; + count += columns; + } + } + + } else { + int extra = (table_width - max_width) / + flexible_columns; + remainder = (table_width - max_width) - + (extra * flexible_columns); + for (i = 0; i != columns; i++) + if (col[i].type != COLUMN_WIDTH_FIXED) { + col[i].width = col[i].max + + extra; + count -= remainder; + if (count < 0) { + col[i].width++; + count += flexible_columns; + } + } + } + } + } else { + /* space between min and max: fill it exactly */ + float scale = (float) (auto_width - min_width) / + (float) (max_width - min_width); + /* fprintf(stderr, "filling, scale %f\n", scale); */ + for (i = 0; i < columns; i++) { + col[i].width = col[i].min + (int) (0.5 + + (col[i].max - col[i].min) * scale); + } + table_width = auto_width; + } + + xs[0] = x = border_spacing_h; + for (i = 0; i != columns; i++) { + if (!col[i].positioned) + x += col[i].width + border_spacing_h; + xs[i + 1] = x; + row_span[i] = 0; + excess_y[i] = 0; + row_span_cell[i] = 0; + } + + /* position cells */ + table_height = border_spacing_v; + for (row_group = table->children; row_group; + row_group = row_group->next) { + int row_group_height = 0; + for (row = row_group->children; row; row = row->next) { + int row_height = 0; + + htype = css_computed_height(row->style, &value, &unit); + if (htype == CSS_HEIGHT_SET && unit != CSS_UNIT_PCT) { + row_height = FIXTOINT(nscss_len2px( + &content->len_ctx, + value, unit, row->style)); + } + for (c = row->children; c; c = c->next) { + assert(c->style); + c->width = xs[c->start_column + c->columns] - + xs[c->start_column] - + border_spacing_h - + c->border[LEFT].width - + c->padding[LEFT] - + c->padding[RIGHT] - + c->border[RIGHT].width; + c->float_children = 0; + c->cached_place_below_level = 0; + + c->height = AUTO; + if (!layout_block_context(c, -1, content)) { + free(col); + free(excess_y); + free(row_span); + free(row_span_cell); + free(xs); + return false; + } + /* warning: c->descendant_y0 and + * c->descendant_y1 used as temporary storage + * until after vertical alignment is complete */ + c->descendant_y0 = c->height; + c->descendant_y1 = c->padding[BOTTOM]; + + htype = css_computed_height(c->style, + &value, &unit); + + if (htype == CSS_HEIGHT_SET && + unit != CSS_UNIT_PCT) { + /* some sites use height="1" or similar + * to attempt to make cells as small as + * possible, so treat it as a minimum */ + int h = FIXTOINT(nscss_len2px( + &content->len_ctx, + value, unit, c->style)); + if (c->height < h) + c->height = h; + } + /* specified row height is treated as a minimum + */ + if (c->height < row_height) + c->height = row_height; + c->x = xs[c->start_column] + + c->border[LEFT].width; + c->y = c->border[TOP].width; + for (i = 0; i != c->columns; i++) { + row_span[c->start_column + i] = c->rows; + excess_y[c->start_column + i] = + c->border[TOP].width + + c->padding[TOP] + + c->height + + c->padding[BOTTOM] + + c->border[BOTTOM].width; + row_span_cell[c->start_column + i] = 0; + } + row_span_cell[c->start_column] = c; + c->padding[BOTTOM] = -border_spacing_v - + c->border[TOP].width - + c->padding[TOP] - + c->height - + c->border[BOTTOM].width; + } + for (i = 0; i != columns; i++) + if (row_span[i] != 0) + row_span[i]--; + else + row_span_cell[i] = 0; + if (row->next || row_group->next) { + /* row height is greatest excess of a cell + * which ends in this row */ + for (i = 0; i != columns; i++) + if (row_span[i] == 0 && row_height < + excess_y[i]) + row_height = excess_y[i]; + } else { + /* except in the last row */ + for (i = 0; i != columns; i++) + if (row_height < excess_y[i]) + row_height = excess_y[i]; + } + for (i = 0; i != columns; i++) { + if (row_height < excess_y[i]) + excess_y[i] -= row_height; + else + excess_y[i] = 0; + if (row_span_cell[i] != 0) + row_span_cell[i]->padding[BOTTOM] += + row_height + + border_spacing_v; + } + + row->x = 0; + row->y = row_group_height; + row->width = table_width; + row->height = row_height; + row_group_height += row_height + border_spacing_v; + } + row_group->x = 0; + row_group->y = table_height; + row_group->width = table_width; + row_group->height = row_group_height; + table_height += row_group_height; + } + /* Table height is either the height of the contents, or specified + * height if greater */ + table_height = max(table_height, min_height); + /** \todo distribute spare height over the row groups / rows / cells */ + + /* perform vertical alignment */ + for (row_group = table->children; row_group; + row_group = row_group->next) { + for (row = row_group->children; row; row = row->next) { + for (c = row->children; c; c = c->next) { + enum css_vertical_align_e vertical_align; + + /* unextended bottom padding is in + * c->descendant_y1, and unextended + * cell height is in c->descendant_y0 */ + spare_height = (c->padding[BOTTOM] - + c->descendant_y1) + + (c->height - c->descendant_y0); + + vertical_align = css_computed_vertical_align( + c->style, &value, &unit); + + switch (vertical_align) { + case CSS_VERTICAL_ALIGN_SUB: + case CSS_VERTICAL_ALIGN_SUPER: + case CSS_VERTICAL_ALIGN_TEXT_TOP: + case CSS_VERTICAL_ALIGN_TEXT_BOTTOM: + case CSS_VERTICAL_ALIGN_SET: + case CSS_VERTICAL_ALIGN_BASELINE: + /* todo: baseline alignment, for now + * just use ALIGN_TOP */ + case CSS_VERTICAL_ALIGN_TOP: + break; + case CSS_VERTICAL_ALIGN_MIDDLE: + c->padding[TOP] += spare_height / 2; + c->padding[BOTTOM] -= spare_height / 2; + layout_move_children(c, 0, + spare_height / 2); + break; + case CSS_VERTICAL_ALIGN_BOTTOM: + c->padding[TOP] += spare_height; + c->padding[BOTTOM] -= spare_height; + layout_move_children(c, 0, + spare_height); + break; + case CSS_VERTICAL_ALIGN_INHERIT: + assert(0); + break; + } + } + } + } + + /* Top and bottom margins of 'auto' are set to 0. CSS2.1 10.6.3 */ + if (table->margin[TOP] == AUTO) + table->margin[TOP] = 0; + if (table->margin[BOTTOM] == AUTO) + table->margin[BOTTOM] = 0; + + free(col); + free(excess_y); + free(row_span); + free(row_span_cell); + free(xs); + + table->width = table_width; + table->height = table_height; + + return true; +} + + +/** + * Manimpulate box height according to CSS min-height and max-height properties + * + * \param len_ctx CSS length conversion context for document. + * \param box block to modify with any min-height or max-height + * \param container containing block for absolutely positioned elements, or + * NULL for non absolutely positioned elements. + * \return whether the height has been changed + */ +static bool layout_apply_minmax_height( + const nscss_len_ctx *len_ctx, + struct box *box, + struct box *container) +{ + int h; + struct box *containing_block = NULL; + bool updated = false; + + /* Find containing block for percentage heights */ + if (box->style != NULL && css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE) { + /* Box is absolutely positioned */ + assert(container); + containing_block = container; + } else if (box->float_container && box->style != NULL && + (css_computed_float(box->style) == CSS_FLOAT_LEFT || + css_computed_float(box->style) == CSS_FLOAT_RIGHT)) { + /* Box is a float */ + assert(box->parent && box->parent->parent && + box->parent->parent->parent); + containing_block = box->parent->parent->parent; + } else if (box->parent && box->parent->type != BOX_INLINE_CONTAINER) { + /* Box is a block level element */ + containing_block = box->parent; + } else if (box->parent && box->parent->type == BOX_INLINE_CONTAINER) { + /* Box is an inline block */ + assert(box->parent->parent); + containing_block = box->parent->parent; + } + + if (box->style) { + enum css_height_e htype = CSS_HEIGHT_AUTO; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + if (containing_block) { + htype = css_computed_height(containing_block->style, + &value, &unit); + } + + /* max-height */ + if (css_computed_max_height(box->style, &value, &unit) == + CSS_MAX_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + if (containing_block && + containing_block->height != AUTO && + (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE || + htype == CSS_HEIGHT_SET)) { + /* Box is absolutely positioned or its + * containing block has a valid + * specified height. (CSS 2.1 + * Section 10.5) */ + h = FPCT_OF_INT_TOINT(value, + containing_block->height); + if (h < box->height) { + box->height = h; + updated = true; + } + } + } else { + h = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + if (h < box->height) { + box->height = h; + updated = true; + } + } + } + + /* min-height */ + if (ns_computed_min_height(box->style, &value, &unit) == + CSS_MIN_HEIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + if (containing_block && + containing_block->height != AUTO && + (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE || + htype == CSS_HEIGHT_SET)) { + /* Box is absolutely positioned or its + * containing block has a valid + * specified height. (CSS 2.1 + * Section 10.5) */ + h = FPCT_OF_INT_TOINT(value, + containing_block->height); + if (h > box->height) { + box->height = h; + updated = true; + } + } + } else { + h = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + if (h > box->height) { + box->height = h; + updated = true; + } + } + } + } + return updated; +} + + +/** + * Layout a block which contains an object. + * + * \param block box of type BLOCK, INLINE_BLOCK, TABLE, or TABLE_CELL + * \return true on success, false on memory exhaustion + */ +static bool layout_block_object(struct box *block) +{ + assert(block); + assert(block->type == BOX_BLOCK || + block->type == BOX_INLINE_BLOCK || + block->type == BOX_TABLE || + block->type == BOX_TABLE_CELL); + assert(block->object); + + NSLOG(layout, DEBUG, "block %p, object %p, width %i", block, + hlcache_handle_get_url(block->object), block->width); + + if (content_get_type(block->object) == CONTENT_HTML) { + content_reformat(block->object, false, block->width, 1); + } else { + /* Non-HTML objects */ + /* this case handled already in + * layout_block_find_dimensions() */ + } + + return true; +} + + +/** + * Insert a float into a container. + * + * \param cont block formatting context block, used to contain float + * \param b box to add to float + * + * This sorts floats in order of descending bottom edges. + */ +static void add_float_to_container(struct box *cont, struct box *b) +{ + struct box *box = cont->float_children; + int b_bottom = b->y + b->height; + + assert(b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT); + + if (box == NULL) { + /* No other float children */ + b->next_float = NULL; + cont->float_children = b; + return; + } else if (b_bottom >= box->y + box->height) { + /* Goes at start of list */ + b->next_float = cont->float_children; + cont->float_children = b; + } else { + struct box *prev = NULL; + while (box != NULL && b_bottom < box->y + box->height) { + prev = box; + box = box->next_float; + } + if (prev != NULL) { + b->next_float = prev->next_float; + prev->next_float = b; + } + } +} + + +/** + * Split a text box. + * + * \param content memory pool for any new boxes + * \param fstyle style for text in text box + * \param split_box box with text to split + * \param new_length new length for text in split_box, after splitting + * \param new_width new width for text in split_box, after splitting + * \return true on success, false on memory exhaustion + * + * A new box is created and inserted into the box tree after split_box, + * containing the text after new_length excluding the initial space character. + */ +static bool +layout_text_box_split(html_content *content, + plot_font_style_t *fstyle, + struct box *split_box, + size_t new_length, + int new_width) +{ + int space_width = split_box->space; + struct box *c2; + const struct gui_layout_table *font_func = content->font_func; + bool space = (split_box->text[new_length] == ' '); + int used_length = new_length + (space ? 1 : 0); + + if ((space && space_width == 0) || space_width == UNKNOWN_WIDTH) { + /* We're need to add a space, and we don't know how big + * it's to be, OR we have a space of unknown width anyway; + * Calculate space width */ + font_func->width(fstyle, " ", 1, &space_width); + } + + if (split_box->space == UNKNOWN_WIDTH) + split_box->space = space_width; + if (!space) + space_width = 0; + + /* Create clone of split_box, c2 */ + c2 = talloc_memdup(content->bctx, split_box, sizeof *c2); + if (!c2) + return false; + c2->flags |= CLONE; + + /* Set remaining text in c2 */ + c2->text += used_length; + + /* Set c2 according to the remaining text */ + c2->width -= new_width + space_width; + c2->flags &= ~MEASURED; /* width has been estimated */ + c2->length = split_box->length - used_length; + + /* Update split_box for its reduced text */ + split_box->width = new_width; + split_box->flags |= MEASURED; + split_box->length = new_length; + split_box->space = space_width; + + /* Insert c2 into box list */ + c2->next = split_box->next; + split_box->next = c2; + c2->prev = split_box; + if (c2->next) + c2->next->prev = c2; + else + c2->parent->last = c2; + + NSLOG(layout, DEBUG, + "split_box %p len: %" PRIsizet " \"%.*s\"", + split_box, + split_box->length, + (int)split_box->length, + split_box->text); + NSLOG(layout, DEBUG, + " new_box %p len: %" PRIsizet " \"%.*s\"", + c2, + c2->length, + (int)c2->length, + c2->text); + + return true; +} + + +/** + * Compute dimensions of box, margins, paddings, and borders for a floating + * element using shrink-to-fit. Also used for inline-blocks. + * + * \param len_ctx CSS length conversion context for document. + * \param available_width Max width available in pixels + * \param style Box's style + * \param box Box for which to find dimensions + * Box margins, borders, paddings, width and + * height are updated. + */ +static void +layout_float_find_dimensions( + const nscss_len_ctx *len_ctx, + int available_width, + const css_computed_style *style, + struct box *box) +{ + int width, height, max_width, min_width, max_height, min_height; + int *margin = box->margin; + int *padding = box->padding; + struct box_border *border = box->border; + enum css_overflow_e overflow_x = css_computed_overflow_x(style); + enum css_overflow_e overflow_y = css_computed_overflow_y(style); + int scrollbar_width_x = + (overflow_x == CSS_OVERFLOW_SCROLL || + overflow_x == CSS_OVERFLOW_AUTO) ? + SCROLLBAR_WIDTH : 0; + int scrollbar_width_y = + (overflow_y == CSS_OVERFLOW_SCROLL || + overflow_y == CSS_OVERFLOW_AUTO) ? + SCROLLBAR_WIDTH : 0; + + layout_find_dimensions(len_ctx, available_width, -1, box, style, + &width, &height, &max_width, &min_width, + &max_height, &min_height, margin, padding, border); + + if (margin[LEFT] == AUTO) + margin[LEFT] = 0; + if (margin[RIGHT] == AUTO) + margin[RIGHT] = 0; + + if (box->gadget == NULL) { + padding[RIGHT] += scrollbar_width_y; + padding[BOTTOM] += scrollbar_width_x; + } + + if (box->object && !(box->flags & REPLACE_DIM) && + content_get_type(box->object) != CONTENT_HTML) { + /* Floating replaced element, with intrinsic width or height. + * See 10.3.6 and 10.6.2 */ + layout_get_object_dimensions(box, &width, &height, + min_width, max_width, min_height, max_height); + } else if (box->gadget && (box->gadget->type == GADGET_TEXTBOX || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_FILE || + box->gadget->type == GADGET_TEXTAREA)) { + css_fixed size = 0; + css_unit unit = CSS_UNIT_EM; + + /* Give sensible dimensions to gadgets, with auto width/height, + * that don't shrink to fit contained text. */ + assert(box->style); + + if (box->gadget->type == GADGET_TEXTBOX || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_FILE) { + if (width == AUTO) { + size = INTTOFIX(10); + width = FIXTOINT(nscss_len2px(len_ctx, + size, unit, box->style)); + } + if (box->gadget->type == GADGET_FILE && + height == AUTO) { + size = FLTTOFIX(1.5); + height = FIXTOINT(nscss_len2px(len_ctx, + size, unit, box->style)); + } + } + if (box->gadget->type == GADGET_TEXTAREA) { + if (width == AUTO) { + size = INTTOFIX(10); + width = FIXTOINT(nscss_len2px(len_ctx, + size, unit, box->style)); + } + if (height == AUTO) { + size = INTTOFIX(4); + height = FIXTOINT(nscss_len2px(len_ctx, + size, unit, box->style)); + } + } + } else if (width == AUTO) { + /* CSS 2.1 section 10.3.5 */ + width = min(max(box->min_width, available_width), + box->max_width); + + /* width includes margin, borders and padding */ + if (width == available_width) { + width -= box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + + box->padding[RIGHT] + + box->border[RIGHT].width + + box->margin[RIGHT]; + } else { + /* width was obtained from a min_width or max_width + * value, so need to use the same method for calculating + * mbp as was used in layout_minmax_block() */ + int fixed = 0; + float frac = 0; + calculate_mbp_width(len_ctx, box->style, LEFT, + true, true, true, &fixed, &frac); + calculate_mbp_width(len_ctx, box->style, RIGHT, + true, true, true, &fixed, &frac); + if (fixed < 0) + fixed = 0; + + width -= fixed; + } + + if (max_width >= 0 && width > max_width) width = max_width; + if (min_width > 0 && width < min_width) width = min_width; + + } else { + if (max_width >= 0 && width > max_width) width = max_width; + if (min_width > 0 && width < min_width) width = min_width; + width -= scrollbar_width_y; + } + + box->width = width; + box->height = height; + + if (margin[TOP] == AUTO) + margin[TOP] = 0; + if (margin[BOTTOM] == AUTO) + margin[BOTTOM] = 0; +} + + +/** + * Layout the contents of a float or inline block. + * + * \param b float or inline block box + * \param width available width + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool layout_float(struct box *b, int width, html_content *content) +{ + assert(b->type == BOX_TABLE || b->type == BOX_BLOCK || + b->type == BOX_INLINE_BLOCK); + layout_float_find_dimensions(&content->len_ctx, width, b->style, b); + if (b->type == BOX_TABLE) { + if (!layout_table(b, width, content)) + return false; + if (b->margin[LEFT] == AUTO) + b->margin[LEFT] = 0; + if (b->margin[RIGHT] == AUTO) + b->margin[RIGHT] = 0; + if (b->margin[TOP] == AUTO) + b->margin[TOP] = 0; + if (b->margin[BOTTOM] == AUTO) + b->margin[BOTTOM] = 0; + } else + return layout_block_context(b, -1, content); + return true; +} + + +/** + * Position a float in the first available space. + * + * \param c float box to position + * \param width available width + * \param cx x coordinate relative to cont to place float right of + * \param y y coordinate relative to cont to place float below + * \param cont ancestor box which defines horizontal space, for floats + */ +static void +place_float_below(struct box *c, int width, int cx, int y, struct box *cont) +{ + int x0, x1, yy; + struct box *left; + struct box *right; + + yy = y > cont->cached_place_below_level ? + y : cont->cached_place_below_level; + + NSLOG(layout, DEBUG, + "c %p, width %i, cx %i, y %i, cont %p", c, + width, cx, y, cont); + + do { + y = yy; + x0 = cx; + x1 = cx + width; + find_sides(cont->float_children, y, y + c->height, &x0, &x1, + &left, &right); + if (left != 0 && right != 0) { + yy = (left->y + left->height < + right->y + right->height ? + left->y + left->height : + right->y + right->height); + } else if (left == 0 && right != 0) { + yy = right->y + right->height; + } else if (left != 0 && right == 0) { + yy = left->y + left->height; + } + } while ((left != 0 || right != 0) && (c->width > x1 - x0)); + + if (c->type == BOX_FLOAT_LEFT) { + c->x = x0; + } else { + c->x = x1 - c->width; + } + c->y = y; + cont->cached_place_below_level = y; +} + + +/** + * Calculate line height from a style. + */ +static int line_height( + const nscss_len_ctx *len_ctx, + const css_computed_style *style) +{ + enum css_line_height_e lhtype; + css_fixed lhvalue = 0; + css_unit lhunit = CSS_UNIT_PX; + css_fixed line_height; + + assert(style); + + lhtype = css_computed_line_height(style, &lhvalue, &lhunit); + if (lhtype == CSS_LINE_HEIGHT_NORMAL) { + /* Normal => use a constant of 1.3 * font-size */ + lhvalue = FLTTOFIX(1.3); + lhtype = CSS_LINE_HEIGHT_NUMBER; + } + + if (lhtype == CSS_LINE_HEIGHT_NUMBER || + lhunit == CSS_UNIT_PCT) { + line_height = nscss_len2px(len_ctx, + lhvalue, CSS_UNIT_EM, style); + + if (lhtype != CSS_LINE_HEIGHT_NUMBER) + line_height = FDIV(line_height, F_100); + } else { + assert(lhunit != CSS_UNIT_PCT); + + line_height = nscss_len2px(len_ctx, + lhvalue, lhunit, style); + } + + return FIXTOINT(line_height); +} + + +/** + * Position a line of boxes in inline formatting context. + * + * \param first box at start of line + * \param width available width on input, updated with actual width on output + * (may be incorrect if the line gets split?) + * \param y coordinate of top of line, updated on exit to bottom + * \param cx coordinate of left of line relative to cont + * \param cy coordinate of top of line relative to cont + * \param cont ancestor box which defines horizontal space, for floats + * \param indent apply any first-line indent + * \param has_text_children at least one TEXT in the inline_container + * \param next_box updated to first box for next line, or 0 at end + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool +layout_line(struct box *first, + int *width, + int *y, + int cx, + int cy, + struct box *cont, + bool indent, + bool has_text_children, + html_content *content, + struct box **next_box) +{ + int height, used_height; + int x0 = 0; + int x1 = *width; + int x, h, x_previous; + int fy = cy; + struct box *left; + struct box *right; + struct box *b; + struct box *split_box = 0; + struct box *d; + struct box *br_box = 0; + bool move_y = false; + bool place_below = false; + int space_before = 0, space_after = 0; + unsigned int inline_count = 0; + unsigned int i; + const struct gui_layout_table *font_func = content->font_func; + plot_font_style_t fstyle; + + NSLOG(layout, DEBUG, + "first %p, first->text '%.*s', width %i, y %i, cx %i, cy %i", + first, + (int)first->length, + first->text, + *width, + *y, + cx, + cy); + + /* find sides at top of line */ + x0 += cx; + x1 += cx; + find_sides(cont->float_children, cy, cy, &x0, &x1, &left, &right); + x0 -= cx; + x1 -= cx; + + if (indent) + x0 += layout_text_indent(&content->len_ctx, + first->parent->parent->style, *width); + + if (x1 < x0) + x1 = x0; + + /* get minimum line height from containing block. + * this is the line-height if there are text children and also in the + * case of an initially empty text input */ + if (has_text_children || first->parent->parent->gadget) + used_height = height = line_height(&content->len_ctx, + first->parent->parent->style); + else + /* inline containers with no text are usually for layout and + * look better with no minimum line-height */ + used_height = height = 0; + + /* pass 1: find height of line assuming sides at top of line: loop + * body executed at least once + * keep in sync with the loop in layout_minmax_line() */ + + NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0); + + + for (x = 0, b = first; x <= x1 - x0 && b != 0; b = b->next) { + int min_width, max_width, min_height, max_height; + + assert(b->type == BOX_INLINE || b->type == BOX_INLINE_BLOCK || + b->type == BOX_FLOAT_LEFT || + b->type == BOX_FLOAT_RIGHT || + b->type == BOX_BR || b->type == BOX_TEXT || + b->type == BOX_INLINE_END); + + + NSLOG(layout, DEBUG, "pass 1: b %p, x %i", b, x); + + + if (b->type == BOX_BR) + break; + + if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT) + continue; + if (b->type == BOX_INLINE_BLOCK && + (css_computed_position(b->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(b->style) == + CSS_POSITION_FIXED)) + continue; + + assert(b->style != NULL); + font_plot_style_from_css(&content->len_ctx, b->style, &fstyle); + + x += space_after; + + if (b->type == BOX_INLINE_BLOCK) { + if (b->max_width != UNKNOWN_WIDTH) + if (!layout_float(b, *width, content)) + return false; + h = b->border[TOP].width + b->padding[TOP] + b->height + + b->padding[BOTTOM] + + b->border[BOTTOM].width; + if (height < h) + height = h; + x += b->margin[LEFT] + b->border[LEFT].width + + b->padding[LEFT] + b->width + + b->padding[RIGHT] + + b->border[RIGHT].width + + b->margin[RIGHT]; + space_after = 0; + continue; + } + + if (b->type == BOX_INLINE) { + /* calculate borders, margins, and padding */ + layout_find_dimensions(&content->len_ctx, + *width, -1, b, b->style, 0, 0, 0, 0, + 0, 0, b->margin, b->padding, b->border); + for (i = 0; i != 4; i++) + if (b->margin[i] == AUTO) + b->margin[i] = 0; + x += b->margin[LEFT] + b->border[LEFT].width + + b->padding[LEFT]; + if (b->inline_end) { + b->inline_end->margin[RIGHT] = b->margin[RIGHT]; + b->inline_end->padding[RIGHT] = + b->padding[RIGHT]; + b->inline_end->border[RIGHT] = + b->border[RIGHT]; + } else { + x += b->padding[RIGHT] + + b->border[RIGHT].width + + b->margin[RIGHT]; + } + } else if (b->type == BOX_INLINE_END) { + b->width = 0; + if (b->space == UNKNOWN_WIDTH) { + font_func->width(&fstyle, " ", 1, &b->space); + /** \todo handle errors */ + } + space_after = b->space; + + x += b->padding[RIGHT] + b->border[RIGHT].width + + b->margin[RIGHT]; + continue; + } + + if (!b->object && !(b->flags & IFRAME) && !b->gadget && + !(b->flags & REPLACE_DIM)) { + /* inline non-replaced, 10.3.1 and 10.6.1 */ + b->height = line_height(&content->len_ctx, + b->style ? b->style : + b->parent->parent->style); + if (height < b->height) + height = b->height; + + if (!b->text) { + b->width = 0; + space_after = 0; + continue; + } + + if (b->width == UNKNOWN_WIDTH) { + /** \todo handle errors */ + + /* If it's a select element, we must use the + * width of the widest option text */ + if (b->parent->parent->gadget && + b->parent->parent->gadget->type + == GADGET_SELECT) { + int opt_maxwidth = 0; + struct form_option *o; + + for (o = b->parent->parent->gadget-> + data.select.items; o; + o = o->next) { + int opt_width; + font_func->width(&fstyle, + o->text, + strlen(o->text), + &opt_width); + + if (opt_maxwidth < opt_width) + opt_maxwidth =opt_width; + } + b->width = opt_maxwidth; + if (nsoption_bool(core_select_menu)) + b->width += SCROLLBAR_WIDTH; + } else { + font_func->width(&fstyle, b->text, + b->length, &b->width); + b->flags |= MEASURED; + } + } + + /* If the current text has not been measured (i.e. its + * width was estimated after splitting), and it fits on + * the line, measure it properly, so next box is placed + * correctly. */ + if (b->text && (x + b->width < x1 - x0) && + !(b->flags & MEASURED) && + b->next) { + font_func->width(&fstyle, b->text, + b->length, &b->width); + b->flags |= MEASURED; + } + + x += b->width; + if (b->space == UNKNOWN_WIDTH) { + font_func->width(&fstyle, " ", 1, &b->space); + /** \todo handle errors */ + } + space_after = b->space; + continue; + } + + space_after = 0; + + /* inline replaced, 10.3.2 and 10.6.2 */ + assert(b->style); + + layout_find_dimensions(&content->len_ctx, + *width, -1, b, b->style, + &b->width, &b->height, + &max_width, &min_width, + &max_height, &min_height, + NULL, NULL, NULL); + + if (b->object && !(b->flags & REPLACE_DIM)) { + layout_get_object_dimensions(b, &b->width, &b->height, + min_width, max_width, + min_height, max_height); + } else if (b->flags & IFRAME) { + /* TODO: should we look at the content dimensions? */ + if (b->width == AUTO) + b->width = 400; + if (b->height == AUTO) + b->height = 300; + + /* We reformat the iframe browser window to new + * dimensions in pass 2 */ + } else { + /* form control with no object */ + if (b->width == AUTO) + b->width = FIXTOINT(nscss_len2px( + &content->len_ctx, INTTOFIX(1), + CSS_UNIT_EM, b->style)); + if (b->height == AUTO) + b->height = FIXTOINT(nscss_len2px( + &content->len_ctx, INTTOFIX(1), + CSS_UNIT_EM, b->style)); + } + + /* Reformat object to new box size */ + if (b->object && content_get_type(b->object) == CONTENT_HTML && + b->width != + content_get_available_width(b->object)) { + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + enum css_height_e htype = css_computed_height(b->style, + &value, &unit); + + content_reformat(b->object, false, b->width, b->height); + + if (htype == CSS_HEIGHT_AUTO) + b->height = content_get_height(b->object); + } + + if (height < b->height) + height = b->height; + + x += b->width; + } + + /* find new sides using this height */ + x0 = cx; + x1 = cx + *width; + find_sides(cont->float_children, cy, cy + height, &x0, &x1, + &left, &right); + x0 -= cx; + x1 -= cx; + + if (indent) + x0 += layout_text_indent(&content->len_ctx, + first->parent->parent->style, *width); + + if (x1 < x0) + x1 = x0; + + space_after = space_before = 0; + + /* pass 2: place boxes in line: loop body executed at least once */ + + NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0); + + for (x = x_previous = 0, b = first; x <= x1 - x0 && b; b = b->next) { + + NSLOG(layout, DEBUG, "pass 2: b %p, x %i", b, x); + + if (b->type == BOX_INLINE_BLOCK && + (css_computed_position(b->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(b->style) == + CSS_POSITION_FIXED)) { + b->x = x + space_after; + + } else if (b->type == BOX_INLINE || + b->type == BOX_INLINE_BLOCK || + b->type == BOX_TEXT || + b->type == BOX_INLINE_END) { + assert(b->width != UNKNOWN_WIDTH); + + x_previous = x; + x += space_after; + b->x = x; + + if ((b->type == BOX_INLINE && !b->inline_end) || + b->type == BOX_INLINE_BLOCK) { + b->x += b->margin[LEFT] + b->border[LEFT].width; + x = b->x + b->padding[LEFT] + b->width + + b->padding[RIGHT] + + b->border[RIGHT].width + + b->margin[RIGHT]; + } else if (b->type == BOX_INLINE) { + b->x += b->margin[LEFT] + b->border[LEFT].width; + x = b->x + b->padding[LEFT] + b->width; + } else if (b->type == BOX_INLINE_END) { + b->height = b->inline_end->height; + x += b->padding[RIGHT] + + b->border[RIGHT].width + + b->margin[RIGHT]; + } else { + x += b->width; + } + + space_before = space_after; + if (b->object || b->flags & REPLACE_DIM || + b->flags & IFRAME) + space_after = 0; + else if (b->text || b->type == BOX_INLINE_END) { + if (b->space == UNKNOWN_WIDTH) { + font_plot_style_from_css( + &content->len_ctx, + b->style, &fstyle); + /** \todo handle errors */ + font_func->width(&fstyle, " ", 1, + &b->space); + } + space_after = b->space; + } else { + space_after = 0; + } + split_box = b; + move_y = true; + inline_count++; + } else if (b->type == BOX_BR) { + b->x = x; + b->width = 0; + br_box = b; + b = b->next; + split_box = 0; + move_y = true; + break; + + } else { + /* float */ + NSLOG(layout, DEBUG, "float %p", b); + + d = b->children; + d->float_children = 0; + d->cached_place_below_level = 0; + b->float_container = d->float_container = cont; + + if (!layout_float(d, *width, content)) + return false; + + NSLOG(layout, DEBUG, + "%p : %d %d", + d, + d->margin[TOP], + d->border[TOP].width); + + d->x = d->margin[LEFT] + d->border[LEFT].width; + d->y = d->margin[TOP] + d->border[TOP].width; + b->width = d->margin[LEFT] + d->border[LEFT].width + + d->padding[LEFT] + d->width + + d->padding[RIGHT] + + d->border[RIGHT].width + + d->margin[RIGHT]; + b->height = d->margin[TOP] + d->border[TOP].width + + d->padding[TOP] + d->height + + d->padding[BOTTOM] + + d->border[BOTTOM].width + + d->margin[BOTTOM]; + + if (b->width > (x1 - x0) - x) + place_below = true; + if (d->style && (css_computed_clear(d->style) == + CSS_CLEAR_NONE || + (css_computed_clear(d->style) == + CSS_CLEAR_LEFT && left == 0) || + (css_computed_clear(d->style) == + CSS_CLEAR_RIGHT && + right == 0) || + (css_computed_clear(d->style) == + CSS_CLEAR_BOTH && + left == 0 && right == 0)) && + (!place_below || + (left == 0 && right == 0 && x == 0)) && + cy >= cont->clear_level && + cy >= cont->cached_place_below_level) { + /* + not cleared or, + * cleared and there are no floats to clear + * + fits without needing to be placed below or, + * this line is empty with no floats + * + current y, cy, is below the clear level + * + * Float affects current line */ + if (b->type == BOX_FLOAT_LEFT) { + b->x = cx + x0; + if (b->width > 0) + x0 += b->width; + left = b; + } else { + b->x = cx + x1 - b->width; + if (b->width > 0) + x1 -= b->width; + right = b; + } + b->y = cy; + } else { + /* cleared or doesn't fit on line */ + /* place below into next available space */ + int fcy = (cy > cont->clear_level) ? cy : + cont->clear_level; + fcy = (fcy > cont->cached_place_below_level) ? + fcy : + cont->cached_place_below_level; + fy = (fy > fcy) ? fy : fcy; + fy = (fy == cy) ? fy + height : fy; + + place_float_below(b, *width, cx, fy, cont); + fy = b->y; + if (d->style && ( + (css_computed_clear(d->style) == + CSS_CLEAR_LEFT && left != 0) || + (css_computed_clear(d->style) == + CSS_CLEAR_RIGHT && + right != 0) || + (css_computed_clear(d->style) == + CSS_CLEAR_BOTH && + (left != 0 || right != 0)))) { + /* to be cleared below existing + * floats */ + if (b->type == BOX_FLOAT_LEFT) + b->x = cx; + else + b->x = cx + *width - b->width; + + fcy = layout_clear(cont->float_children, + css_computed_clear(d->style)); + if (fcy > cont->clear_level) + cont->clear_level = fcy; + if (b->y < fcy) + b->y = fcy; + } + if (b->type == BOX_FLOAT_LEFT) + left = b; + else + right = b; + } + add_float_to_container(cont, b); + + split_box = 0; + } + } + + if (x1 - x0 < x && split_box) { + /* the last box went over the end */ + size_t split = 0; + int w; + bool no_wrap = css_computed_white_space( + split_box->style) == CSS_WHITE_SPACE_NOWRAP || + css_computed_white_space( + split_box->style) == CSS_WHITE_SPACE_PRE; + + x = x_previous; + + if (!no_wrap && + (split_box->type == BOX_INLINE || + split_box->type == BOX_TEXT) && + !split_box->object && + !(split_box->flags & REPLACE_DIM) && + !(split_box->flags & IFRAME) && + !split_box->gadget && split_box->text) { + + font_plot_style_from_css(&content->len_ctx, + split_box->style, &fstyle); + /** \todo handle errors */ + font_func->split(&fstyle, + split_box->text, + split_box->length, + x1 - x0 - x - space_before, + &split, + &w); + } + + /* split == 0 implies that text can't be split */ + + if (split == 0) + w = split_box->width; + + + NSLOG(layout, DEBUG, + "splitting: split_box %p \"%.*s\", spilt %zu, w %i, " + "left %p, right %p, inline_count %u", + split_box, + (int)split_box->length, + split_box->text, + split, + w, + left, + right, + inline_count); + + if ((split == 0 || x1 - x0 <= x + space_before + w) && + !left && !right && inline_count == 1) { + /* first word of box doesn't fit, but no floats and + * first box on line so force in */ + if (split == 0 || split == split_box->length) { + /* only one word in this box, or not text + * or white-space:nowrap */ + b = split_box->next; + } else { + /* cut off first word for this line */ + if (!layout_text_box_split(content, &fstyle, + split_box, split, w)) + return false; + b = split_box->next; + } + x += space_before + w; + + NSLOG(layout, DEBUG, "forcing"); + + } else if ((split == 0 || x1 - x0 <= x + space_before + w) && + inline_count == 1) { + /* first word of first box doesn't fit, but a float is + * taking some of the width so move below it */ + assert(left || right); + used_height = 0; + if (left) { + + NSLOG(layout, DEBUG, + "cy %i, left->y %i, left->height %i", + cy, + left->y, + left->height); + + used_height = left->y + left->height - cy + 1; + + NSLOG(layout, DEBUG, "used_height %i", + used_height); + + } + if (right && used_height < + right->y + right->height - cy + 1) + used_height = right->y + right->height - cy + 1; + + if (used_height < 0) + used_height = 0; + + b = split_box; + + NSLOG(layout, DEBUG, "moving below float"); + + } else if (split == 0 || x1 - x0 <= x + space_before + w) { + /* first word of box doesn't fit so leave box for next + * line */ + b = split_box; + + NSLOG(layout, DEBUG, "leaving for next line"); + + } else { + /* fit as many words as possible */ + assert(split != 0); + + NSLOG(layout, DEBUG, "'%.*s' %i %zu %i", + (int)split_box->length, split_box->text, + x1 - x0, split, w); + + if (split != split_box->length) { + if (!layout_text_box_split(content, &fstyle, + split_box, split, w)) + return false; + b = split_box->next; + } + x += space_before + w; + + NSLOG(layout, DEBUG, "fitting words"); + + } + move_y = true; + } + + /* set positions */ + switch (css_computed_text_align(first->parent->parent->style)) { + case CSS_TEXT_ALIGN_RIGHT: + case CSS_TEXT_ALIGN_LIBCSS_RIGHT: + x0 = x1 - x; + break; + case CSS_TEXT_ALIGN_CENTER: + case CSS_TEXT_ALIGN_LIBCSS_CENTER: + x0 = (x0 + (x1 - x)) / 2; + break; + case CSS_TEXT_ALIGN_LEFT: + case CSS_TEXT_ALIGN_LIBCSS_LEFT: + case CSS_TEXT_ALIGN_JUSTIFY: + /* leave on left */ + break; + case CSS_TEXT_ALIGN_DEFAULT: + /* None; consider text direction */ + switch (css_computed_direction(first->parent->parent->style)) { + case CSS_DIRECTION_LTR: + /* leave on left */ + break; + case CSS_DIRECTION_RTL: + x0 = x1 - x; + break; + } + break; + } + + for (d = first; d != b; d = d->next) { + d->flags &= ~NEW_LINE; + + if (d->type == BOX_INLINE_BLOCK && + (css_computed_position(d->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(d->style) == + CSS_POSITION_FIXED)) { + /* positioned inline-blocks: + * set static position (x,y) only, rest of positioning + * is handled later */ + d->x += x0; + d->y = *y; + continue; + } else if ((d->type == BOX_INLINE && + ((d->object || d->gadget) == false) && + !(d->flags & IFRAME) && + !(d->flags & REPLACE_DIM)) || + d->type == BOX_BR || + d->type == BOX_TEXT || + d->type == BOX_INLINE_END) { + /* regular (non-replaced) inlines */ + d->x += x0; + d->y = *y - d->padding[TOP]; + + if (d->type == BOX_TEXT && d->height > used_height) { + /* text */ + used_height = d->height; + } + } else if ((d->type == BOX_INLINE) || + d->type == BOX_INLINE_BLOCK) { + /* replaced inlines and inline-blocks */ + d->x += x0; + d->y = *y + d->border[TOP].width + d->margin[TOP]; + h = d->margin[TOP] + d->border[TOP].width + + d->padding[TOP] + d->height + + d->padding[BOTTOM] + + d->border[BOTTOM].width + + d->margin[BOTTOM]; + if (used_height < h) + used_height = h; + } + } + + first->flags |= NEW_LINE; + + assert(b != first || (move_y && 0 < used_height && (left || right))); + + /* handle vertical-align by adjusting box y values */ + /** \todo proper vertical alignment handling */ + for (d = first; d != b; d = d->next) { + if ((d->type == BOX_INLINE && d->inline_end) || + d->type == BOX_BR || + d->type == BOX_TEXT || + d->type == BOX_INLINE_END) { + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + switch (css_computed_vertical_align(d->style, &value, + &unit)) { + case CSS_VERTICAL_ALIGN_SUPER: + case CSS_VERTICAL_ALIGN_TOP: + case CSS_VERTICAL_ALIGN_TEXT_TOP: + /* already at top */ + break; + case CSS_VERTICAL_ALIGN_SUB: + case CSS_VERTICAL_ALIGN_BOTTOM: + case CSS_VERTICAL_ALIGN_TEXT_BOTTOM: + d->y += used_height - d->height; + break; + default: + case CSS_VERTICAL_ALIGN_BASELINE: + d->y += 0.75 * (used_height - d->height); + break; + } + } + } + + /* handle clearance for br */ + if (br_box && css_computed_clear(br_box->style) != CSS_CLEAR_NONE) { + int clear_y = layout_clear(cont->float_children, + css_computed_clear(br_box->style)); + if (used_height < clear_y - cy) + used_height = clear_y - cy; + } + + if (move_y) + *y += used_height; + *next_box = b; + *width = x; /* return actual width */ + return true; +} + + +/** + * Layout lines of text or inline boxes with floats. + * + * \param box inline container box + * \param width horizontal space available + * \param cont ancestor box which defines horizontal space, for floats + * \param cx box position relative to cont + * \param cy box position relative to cont + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool layout_inline_container(struct box *inline_container, int width, + struct box *cont, int cx, int cy, html_content *content) +{ + bool first_line = true; + bool has_text_children; + struct box *c, *next; + int y = 0; + int curwidth,maxwidth = width; + + assert(inline_container->type == BOX_INLINE_CONTAINER); + + NSLOG(layout, DEBUG, + "inline_container %p, width %i, cont %p, cx %i, cy %i", + inline_container, + width, + cont, + cx, + cy); + + + has_text_children = false; + for (c = inline_container->children; c; c = c->next) { + bool is_pre = false; + + if (c->style) { + enum css_white_space_e whitespace; + + whitespace = css_computed_white_space(c->style); + + is_pre = (whitespace == CSS_WHITE_SPACE_PRE || + whitespace == CSS_WHITE_SPACE_PRE_LINE || + whitespace == CSS_WHITE_SPACE_PRE_WRAP); + } + + if ((!c->object && !(c->flags & REPLACE_DIM) && + !(c->flags & IFRAME) && + c->text && (c->length || is_pre)) || + c->type == BOX_BR) + has_text_children = true; + } + + /** \todo fix wrapping so that a box with horizontal scrollbar will + * shrink back to 'width' if no word is wider than 'width' (Or just set + * curwidth = width and have the multiword lines wrap to the min width) + */ + for (c = inline_container->children; c; ) { + + NSLOG(layout, DEBUG, "c %p", c); + + curwidth = inline_container->width; + if (!layout_line(c, &curwidth, &y, cx, cy + y, cont, first_line, + has_text_children, content, &next)) + return false; + maxwidth = max(maxwidth,curwidth); + c = next; + first_line = false; + } + + inline_container->width = maxwidth; + inline_container->height = y; + + return true; +} + + +/** + * Layout a block formatting context. + * + * \param block BLOCK, INLINE_BLOCK, or TABLE_CELL to layout + * \param viewport_height Height of viewport in pixels or -ve if unknown + * \param content Memory pool for any new boxes + * \return true on success, false on memory exhaustion + * + * This function carries out layout of a block and its children, as described + * in CSS 2.1 9.4.1. + */ +static bool +layout_block_context(struct box *block, + int viewport_height, + html_content *content) +{ + struct box *box; + int cx, cy; /**< current coordinates */ + int max_pos_margin = 0; + int max_neg_margin = 0; + int y = 0; + int lm, rm; + struct box *margin_collapse = NULL; + bool in_margin = false; + css_fixed gadget_size; + css_unit gadget_unit; /* Checkbox / radio buttons */ + + assert(block->type == BOX_BLOCK || + block->type == BOX_INLINE_BLOCK || + block->type == BOX_TABLE_CELL); + assert(block->width != UNKNOWN_WIDTH); + assert(block->width != AUTO); + + block->float_children = NULL; + block->cached_place_below_level = 0; + block->clear_level = 0; + + /* special case if the block contains an object */ + if (block->object) { + int temp_width = block->width; + if (!layout_block_object(block)) + return false; + layout_get_object_dimensions(block, &temp_width, + &block->height, INT_MIN, INT_MAX, + INT_MIN, INT_MAX); + return true; + } else if (block->flags & REPLACE_DIM) { + return true; + } + + /* special case if the block contains an radio button or checkbox */ + if (block->gadget && (block->gadget->type == GADGET_RADIO || + block->gadget->type == GADGET_CHECKBOX)) { + /* form checkbox or radio button + * if width or height is AUTO, set it to 1em */ + gadget_unit = CSS_UNIT_EM; + gadget_size = INTTOFIX(1); + if (block->height == AUTO) + block->height = FIXTOINT(nscss_len2px( + &content->len_ctx, gadget_size, + gadget_unit, block->style)); + } + + box = block->children; + /* set current coordinates to top-left of the block */ + cx = 0; + y = cy = block->padding[TOP]; + if (box) + box->y = block->padding[TOP]; + + /* Step through the descendants of the block in depth-first order, but + * not into the children of boxes which aren't blocks. For example, if + * the tree passed to this function looks like this (box->type shown): + * + * block -> BOX_BLOCK + * BOX_BLOCK * (1) + * BOX_INLINE_CONTAINER * (2) + * BOX_INLINE + * BOX_TEXT + * ... + * BOX_BLOCK * (3) + * BOX_TABLE * (4) + * BOX_TABLE_ROW + * BOX_TABLE_CELL + * ... + * BOX_TABLE_CELL + * ... + * BOX_BLOCK * (5) + * BOX_INLINE_CONTAINER * (6) + * BOX_TEXT + * ... + * then the while loop will visit each box marked with *, setting box + * to each in the order shown. */ + while (box) { + enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; + enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; + + assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || + box->type == BOX_INLINE_CONTAINER); + + /* Tables are laid out before being positioned, because the + * position depends on the width which is calculated in + * table layout. Blocks and inline containers are positioned + * before being laid out, because width is not dependent on + * content, and the position is required during layout for + * correct handling of floats. + */ + + if (box->style && + (css_computed_position(box->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(box->style) == + CSS_POSITION_FIXED)) { + box->x = box->parent->padding[LEFT]; + /* absolute positioned; this element will establish + * its own block context when it gets laid out later, + * so no need to look at its children now. */ + goto advance_to_next_box; + } + + /* If we don't know which box the current margin collapses + * through to, find out. Update the pos/neg margin values. */ + if (margin_collapse == NULL) { + margin_collapse = layout_next_margin_block( + &content->len_ctx, box, block, + viewport_height, + &max_pos_margin, &max_neg_margin); + /* We have a margin that has not yet been applied. */ + in_margin = true; + } + + /* Clearance. */ + y = 0; + if (box->style && css_computed_clear(box->style) != + CSS_CLEAR_NONE) + y = layout_clear(block->float_children, + css_computed_clear(box->style)); + + /* Find box's overflow properties */ + if (box->style) { + overflow_x = css_computed_overflow_x(box->style); + overflow_y = css_computed_overflow_y(box->style); + } + + /* Blocks establishing a block formatting context get minimum + * left and right margins to avoid any floats. */ + lm = rm = 0; + + if (box->type == BOX_BLOCK || box->flags & IFRAME) { + if (!box->object && !(box->flags & IFRAME) && + !(box->flags & REPLACE_DIM) && + box->style && + (overflow_x != CSS_OVERFLOW_VISIBLE || + overflow_y != CSS_OVERFLOW_VISIBLE)) { + /* box establishes new block formatting context + * so available width may be diminished due to + * floats. */ + int x0, x1, top; + struct box *left, *right; + top = cy + max_pos_margin - max_neg_margin; + top = (top > y) ? top : y; + x0 = cx; + x1 = cx + box->parent->width - + box->parent->padding[LEFT] - + box->parent->padding[RIGHT]; + find_sides(block->float_children, top, top, + &x0, &x1, &left, &right); + /* calculate min required left & right margins + * needed to avoid floats */ + lm = x0 - cx; + rm = cx + box->parent->width - + box->parent->padding[LEFT] - + box->parent->padding[RIGHT] - + x1; + } + layout_block_find_dimensions(&content->len_ctx, + box->parent->width, + viewport_height, lm, rm, box); + if (box->type == BOX_BLOCK && !(box->flags & IFRAME)) { + layout_block_add_scrollbar(box, RIGHT); + layout_block_add_scrollbar(box, BOTTOM); + } + } else if (box->type == BOX_TABLE) { + if (box->style != NULL) { + enum css_width_e wtype; + css_fixed width = 0; + css_unit unit = CSS_UNIT_PX; + + wtype = css_computed_width(box->style, &width, + &unit); + + if (wtype == CSS_WIDTH_AUTO) { + /* max available width may be + * diminished due to floats. */ + int x0, x1, top; + struct box *left, *right; + top = cy + max_pos_margin - + max_neg_margin; + top = (top > y) ? top : y; + x0 = cx; + x1 = cx + box->parent->width - + box->parent->padding[LEFT] - + box->parent->padding[RIGHT]; + find_sides(block->float_children, + top, top, &x0, &x1, + &left, &right); + /* calculate min required left & right + * margins needed to avoid floats */ + lm = x0 - cx; + rm = cx + box->parent->width - + box->parent->padding[LEFT] - + box->parent->padding[RIGHT] - + x1; + } + } + if (!layout_table(box, box->parent->width - lm - rm, + content)) + return false; + layout_solve_width(box, box->parent->width, box->width, + lm, rm, -1, -1); + } + + /* Position box: horizontal. */ + box->x = box->parent->padding[LEFT] + box->margin[LEFT] + + box->border[LEFT].width; + cx += box->x; + + /* Position box: vertical. */ + if (box->border[TOP].width) { + box->y += box->border[TOP].width; + cy += box->border[TOP].width; + } + + /* Vertical margin */ + if (((box->type == BOX_BLOCK && + (box->flags & HAS_HEIGHT)) || + box->type == BOX_TABLE || + (box->type == BOX_INLINE_CONTAINER && + box != box->parent->children) || + margin_collapse == box) && + in_margin == true) { + /* Margin goes above this box. */ + cy += max_pos_margin - max_neg_margin; + box->y += max_pos_margin - max_neg_margin; + + /* Current margin has been applied. */ + in_margin = false; + max_pos_margin = max_neg_margin = 0; + } + + /* Handle clearance */ + if (box->type != BOX_INLINE_CONTAINER && + (y > 0) && (cy < y)) { + /* box clears something*/ + box->y += y - cy; + cy = y; + } + + /* Unless the box has an overflow style of visible, the box + * establishes a new block context. */ + if (box->type == BOX_BLOCK && box->style && + (overflow_x != CSS_OVERFLOW_VISIBLE || + overflow_y != CSS_OVERFLOW_VISIBLE)) { + + layout_block_context(box, viewport_height, content); + + cy += box->padding[TOP]; + + if (box->height == AUTO) { + box->height = 0; + layout_block_add_scrollbar(box, BOTTOM); + } + + cx -= box->x; + cy += box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width; + y = box->y + box->padding[TOP] + box->height + + box->padding[BOTTOM] + + box->border[BOTTOM].width; + + /* Skip children, because they are done in the new + * block context */ + goto advance_to_next_box; + } + + NSLOG(layout, DEBUG, "box %p, cx %i, cy %i", box, cx, cy); + + /* Layout (except tables). */ + if (box->object) { + if (!layout_block_object(box)) + return false; + + } else if (box->type == BOX_INLINE_CONTAINER) { + box->width = box->parent->width; + if (!layout_inline_container(box, box->width, block, + cx, cy, content)) + return false; + + } else if (box->type == BOX_TABLE) { + /* Move down to avoid floats if necessary. */ + int x0, x1; + struct box *left, *right; + y = cy; + while (1) { + enum css_width_e wtype; + css_fixed width = 0; + css_unit unit = CSS_UNIT_PX; + + wtype = css_computed_width(box->style, + &width, &unit); + + x0 = cx; + x1 = cx + box->parent->width; + find_sides(block->float_children, y, + y + box->height, + &x0, &x1, &left, &right); + if (wtype == CSS_WIDTH_AUTO) + break; + if (box->width <= x1 - x0) + break; + if (!left && !right) + break; + else if (!left) + y = right->y + right->height + 1; + else if (!right) + y = left->y + left->height + 1; + else if (left->y + left->height < + right->y + right->height) + y = left->y + left->height + 1; + else + y = right->y + right->height + 1; + } + box->x += x0 - cx; + cx = x0; + box->y += y - cy; + cy = y; + } + + /* Advance to next box. */ + if (box->type == BOX_BLOCK && !box->object && !(box->iframe) && + box->children) { + /* Down into children. */ + + if (box == margin_collapse) { + /* Current margin collapsed though to this box. + * Unset margin_collapse. */ + margin_collapse = NULL; + } + + y = box->padding[TOP]; + box = box->children; + box->y = y; + cy += y; + continue; + } else if (box->type == BOX_BLOCK || box->object || + box->flags & IFRAME) + cy += box->padding[TOP]; + + if (box->type == BOX_BLOCK && box->height == AUTO) { + box->height = 0; + layout_block_add_scrollbar(box, BOTTOM); + } + + cy += box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width; + cx -= box->x; + y = box->y + box->padding[TOP] + box->height + + box->padding[BOTTOM] + + box->border[BOTTOM].width; + + advance_to_next_box: + if (!box->next) { + /* No more siblings: + * up to first ancestor with a sibling. */ + + do { + if (box == margin_collapse) { + /* Current margin collapsed though to + * this box. Unset margin_collapse. */ + margin_collapse = NULL; + } + + /* Apply bottom margin */ + if (max_pos_margin < box->margin[BOTTOM]) + max_pos_margin = box->margin[BOTTOM]; + else if (max_neg_margin < -box->margin[BOTTOM]) + max_neg_margin = -box->margin[BOTTOM]; + + box = box->parent; + if (box == block) + break; + + /* Margin is invalidated if this is a box + * margins can't collapse through. */ + if (box->type == BOX_BLOCK && + box->flags & MAKE_HEIGHT) { + margin_collapse = NULL; + in_margin = false; + max_pos_margin = max_neg_margin = 0; + } + + if (box->height == AUTO) { + box->height = y - box->padding[TOP]; + + if (box->type == BOX_BLOCK) + layout_block_add_scrollbar(box, + BOTTOM); + } else + cy += box->height - + (y - box->padding[TOP]); + + /* Apply any min-height and max-height to + * boxes in normal flow */ + if (box->style && + css_computed_position(box->style) != + CSS_POSITION_ABSOLUTE && + layout_apply_minmax_height( + &content->len_ctx, + box, NULL)) { + /* Height altered */ + /* Set current cy */ + cy += box->height - + (y - box->padding[TOP]); + } + + cy += box->padding[BOTTOM] + + box->border[BOTTOM].width; + cx -= box->x; + y = box->y + box->padding[TOP] + box->height + + box->padding[BOTTOM] + + box->border[BOTTOM].width; + + } while (box->next == NULL); + if (box == block) + break; + } + + /* To next sibling. */ + + if (box == margin_collapse) { + /* Current margin collapsed though to this box. + * Unset margin_collapse. */ + margin_collapse = NULL; + } + + if (max_pos_margin < box->margin[BOTTOM]) + max_pos_margin = box->margin[BOTTOM]; + else if (max_neg_margin < -box->margin[BOTTOM]) + max_neg_margin = -box->margin[BOTTOM]; + + box = box->next; + box->y = y; + } + + /* Account for bottom margin of last contained block */ + cy += max_pos_margin - max_neg_margin; + + /* Increase height to contain any floats inside (CSS 2.1 10.6.7). */ + for (box = block->float_children; box; box = box->next_float) { + y = box->y + box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width + box->margin[BOTTOM]; + if (cy < y) + cy = y; + } + + if (block->height == AUTO) { + block->height = cy - block->padding[TOP]; + if (block->type == BOX_BLOCK) + layout_block_add_scrollbar(block, BOTTOM); + } + + if (block->style && css_computed_position(block->style) != + CSS_POSITION_ABSOLUTE) { + /* Block is in normal flow */ + layout_apply_minmax_height(&content->len_ctx, block, NULL); + } + + if (block->gadget && + (block->gadget->type == GADGET_TEXTAREA || + block->gadget->type == GADGET_PASSWORD || + block->gadget->type == GADGET_TEXTBOX)) { + plot_font_style_t fstyle; + int ta_width = block->padding[LEFT] + block->width + + block->padding[RIGHT]; + int ta_height = block->padding[TOP] + block->height + + block->padding[BOTTOM]; + font_plot_style_from_css(&content->len_ctx, + block->style, &fstyle); + fstyle.background = NS_TRANSPARENT; + textarea_set_layout(block->gadget->data.text.ta, + &fstyle, ta_width, ta_height, + block->padding[TOP], block->padding[RIGHT], + block->padding[BOTTOM], block->padding[LEFT]); + } + + return true; +} + + +/** + * Layout list markers. + */ +static void +layout_lists(struct box *box, + const struct gui_layout_table *font_func, + const nscss_len_ctx *len_ctx) +{ + struct box *child; + struct box *marker; + plot_font_style_t fstyle; + + for (child = box->children; child; child = child->next) { + if (child->list_marker) { + marker = child->list_marker; + if (marker->object) { + marker->width = + content_get_width(marker->object); + marker->x = -marker->width; + marker->height = + content_get_height(marker->object); + marker->y = (line_height(len_ctx, + marker->style) - + marker->height) / 2; + } else if (marker->text) { + if (marker->width == UNKNOWN_WIDTH) { + font_plot_style_from_css(len_ctx, + marker->style, &fstyle); + font_func->width(&fstyle, + marker->text, + marker->length, + &marker->width); + marker->flags |= MEASURED; + } + marker->x = -marker->width; + marker->y = 0; + marker->height = line_height(len_ctx, + marker->style); + } else { + marker->x = 0; + marker->y = 0; + marker->width = 0; + marker->height = 0; + } + /* Gap between marker and content */ + marker->x -= 4; + } + layout_lists(child, font_func, len_ctx); + } +} + + +/** + * Compute box offsets for a relatively or absolutely positioned box with + * respect to a box. + * + * \param len_ctx Length conversion context + * \param box box to compute offsets for + * \param containing_block box to compute percentages with respect to + * \param top updated to top offset, or AUTO + * \param right updated to right offset, or AUTO + * \param bottom updated to bottom offset, or AUTO + * \param left updated to left offset, or AUTO + * + * See CSS 2.1 9.3.2. containing_block must have width and height. + */ +static void +layout_compute_offsets(const nscss_len_ctx *len_ctx, + struct box *box, + struct box *containing_block, + int *top, + int *right, + int *bottom, + int *left) +{ + uint32_t type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(containing_block->width != UNKNOWN_WIDTH && + containing_block->width != AUTO && + containing_block->height != AUTO); + + /* left */ + type = css_computed_left(box->style, &value, &unit); + if (type == CSS_LEFT_SET) { + if (unit == CSS_UNIT_PCT) { + *left = FPCT_OF_INT_TOINT(value, + containing_block->width); + } else { + *left = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + } + } else { + *left = AUTO; + } + + /* right */ + type = css_computed_right(box->style, &value, &unit); + if (type == CSS_RIGHT_SET) { + if (unit == CSS_UNIT_PCT) { + *right = FPCT_OF_INT_TOINT(value, + containing_block->width); + } else { + *right = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + } + } else { + *right = AUTO; + } + + /* top */ + type = css_computed_top(box->style, &value, &unit); + if (type == CSS_TOP_SET) { + if (unit == CSS_UNIT_PCT) { + *top = FPCT_OF_INT_TOINT(value, + containing_block->height); + } else { + *top = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + } + } else { + *top = AUTO; + } + + /* bottom */ + type = css_computed_bottom(box->style, &value, &unit); + if (type == CSS_BOTTOM_SET) { + if (unit == CSS_UNIT_PCT) { + *bottom = FPCT_OF_INT_TOINT(value, + containing_block->height); + } else { + *bottom = FIXTOINT(nscss_len2px(len_ctx, + value, unit, box->style)); + } + } else { + *bottom = AUTO; + } +} + + +/** + * Layout and position an absolutely positioned box. + * + * \param box absolute box to layout and position + * \param containing_block containing block + * \param cx position of box relative to containing_block + * \param cy position of box relative to containing_block + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool +layout_absolute(struct box *box, + struct box *containing_block, + int cx, int cy, + html_content *content) +{ + int static_left, static_top; /* static position */ + int top, right, bottom, left; + int width, height, max_width, min_width; + int *margin = box->margin; + int *padding = box->padding; + struct box_border *border = box->border; + int available_width = containing_block->width; + int space; + + assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || + box->type == BOX_INLINE_BLOCK); + + /* The static position is where the box would be if it was not + * absolutely positioned. The x and y are filled in by + * layout_block_context(). */ + static_left = cx + box->x; + static_top = cy + box->y; + + if (containing_block->type == BOX_BLOCK || + containing_block->type == BOX_INLINE_BLOCK || + containing_block->type == BOX_TABLE_CELL) { + /* Block level container => temporarily increase containing + * block dimensions to include padding (we restore this + * again at the end) */ + containing_block->width += containing_block->padding[LEFT] + + containing_block->padding[RIGHT]; + containing_block->height += containing_block->padding[TOP] + + containing_block->padding[BOTTOM]; + } else { + /** \todo inline containers */ + } + + layout_compute_offsets(&content->len_ctx, box, containing_block, + &top, &right, &bottom, &left); + + /* Pass containing block into layout_find_dimensions via the float + * containing block box member. This is unused for absolutely positioned + * boxes because a box can't be floated and absolutely positioned. */ + box->float_container = containing_block; + layout_find_dimensions(&content->len_ctx, available_width, -1, + box, box->style, &width, &height, + &max_width, &min_width, 0, 0, + margin, padding, border); + box->float_container = NULL; + + /* 10.3.7 */ + NSLOG(layout, DEBUG, + "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", + left, margin[LEFT], border[LEFT].width, padding[LEFT], width, + padding[RIGHT], border[RIGHT].width, margin[RIGHT], right, + containing_block->width); + + + if (left == AUTO && width == AUTO && right == AUTO) { + if (margin[LEFT] == AUTO) + margin[LEFT] = 0; + if (margin[RIGHT] == AUTO) + margin[RIGHT] = 0; + left = static_left; + + width = min(max(box->min_width, available_width), + box->max_width); + width -= box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + box->padding[RIGHT] + + box->border[RIGHT].width + box->margin[RIGHT]; + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) width = max_width; + if (width < min_width) width = min_width; + + right = containing_block->width - + left - + margin[LEFT] - border[LEFT].width - padding[LEFT] - + width - + padding[RIGHT] - border[RIGHT].width - margin[RIGHT]; + } else if (left != AUTO && width != AUTO && right != AUTO) { + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) width = max_width; + if (min_width > 0 && width < min_width) width = min_width; + + if (margin[LEFT] == AUTO && margin[RIGHT] == AUTO) { + space = containing_block->width - + left - border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - right; + if (space < 0) { + margin[LEFT] = 0; + margin[RIGHT] = space; + } else { + margin[LEFT] = margin[RIGHT] = space / 2; + } + } else if (margin[LEFT] == AUTO) { + margin[LEFT] = containing_block->width - + left - border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT] - + right; + } else if (margin[RIGHT] == AUTO) { + margin[RIGHT] = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - right; + } else { + right = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT]; + } + } else { + if (margin[LEFT] == AUTO) + margin[LEFT] = 0; + if (margin[RIGHT] == AUTO) + margin[RIGHT] = 0; + + if (left == AUTO && width == AUTO && right != AUTO) { + available_width -= right; + + width = min(max(box->min_width, available_width), + box->max_width); + width -= box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + box->padding[RIGHT] + + box->border[RIGHT].width + box->margin[RIGHT]; + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + left = containing_block->width - + margin[LEFT] - border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT] - + right; + } else if (left == AUTO && width != AUTO && right == AUTO) { + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (min_width > 0 && width < min_width) + width = min_width; + + left = static_left; + right = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT]; + } else if (left != AUTO && width == AUTO && right == AUTO) { + available_width -= left; + + width = min(max(box->min_width, available_width), + box->max_width); + width -= box->margin[LEFT] + box->border[LEFT].width + + box->padding[LEFT] + box->padding[RIGHT] + + box->border[RIGHT].width + box->margin[RIGHT]; + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + right = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT]; + } else if (left == AUTO && width != AUTO && right != AUTO) { + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + left = containing_block->width - + margin[LEFT] - border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT] - + right; + } else if (left != AUTO && width == AUTO && right != AUTO) { + width = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT] - + right; + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + } else if (left != AUTO && width != AUTO && right == AUTO) { + + /* Adjust for {min|max}-width */ + if (max_width >= 0 && width > max_width) + width = max_width; + if (width < min_width) + width = min_width; + + right = containing_block->width - + left - margin[LEFT] - + border[LEFT].width - + padding[LEFT] - width - padding[RIGHT] - + border[RIGHT].width - margin[RIGHT]; + } + } + + NSLOG(layout, DEBUG, + "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", + left, margin[LEFT], border[LEFT].width, padding[LEFT], width, + padding[RIGHT], border[RIGHT].width, margin[RIGHT], right, + containing_block->width); + + box->x = left + margin[LEFT] + border[LEFT].width - cx; + if (containing_block->type == BOX_BLOCK || + containing_block->type == BOX_INLINE_BLOCK || + containing_block->type == BOX_TABLE_CELL) { + /* Block-level ancestor => reset container's width */ + containing_block->width -= containing_block->padding[LEFT] + + containing_block->padding[RIGHT]; + } else { + /** \todo inline ancestors */ + } + box->width = width; + box->height = height; + + if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || + box->object || box->flags & IFRAME) { + if (!layout_block_context(box, -1, content)) + return false; + } else if (box->type == BOX_TABLE) { + /* layout_table also expects the containing block to be + * stored in the float_container field */ + box->float_container = containing_block; + /* \todo layout_table considers margins etc. again */ + if (!layout_table(box, width, content)) + return false; + box->float_container = NULL; + layout_solve_width(box, box->parent->width, box->width, 0, 0, + -1, -1); + } + + /* 10.6.4 */ + NSLOG(layout, DEBUG, + "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", + top, margin[TOP], border[TOP].width, padding[TOP], height, + padding[BOTTOM], border[BOTTOM].width, margin[BOTTOM], bottom, + containing_block->height); + + if (top == AUTO && height == AUTO && bottom == AUTO) { + top = static_top; + height = box->height; + if (margin[TOP] == AUTO) + margin[TOP] = 0; + if (margin[BOTTOM] == AUTO) + margin[BOTTOM] = 0; + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - padding[BOTTOM] - + border[BOTTOM].width - margin[BOTTOM]; + } else if (top != AUTO && height != AUTO && bottom != AUTO) { + if (margin[TOP] == AUTO && margin[BOTTOM] == AUTO) { + space = containing_block->height - + top - border[TOP].width - padding[TOP] - + height - padding[BOTTOM] - + border[BOTTOM].width - bottom; + margin[TOP] = margin[BOTTOM] = space / 2; + } else if (margin[TOP] == AUTO) { + margin[TOP] = containing_block->height - + top - border[TOP].width - padding[TOP] - + height - padding[BOTTOM] - + border[BOTTOM].width - margin[BOTTOM] - + bottom; + } else if (margin[BOTTOM] == AUTO) { + margin[BOTTOM] = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + bottom; + } else { + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM]; + } + } else { + if (margin[TOP] == AUTO) + margin[TOP] = 0; + if (margin[BOTTOM] == AUTO) + margin[BOTTOM] = 0; + if (top == AUTO && height == AUTO && bottom != AUTO) { + height = box->height; + top = containing_block->height - + margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM] - bottom; + } else if (top == AUTO && height != AUTO && bottom == AUTO) { + top = static_top; + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM]; + } else if (top != AUTO && height == AUTO && bottom == AUTO) { + height = box->height; + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM]; + } else if (top == AUTO && height != AUTO && bottom != AUTO) { + top = containing_block->height - + margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM] - bottom; + } else if (top != AUTO && height == AUTO && bottom != AUTO) { + height = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - padding[BOTTOM] - + border[BOTTOM].width - margin[BOTTOM] - + bottom; + } else if (top != AUTO && height != AUTO && bottom == AUTO) { + bottom = containing_block->height - + top - margin[TOP] - border[TOP].width - + padding[TOP] - height - + padding[BOTTOM] - border[BOTTOM].width - + margin[BOTTOM]; + } + } + + NSLOG(layout, DEBUG, + "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", + top, margin[TOP], border[TOP].width, padding[TOP], height, + padding[BOTTOM], border[BOTTOM].width, margin[BOTTOM], bottom, + containing_block->height); + + box->y = top + margin[TOP] + border[TOP].width - cy; + if (containing_block->type == BOX_BLOCK || + containing_block->type == BOX_INLINE_BLOCK || + containing_block->type == BOX_TABLE_CELL) { + /* Block-level ancestor => reset container's height */ + containing_block->height -= containing_block->padding[TOP] + + containing_block->padding[BOTTOM]; + } else { + /** \todo Inline ancestors */ + } + box->height = height; + layout_apply_minmax_height(&content->len_ctx, box, containing_block); + + return true; +} + + +/** + * Recursively layout and position absolutely positioned boxes. + * + * \param box tree of boxes to layout + * \param containing_block current containing block + * \param cx position of box relative to containing_block + * \param cy position of box relative to containing_block + * \param content memory pool for any new boxes + * \return true on success, false on memory exhaustion + */ +static bool +layout_position_absolute(struct box *box, + struct box *containing_block, + int cx, int cy, + html_content *content) +{ + struct box *c; + + for (c = box->children; c; c = c->next) { + if ((c->type == BOX_BLOCK || c->type == BOX_TABLE || + c->type == BOX_INLINE_BLOCK) && + (css_computed_position(c->style) == + CSS_POSITION_ABSOLUTE || + css_computed_position(c->style) == + CSS_POSITION_FIXED)) { + if (!layout_absolute(c, containing_block, + cx, cy, content)) + return false; + if (!layout_position_absolute(c, c, 0, 0, content)) + return false; + } else if (c->style && css_computed_position(c->style) == + CSS_POSITION_RELATIVE) { + if (!layout_position_absolute(c, c, 0, 0, content)) + return false; + } else { + int px, py; + if (c->style && (css_computed_float(c->style) == + CSS_FLOAT_LEFT || + css_computed_float(c->style) == + CSS_FLOAT_RIGHT)) { + /* Float x/y coords are relative to nearest + * ansestor with float_children, rather than + * relative to parent. Need to get x/y relative + * to parent */ + struct box *p; + px = c->x; + py = c->y; + for (p = box->parent; p && !p->float_children; + p = p->parent) { + px -= p->x; + py -= p->y; + } + } else { + /* Not a float, so box x/y coords are relative + * to parent */ + px = c->x; + py = c->y; + } + if (!layout_position_absolute(c, containing_block, + cx + px, cy + py, content)) + return false; + } + } + + return true; +} + + +/** + * Compute a box's relative offset as per CSS 2.1 9.4.3 + * + * \param len_ctx Length conversion context + * \param box Box to compute relative offsets for. + * \param x Receives relative offset in x. + * \param y Receives relative offset in y. + */ +static void layout_compute_relative_offset( + const nscss_len_ctx *len_ctx, + struct box *box, + int *x, + int *y) +{ + int left, right, top, bottom; + struct box *containing_block; + + assert(box && box->parent && box->style && + css_computed_position(box->style) == + CSS_POSITION_RELATIVE); + + if (box->float_container && + (css_computed_float(box->style) == CSS_FLOAT_LEFT || + css_computed_float(box->style) == CSS_FLOAT_RIGHT)) { + containing_block = box->float_container; + } else { + containing_block = box->parent; + } + + layout_compute_offsets(len_ctx, box, containing_block, + &top, &right, &bottom, &left); + + if (left == AUTO && right == AUTO) + left = right = 0; + else if (left == AUTO) + /* left is auto => computed = -right */ + left = -right; + else if (right == AUTO) + /* right is auto => computed = -left */ + right = -left; + else { + /* over constrained => examine direction property + * of containing block */ + if (containing_block->style && + css_computed_direction( + containing_block->style) == + CSS_DIRECTION_RTL) { + /* right wins */ + left = -right; + } else { + /* assume LTR in all other cases */ + right = -left; + } + } + + assert(left == -right); + + if (top == AUTO && bottom == AUTO) { + top = bottom = 0; + } else if (top == AUTO) { + top = -bottom; + } else { + /* bottom is AUTO, or neither are AUTO */ + bottom = -top; + } + + NSLOG(layout, DEBUG, "left %i, right %i, top %i, bottom %i", left, + right, top, bottom); + + *x = left; + *y = top; +} + + +/** + * Adjust positions of relatively positioned boxes. + * + * \param len_ctx Length conversion context + * \param root box to adjust the position of + * \param fp box which forms the block formatting context for children of + * "root" which are floats + * \param fx x offset due to intervening relatively positioned boxes + * between current box, "root", and the block formatting context + * box, "fp", for float children of "root" + * \param fy y offset due to intervening relatively positioned boxes + * between current box, "root", and the block formatting context + * box, "fp", for float children of "root" + */ +static void +layout_position_relative( + const nscss_len_ctx *len_ctx, + struct box *root, + struct box *fp, + int fx, + int fy) +{ + struct box *box; /* for children of "root" */ + struct box *fn; /* for block formatting context box for children of + * "box" */ + struct box *fc; /* for float children of the block formatting context, + * "fp" */ + int x, y; /* for the offsets resulting from any relative + * positioning on the current block */ + int fnx, fny; /* for affsets which apply to flat children of "box" */ + + /**\todo ensure containing box is large enough after moving boxes */ + + assert(root); + + /* Normal children */ + for (box = root->children; box; box = box->next) { + + if (box->type == BOX_TEXT) + continue; + + /* If relatively positioned, get offsets */ + if (box->style && css_computed_position(box->style) == + CSS_POSITION_RELATIVE) + layout_compute_relative_offset( + len_ctx, box, &x, &y); + else + x = y = 0; + + /* Adjust float coordinates. + * (note float x and y are relative to their block formatting + * context box and not their parent) */ + if (box->style && (css_computed_float(box->style) == + CSS_FLOAT_LEFT || + css_computed_float(box->style) == + CSS_FLOAT_RIGHT) && + (fx != 0 || fy != 0)) { + /* box is a float and there is a float offset to + * apply */ + for (fc = fp->float_children; fc; fc = fc->next_float) { + if (box == fc->children) { + /* Box is floated in the block + * formatting context block, fp. + * Apply float offsets. */ + box->x += fx; + box->y += fy; + fx = fy = 0; + } + } + } + + if (box->float_children) { + fn = box; + fnx = fny = 0; + } else { + fn = fp; + fnx = fx + x; + fny = fy + y; + } + + /* recurse first */ + layout_position_relative(len_ctx, box, fn, fnx, fny); + + /* Ignore things we're not interested in. */ + if (!box->style || (box->style && + css_computed_position(box->style) != + CSS_POSITION_RELATIVE)) + continue; + + box->x += x; + box->y += y; + + /* Handle INLINEs - their "children" are in fact + * the sibling boxes between the INLINE and + * INLINE_END boxes */ + if (box->type == BOX_INLINE && box->inline_end) { + struct box *b; + for (b = box->next; b && b != box->inline_end; + b = b->next) { + b->x += x; + b->y += y; + } + } + } +} + + +/** + * Find a box's bounding box relative to itself, i.e. the box's border edge box + * + * \param len_ctx Length conversion context + * \param box box find bounding box of + * \param desc_x0 updated to left of box's bbox + * \param desc_y0 updated to top of box's bbox + * \param desc_x1 updated to right of box's bbox + * \param desc_y1 updated to bottom of box's bbox + */ +static void +layout_get_box_bbox( + const nscss_len_ctx *len_ctx, + struct box *box, + int *desc_x0, int *desc_y0, + int *desc_x1, int *desc_y1) +{ + *desc_x0 = -box->border[LEFT].width; + *desc_y0 = -box->border[TOP].width; + *desc_x1 = box->padding[LEFT] + box->width + box->padding[RIGHT] + + box->border[RIGHT].width; + *desc_y1 = box->padding[TOP] + box->height + box->padding[BOTTOM] + + box->border[BOTTOM].width; + + /* To stop the top of text getting clipped when css line-height is + * reduced, we increase the top of the descendant bbox. */ + if (box->type == BOX_BLOCK && box->style != NULL && + css_computed_overflow_y(box->style) == + CSS_OVERFLOW_VISIBLE && + box->object == NULL) { + css_fixed font_size = 0; + css_unit font_unit = CSS_UNIT_PT; + int text_height; + + css_computed_font_size(box->style, &font_size, &font_unit); + text_height = nscss_len2px(len_ctx, font_size, font_unit, + box->style); + text_height = FIXTOINT(text_height * 3 / 4); + *desc_y0 = (*desc_y0 < -text_height) ? *desc_y0 : -text_height; + } +} + + +/** + * Apply changes to box descendant_[xy][01] values due to given child. + * + * \param len_ctx Length conversion context + * \param box box to update + * \param child a box, which may affect box's descendant bbox + * \param off_x offset to apply to child->x coord to treat as child of box + * \param off_y offset to apply to child->y coord to treat as child of box + */ +static void +layout_update_descendant_bbox( + const nscss_len_ctx *len_ctx, + struct box *box, + struct box *child, + int off_x, + int off_y) +{ + int child_desc_x0, child_desc_y0, child_desc_x1, child_desc_y1; + + /* get coordinates of child relative to box */ + int child_x = child->x - off_x; + int child_y = child->y - off_y; + + bool html_object = (child->object && + content_get_type(child->object) == CONTENT_HTML); + + enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; + enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; + + if (child->style != NULL) { + overflow_x = css_computed_overflow_x(child->style); + overflow_y = css_computed_overflow_y(child->style); + } + + /* Get child's border edge */ + layout_get_box_bbox(len_ctx, child, + &child_desc_x0, &child_desc_y0, + &child_desc_x1, &child_desc_y1); + + if (overflow_x == CSS_OVERFLOW_VISIBLE && + html_object == false) { + /* get child's descendant bbox relative to box */ + child_desc_x0 = child->descendant_x0; + child_desc_x1 = child->descendant_x1; + } + if (overflow_y == CSS_OVERFLOW_VISIBLE && + html_object == false) { + /* get child's descendant bbox relative to box */ + child_desc_y0 = child->descendant_y0; + child_desc_y1 = child->descendant_y1; + } + + child_desc_x0 += child_x; + child_desc_y0 += child_y; + child_desc_x1 += child_x; + child_desc_y1 += child_y; + + /* increase box's descendant bbox to contain descendants */ + if (child_desc_x0 < box->descendant_x0) + box->descendant_x0 = child_desc_x0; + if (child_desc_y0 < box->descendant_y0) + box->descendant_y0 = child_desc_y0; + if (box->descendant_x1 < child_desc_x1) + box->descendant_x1 = child_desc_x1; + if (box->descendant_y1 < child_desc_y1) + box->descendant_y1 = child_desc_y1; +} + + +/** + * Recursively calculate the descendant_[xy][01] values for a laid-out box tree + * and inform iframe browser windows of their size and position. + * + * \param len_ctx Length conversion context + * \param box tree of boxes to update + */ +static void layout_calculate_descendant_bboxes( + const nscss_len_ctx *len_ctx, + struct box *box) +{ + struct box *child; + + assert(box->width != UNKNOWN_WIDTH); + assert(box->height != AUTO); + /* assert((box->width >= 0) && (box->height >= 0)); */ + + /* Initialise box's descendant box to border edge box */ + layout_get_box_bbox(len_ctx, box, + &box->descendant_x0, &box->descendant_y0, + &box->descendant_x1, &box->descendant_y1); + + /* Extend it to contain HTML contents if box is replaced */ + if (box->object && content_get_type(box->object) == CONTENT_HTML) { + if (box->descendant_x1 < content_get_width(box->object)) + box->descendant_x1 = content_get_width(box->object); + if (box->descendant_y1 < content_get_height(box->object)) + box->descendant_y1 = content_get_height(box->object); + } + + if (box->iframe != NULL) { + int x, y; + box_coords(box, &x, &y); + + browser_window_set_position(box->iframe, x, y); + browser_window_set_dimensions(box->iframe, + box->width, box->height); + browser_window_reformat(box->iframe, true, + box->width, box->height); + } + + if (box->type == BOX_INLINE || box->type == BOX_TEXT) + return; + + if (box->type == BOX_INLINE_END) { + box = box->inline_end; + for (child = box->next; child; + child = child->next) { + if (child->type == BOX_FLOAT_LEFT || + child->type == BOX_FLOAT_RIGHT) + continue; + + layout_update_descendant_bbox(len_ctx, box, child, + box->x, box->y); + + if (child == box->inline_end) + break; + } + return; + } + + if (box->flags & REPLACE_DIM) + /* Box's children aren't displayed if the box is replaced */ + return; + + for (child = box->children; child; child = child->next) { + if (child->type == BOX_FLOAT_LEFT || + child->type == BOX_FLOAT_RIGHT) + continue; + + layout_calculate_descendant_bboxes(len_ctx, child); + + if (box->style && css_computed_overflow_x(box->style) == + CSS_OVERFLOW_HIDDEN && + css_computed_overflow_y(box->style) == + CSS_OVERFLOW_HIDDEN) + continue; + + layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + } + + for (child = box->float_children; child; child = child->next_float) { + assert(child->type == BOX_FLOAT_LEFT || + child->type == BOX_FLOAT_RIGHT); + + layout_calculate_descendant_bboxes(len_ctx, child); + + layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + } + + if (box->list_marker) { + child = box->list_marker; + layout_calculate_descendant_bboxes(len_ctx, child); + + layout_update_descendant_bbox(len_ctx, box, child, 0, 0); + } +} + + +/* exported function documented in html/layout.h */ +bool layout_document(html_content *content, int width, int height) +{ + bool ret; + struct box *doc = content->layout; + const struct gui_layout_table *font_func = content->font_func; + + layout_minmax_block(doc, font_func, content); + + layout_block_find_dimensions(&content->len_ctx, + width, height, 0, 0, doc); + doc->x = doc->margin[LEFT] + doc->border[LEFT].width; + doc->y = doc->margin[TOP] + doc->border[TOP].width; + width -= doc->margin[LEFT] + doc->border[LEFT].width + + doc->padding[LEFT] + doc->padding[RIGHT] + + doc->border[RIGHT].width + doc->margin[RIGHT]; + if (width < 0) { + width = 0; + } + doc->width = width; + + ret = layout_block_context(doc, height, content); + + /* make and fill available height */ + if (doc->y + doc->padding[TOP] + doc->height + doc->padding[BOTTOM] + + doc->border[BOTTOM].width + doc->margin[BOTTOM] < + height) { + doc->height = height - (doc->y + doc->padding[TOP] + + doc->padding[BOTTOM] + + doc->border[BOTTOM].width + + doc->margin[BOTTOM]); + if (doc->children) + doc->children->height = doc->height - + (doc->children->margin[TOP] + + doc->children->border[TOP].width + + doc->children->padding[TOP] + + doc->children->padding[BOTTOM] + + doc->children->border[BOTTOM].width + + doc->children->margin[BOTTOM]); + } + + layout_lists(doc, font_func, &content->len_ctx); + layout_position_absolute(doc, doc, 0, 0, content); + layout_position_relative(&content->len_ctx, doc, doc, 0, 0); + + layout_calculate_descendant_bboxes(&content->len_ctx, doc); + + return ret; +} diff --git a/content/handlers/html/layout.h b/content/handlers/html/layout.h new file mode 100644 index 000000000..0811e81de --- /dev/null +++ b/content/handlers/html/layout.h @@ -0,0 +1,45 @@ +/* + * Copyright 2003 James Bursa + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * interface to HTML layout. + * + * The main interface to the layout code is layout_document(), which takes a + * normalized box tree and assigns coordinates and dimensions to the boxes, and + * also adds boxes to the tree (eg. when formatting lines of text). + */ + +#ifndef NETSURF_HTML_LAYOUT_H +#define NETSURF_HTML_LAYOUT_H + +struct box; +struct html_content; +struct gui_layout_table; + +/** + * Calculate positions of boxes in a document. + * + * \param content content of type CONTENT_HTML + * \param width available width + * \param height available height + * \return true on success, false on memory exhaustion + */ +bool layout_document(struct html_content *content, int width, int height); + +#endif diff --git a/content/handlers/html/search.c b/content/handlers/html/search.c new file mode 100644 index 000000000..9ba2957e4 --- /dev/null +++ b/content/handlers/html/search.c @@ -0,0 +1,656 @@ +/* + * Copyright 2004 John M Bell + * Copyright 2005 Adrian Lees + * Copyright 2009 Mark Benjamin + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Free text search (core) + */ + +#include +#include +#include + +#include "utils/config.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/utils.h" +#include "content/content.h" +#include "content/hlcache.h" +#include "desktop/selection.h" +#include "netsurf/search.h" +#include "netsurf/misc.h" +#include "desktop/gui_internal.h" + +#include "text/textplain.h" +#include "html/box.h" +#include "html/html.h" +#include "html/html_internal.h" +#include "html/search.h" + +#ifndef NOF_ELEMENTS +#define NOF_ELEMENTS(array) (sizeof(array)/sizeof(*(array))) +#endif + + +struct list_entry { + unsigned start_idx; /* start position of match */ + unsigned end_idx; /* end of match */ + + struct box *start_box; /* used only for html contents */ + struct box *end_box; + + struct selection *sel; + + struct list_entry *prev; + struct list_entry *next; +}; + +struct search_context { + void *gui_p; + struct content *c; + struct list_entry *found; + struct list_entry *current; /* first for select all */ + char *string; + bool prev_case_sens; + bool newsearch; + bool is_html; +}; + + +/* Exported function documented in search.h */ +struct search_context * search_create_context(struct content *c, + content_type type, void *gui_data) +{ + struct search_context *context; + struct list_entry *search_head; + + if (type != CONTENT_HTML && type != CONTENT_TEXTPLAIN) { + return NULL; + } + + context = malloc(sizeof(struct search_context)); + if (context == NULL) { + guit->misc->warning("NoMemory", 0); + return NULL; + } + + search_head = malloc(sizeof(struct list_entry)); + if (search_head == NULL) { + guit->misc->warning("NoMemory", 0); + free(context); + return NULL; + } + + search_head->start_idx = 0; + search_head->end_idx = 0; + search_head->start_box = NULL; + search_head->end_box = NULL; + search_head->sel = NULL; + search_head->prev = NULL; + search_head->next = NULL; + + context->found = search_head; + context->current = NULL; + context->string = NULL; + context->prev_case_sens = false; + context->newsearch = true; + context->c = c; + context->is_html = (type == CONTENT_HTML) ? true : false; + context->gui_p = gui_data; + + return context; +} + + +/** + * Release the memory used by the list of matches, + * deleting selection objects too + */ + +static void free_matches(struct search_context *context) +{ + struct list_entry *a; + struct list_entry *b; + + a = context->found->next; + + /* empty the list before clearing and deleting the + * selections because the the clearing updates the + * screen immediately, causing nested accesses to the list */ + + context->found->prev = NULL; + context->found->next = NULL; + + for (; a; a = b) { + b = a->next; + if (a->sel) { + selection_clear(a->sel, true); + selection_destroy(a->sel); + } + free(a); + } +} + + +/** + * Find the first occurrence of 'match' in 'string' and return its index + * + * \param string the string to be searched (unterminated) + * \param s_len length of the string to be searched + * \param pattern the pattern for which we are searching (unterminated) + * \param p_len length of pattern + * \param case_sens true iff case sensitive match required + * \param m_len accepts length of match in bytes + * \return pointer to first match, NULL if none + */ + +static const char *find_pattern(const char *string, int s_len, + const char *pattern, int p_len, bool case_sens, + unsigned int *m_len) +{ + struct { const char *ss, *s, *p; bool first; } context[16]; + const char *ep = pattern + p_len; + const char *es = string + s_len; + const char *p = pattern - 1; /* a virtual '*' before the pattern */ + const char *ss = string; + const char *s = string; + bool first = true; + int top = 0; + + while (p < ep) { + bool matches; + if (p < pattern || *p == '*') { + char ch; + + /* skip any further asterisks; one is the same as many + */ + do p++; while (p < ep && *p == '*'); + + /* if we're at the end of the pattern, yes, it matches + */ + if (p >= ep) break; + + /* anything matches a # so continue matching from + here, and stack a context that will try to match + the wildcard against the next character */ + + ch = *p; + if (ch != '#') { + /* scan forwards until we find a match for + this char */ + if (!case_sens) ch = toupper(ch); + while (s < es) { + if (case_sens) { + if (*s == ch) break; + } else if (toupper(*s) == ch) + break; + s++; + } + } + + if (s < es) { + /* remember where we are in case the match + fails; we may then resume */ + if (top < (int)NOF_ELEMENTS(context)) { + context[top].ss = ss; + context[top].s = s + 1; + context[top].p = p - 1; + /* ptr to last asterisk */ + context[top].first = first; + top++; + } + + if (first) { + ss = s; + /* remember first non-'*' char */ + first = false; + } + + matches = true; + } else { + matches = false; + } + + } else if (s < es) { + char ch = *p; + if (ch == '#') + matches = true; + else { + if (case_sens) + matches = (*s == ch); + else + matches = (toupper(*s) == toupper(ch)); + } + if (matches && first) { + ss = s; /* remember first non-'*' char */ + first = false; + } + } else { + matches = false; + } + + if (matches) { + p++; s++; + } else { + /* doesn't match, + * resume with stacked context if we have one */ + if (--top < 0) + return NULL; /* no match, give up */ + + ss = context[top].ss; + s = context[top].s; + p = context[top].p; + first = context[top].first; + } + } + + /* end of pattern reached */ + *m_len = max(s - ss, 1); + return ss; +} + + +/** + * Add a new entry to the list of matches + * + * \param start_idx Offset of match start within textual representation + * \param end_idx Offset of match end + * \param context The search context to add the entry to. + * \return Pointer to added entry, NULL iff failed. + */ + +static struct list_entry *add_entry(unsigned start_idx, unsigned end_idx, + struct search_context *context) +{ + struct list_entry *entry; + + /* found string in box => add to list */ + entry = calloc(1, sizeof(*entry)); + if (!entry) { + guit->misc->warning("NoMemory", 0); + return NULL; + } + + entry->start_idx = start_idx; + entry->end_idx = end_idx; + entry->sel = NULL; + + entry->next = 0; + entry->prev = context->found->prev; + + if (context->found->prev == NULL) + context->found->next = entry; + else + context->found->prev->next = entry; + + context->found->prev = entry; + + return entry; +} + + +/** + * Finds all occurrences of a given string in the html box tree + * + * \param pattern the string pattern to search for + * \param p_len pattern length + * \param cur pointer to the current box + * \param case_sens whether to perform a case sensitive search + * \param context The search context to add the entry to. + * \return true on success, false on memory allocation failure + */ +static bool find_occurrences_html(const char *pattern, int p_len, + struct box *cur, bool case_sens, + struct search_context *context) +{ + struct box *a; + + /* ignore this box, if there's no visible text */ + if (!cur->object && cur->text) { + const char *text = cur->text; + unsigned length = cur->length; + + while (length > 0) { + struct list_entry *entry; + unsigned match_length; + unsigned match_offset; + const char *new_text; + const char *pos = find_pattern(text, length, + pattern, p_len, case_sens, + &match_length); + if (!pos) + break; + + /* found string in box => add to list */ + match_offset = pos - cur->text; + + entry = add_entry(cur->byte_offset + match_offset, + cur->byte_offset + + match_offset + + match_length, context); + if (!entry) + return false; + + entry->start_box = cur; + entry->end_box = cur; + + new_text = pos + match_length; + length -= (new_text - text); + text = new_text; + } + } + + /* and recurse */ + for (a = cur->children; a; a = a->next) { + if (!find_occurrences_html(pattern, p_len, a, case_sens, + context)) + return false; + } + + return true; +} + + +/** + * Finds all occurrences of a given string in a textplain content + * + * \param pattern the string pattern to search for + * \param p_len pattern length + * \param c the content to be searched + * \param case_sens whether to perform a case sensitive search + * \param context The search context to add the entry to. + * \return true on success, false on memory allocation failure + */ + +static bool find_occurrences_text(const char *pattern, int p_len, + struct content *c, bool case_sens, + struct search_context *context) +{ + int nlines = textplain_line_count(c); + int line; + + for(line = 0; line < nlines; line++) { + size_t offset, length; + const char *text = textplain_get_line(c, line, + &offset, &length); + if (text) { + while (length > 0) { + struct list_entry *entry; + unsigned match_length; + size_t start_idx; + const char *new_text; + const char *pos = find_pattern(text, length, + pattern, p_len, case_sens, + &match_length); + if (!pos) + break; + + /* found string in line => add to list */ + start_idx = offset + (pos - text); + entry = add_entry(start_idx, start_idx + + match_length, context); + if (!entry) + return false; + + new_text = pos + match_length; + offset += (new_text - text); + length -= (new_text - text); + text = new_text; + } + } + } + + return true; +} + + +/** + * Search for a string in the box tree + * + * \param string the string to search for + * \param string_len length of search string + * \param context The search context to add the entry to. + * \param flags flags to control the search. + */ +static void search_text(const char *string, int string_len, + struct search_context *context, search_flags_t flags) +{ + struct rect bounds; + struct box *box = NULL; + union content_msg_data msg_data; + bool case_sensitive, forwards, showall; + + case_sensitive = ((flags & SEARCH_FLAG_CASE_SENSITIVE) != 0) ? + true : false; + forwards = ((flags & SEARCH_FLAG_FORWARDS) != 0) ? true : false; + showall = ((flags & SEARCH_FLAG_SHOWALL) != 0) ? true : false; + + if (context->c == NULL) + return; + + if (context->is_html == true) { + html_content *html = (html_content *)context->c; + + box = html->layout; + + if (!box) + return; + } + + + /* check if we need to start a new search or continue an old one */ + if (context->newsearch) { + bool res; + + if (context->string != NULL) + free(context->string); + + context->current = NULL; + free_matches(context); + + context->string = malloc(string_len + 1); + if (context->string != NULL) { + memcpy(context->string, string, string_len); + context->string[string_len] = '\0'; + } + + guit->search->hourglass(true, context->gui_p); + + if (context->is_html == true) { + res = find_occurrences_html(string, string_len, + box, case_sensitive, context); + } else { + res = find_occurrences_text(string, string_len, + context->c, case_sensitive, context); + } + + if (!res) { + free_matches(context); + guit->search->hourglass(false, context->gui_p); + return; + } + guit->search->hourglass(false, context->gui_p); + + context->prev_case_sens = case_sensitive; + + /* new search, beginning at the top of the page */ + context->current = context->found->next; + context->newsearch = false; + + } else if (context->current != NULL) { + /* continued search in the direction specified */ + if (forwards) { + if (context->current->next) + context->current = context->current->next; + } else { + if (context->current->prev) + context->current = context->current->prev; + } + } + + guit->search->status((context->current != NULL), context->gui_p); + + search_show_all(showall, context); + + guit->search->back_state((context->current != NULL) && + (context->current->prev != NULL), + context->gui_p); + guit->search->forward_state((context->current != NULL) && + (context->current->next != NULL), + context->gui_p); + + if (context->current == NULL) + return; + + if (context->is_html == true) { + /* get box position and jump to it */ + box_coords(context->current->start_box, &bounds.x0, &bounds.y0); + /* \todo: move x0 in by correct idx */ + box_coords(context->current->end_box, &bounds.x1, &bounds.y1); + /* \todo: move x1 in by correct idx */ + bounds.x1 += context->current->end_box->width; + bounds.y1 += context->current->end_box->height; + } else { + textplain_coords_from_range(context->c, + context->current->start_idx, + context->current->end_idx, &bounds); + } + + msg_data.scroll.area = true; + msg_data.scroll.x0 = bounds.x0; + msg_data.scroll.y0 = bounds.y0; + msg_data.scroll.x1 = bounds.x1; + msg_data.scroll.y1 = bounds.y1; + content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); +} + + +/* Exported function documented in search.h */ +void search_step(struct search_context *context, search_flags_t flags, + const char *string) +{ + int string_len; + int i = 0; + + if (context == NULL) { + guit->misc->warning("SearchError", 0); + return; + } + + guit->search->add_recent(string, context->gui_p); + + string_len = strlen(string); + for (i = 0; i < string_len; i++) + if (string[i] != '#' && string[i] != '*') + break; + if (i >= string_len) { + union content_msg_data msg_data; + free_matches(context); + + guit->search->status(true, context->gui_p); + guit->search->back_state(false, context->gui_p); + guit->search->forward_state(false, context->gui_p); + + msg_data.scroll.area = false; + msg_data.scroll.x0 = 0; + msg_data.scroll.y0 = 0; + content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); + return; + } + search_text(string, string_len, context, flags); +} + + +/* Exported function documented in search.h */ +bool search_term_highlighted(struct content *c, + unsigned start_offset, unsigned end_offset, + unsigned *start_idx, unsigned *end_idx, + struct search_context *context) +{ + if (c == context->c) { + struct list_entry *a; + for (a = context->found->next; a; a = a->next) + if (a->sel && selection_defined(a->sel) && + selection_highlighted(a->sel, + start_offset, end_offset, + start_idx, end_idx)) + return true; + } + + return false; +} + + +/* Exported function documented in search.h */ +void search_show_all(bool all, struct search_context *context) +{ + struct list_entry *a; + + for (a = context->found->next; a; a = a->next) { + bool add = true; + if (!all && a != context->current) { + add = false; + if (a->sel) { + selection_clear(a->sel, true); + selection_destroy(a->sel); + a->sel = NULL; + } + } + if (add && !a->sel) { + + if (context->is_html == true) { + html_content *html = (html_content *)context->c; + a->sel = selection_create(context->c, true); + if (!a->sel) + continue; + + selection_init(a->sel, html->layout, + &html->len_ctx); + } else { + a->sel = selection_create(context->c, false); + if (!a->sel) + continue; + + selection_init(a->sel, NULL, NULL); + } + + selection_set_start(a->sel, a->start_idx); + selection_set_end(a->sel, a->end_idx); + } + } +} + + +/* Exported function documented in search.h */ +void search_destroy_context(struct search_context *context) +{ + assert(context != NULL); + + if (context->string != NULL) { + guit->search->add_recent(context->string, context->gui_p); + free(context->string); + } + + guit->search->forward_state(true, context->gui_p); + guit->search->back_state(true, context->gui_p); + + free_matches(context); + free(context); +} diff --git a/content/handlers/html/search.h b/content/handlers/html/search.h new file mode 100644 index 000000000..5c9408e3e --- /dev/null +++ b/content/handlers/html/search.h @@ -0,0 +1,86 @@ +/* + * Copyright 2009 Mark Benjamin + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Interface to HTML searching. + */ + +#ifndef NETSURF_HTML_SEARCH_H +#define NETSURF_HTML_SEARCH_H + +#include +#include + +#include "desktop/search.h" + +struct search_context; + +/** + * create a search_context + * + * \param c The content the search_context is connected to + * \param type The content type of c + * \param context A context pointer passed to the provider routines. + * \return A new search context or NULL on error. + */ +struct search_context *search_create_context(struct content *c, + content_type type, void *context); + +/** + * Ends the search process, invalidating all state + * freeing the list of found boxes + */ +void search_destroy_context(struct search_context *context); + +/** + * Begins/continues the search process + * + * \note that this may be called many times for a single search. + * + * \param context The search context in use. + * \param flags The flags forward/back etc + * \param string The string to match + */ +void search_step(struct search_context *context, search_flags_t flags, + const char * string); + +/** + * Specifies whether all matches or just the current match should + * be highlighted in the search text. + */ +void search_show_all(bool all, struct search_context *context); + +/** + * Determines whether any portion of the given text box should be + * selected because it matches the current search string. + * + * \param c The content to hilight within. + * \param start_offset byte offset within text of string to be checked + * \param end_offset byte offset within text + * \param start_idx byte offset within string of highlight start + * \param end_idx byte offset of highlight end + * \param context The search context to hilight entries from. + * \return true iff part of the box should be highlighted + */ +bool search_term_highlighted(struct content *c, + unsigned start_offset, unsigned end_offset, + unsigned *start_idx, unsigned *end_idx, + struct search_context *context); + +#endif diff --git a/content/handlers/html/table.c b/content/handlers/html/table.c new file mode 100644 index 000000000..5609e8f29 --- /dev/null +++ b/content/handlers/html/table.c @@ -0,0 +1,1080 @@ +/* + * Copyright 2005 James Bursa + * Copyright 2005 Richard Wilson + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * implementation of HTML table processing and layout. + */ + +#include +#include + +#include "utils/log.h" +#include "utils/talloc.h" +#include "css/utils.h" + +#include "html/box.h" +#include "html/table.h" + +/* Define to enable verbose table debug */ +#undef TABLE_DEBUG + +/** + * Container for border values during table border calculations + */ +struct border { + enum css_border_style_e style; /**< border-style */ + enum css_border_color_e color; /**< border-color type */ + css_color c; /**< border-color value */ + css_fixed width; /**< border-width length */ + css_unit unit; /**< border-width units */ +}; + +static void table_used_left_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); +static void table_used_top_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); +static void table_used_right_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); +static void table_used_bottom_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); +static bool table_border_is_more_eyecatching( + const nscss_len_ctx *len_ctx, + const struct border *a, + box_type a_src, + const struct border *b, + box_type b_src); +static void table_cell_top_process_table( + const nscss_len_ctx *len_ctx, + struct box *table, + struct border *a, + box_type *a_src); +static bool table_cell_top_process_group( + const nscss_len_ctx *len_ctx, + struct box *cell, + struct box *group, + struct border *a, + box_type *a_src); +static bool table_cell_top_process_row( + const nscss_len_ctx *len_ctx, + struct box *cell, + struct box *row, + struct border *a, + box_type *a_src); + + +/** + * Determine the column width types for a table. + * + * \param len_ctx Length conversion context + * \param table box of type BOX_TABLE + * \return true on success, false on memory exhaustion + * + * The table->col array is allocated and type and width are filled in for each + * column. + */ + +bool table_calculate_column_types( + const nscss_len_ctx *len_ctx, + struct box *table) +{ + unsigned int i, j; + struct column *col; + struct box *row_group, *row, *cell; + + if (table->col) + /* table->col already constructed, for example frameset table */ + return true; + + table->col = col = talloc_array(table, struct column, table->columns); + if (!col) + return false; + + for (i = 0; i != table->columns; i++) { + col[i].type = COLUMN_WIDTH_UNKNOWN; + col[i].width = 0; + col[i].positioned = true; + } + + /* 1st pass: cells with colspan 1 only */ + for (row_group = table->children; row_group; row_group =row_group->next) + for (row = row_group->children; row; row = row->next) + for (cell = row->children; cell; cell = cell->next) { + enum css_width_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + assert(cell->type == BOX_TABLE_CELL); + assert(cell->style); + + if (cell->columns != 1) + continue; + i = cell->start_column; + + if (css_computed_position(cell->style) != + CSS_POSITION_ABSOLUTE && + css_computed_position(cell->style) != + CSS_POSITION_FIXED) { + col[i].positioned = false; + } + + type = css_computed_width(cell->style, &value, &unit); + + /* fixed width takes priority over any other width type */ + if (col[i].type != COLUMN_WIDTH_FIXED && + type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) { + col[i].type = COLUMN_WIDTH_FIXED; + col[i].width = FIXTOINT(nscss_len2px(len_ctx, + value, unit, cell->style)); + if (col[i].width < 0) + col[i].width = 0; + continue; + } + + if (col[i].type != COLUMN_WIDTH_UNKNOWN) + continue; + + if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT) { + col[i].type = COLUMN_WIDTH_PERCENT; + col[i].width = FIXTOINT(value); + if (col[i].width < 0) + col[i].width = 0; + } else if (type == CSS_WIDTH_AUTO) { + col[i].type = COLUMN_WIDTH_AUTO; + } + } + + /* 2nd pass: cells which span multiple columns */ + for (row_group = table->children; row_group; row_group =row_group->next) + for (row = row_group->children; row; row = row->next) + for (cell = row->children; cell; cell = cell->next) { + unsigned int fixed_columns = 0, percent_columns = 0, + auto_columns = 0, unknown_columns = 0; + int fixed_width = 0, percent_width = 0; + enum css_width_e type; + css_fixed value = 0; + css_unit unit = CSS_UNIT_PX; + + if (cell->columns == 1) + continue; + i = cell->start_column; + + for (j = i; j < i + cell->columns; j++) { + col[j].positioned = false; + } + + /* count column types in spanned cells */ + for (j = 0; j != cell->columns; j++) { + if (col[i + j].type == COLUMN_WIDTH_FIXED) { + fixed_width += col[i + j].width; + fixed_columns++; + } else if (col[i + j].type == COLUMN_WIDTH_PERCENT) { + percent_width += col[i + j].width; + percent_columns++; + } else if (col[i + j].type == COLUMN_WIDTH_AUTO) { + auto_columns++; + } else { + unknown_columns++; + } + } + + if (!unknown_columns) + continue; + + type = css_computed_width(cell->style, &value, &unit); + + /* if cell is fixed width, and all spanned columns are fixed + * or unknown width, split extra width among unknown columns */ + if (type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT && + fixed_columns + unknown_columns == + cell->columns) { + int width = (FIXTOFLT(nscss_len2px(len_ctx, value, unit, + cell->style)) - fixed_width) / + unknown_columns; + if (width < 0) + width = 0; + for (j = 0; j != cell->columns; j++) { + if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) { + col[i + j].type = COLUMN_WIDTH_FIXED; + col[i + j].width = width; + } + } + } + + /* as above for percentage width */ + if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT && + percent_columns + unknown_columns == + cell->columns) { + int width = (FIXTOFLT(value) - + percent_width) / unknown_columns; + if (width < 0) + width = 0; + for (j = 0; j != cell->columns; j++) { + if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) { + col[i + j].type = COLUMN_WIDTH_PERCENT; + col[i + j].width = width; + } + } + } + } + + /* use AUTO if no width type was specified */ + for (i = 0; i != table->columns; i++) { + if (col[i].type == COLUMN_WIDTH_UNKNOWN) + col[i].type = COLUMN_WIDTH_AUTO; + } + +#ifdef TABLE_DEBUG + for (i = 0; i != table->columns; i++) + NSLOG(netsurf, INFO, + "table %p, column %u: type %s, width %i", table, i, ((const char *[]){ + "UNKNOWN", + "FIXED", + "AUTO", + "PERCENT", + "RELATIVE", + })[col[i].type], col[i].width); +#endif + + return true; +} + +/** + * Calculate used values of border-{trbl}-{style,color,width} for table cells. + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + * + * \post \a cell's border array is populated + */ +void table_used_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + int side; + + assert(cell->type == BOX_TABLE_CELL); + + if (css_computed_border_collapse(cell->style) == + CSS_BORDER_COLLAPSE_SEPARATE) { + css_fixed width = 0; + css_unit unit = CSS_UNIT_PX; + + /* Left border */ + cell->border[LEFT].style = + css_computed_border_left_style(cell->style); + css_computed_border_left_color(cell->style, + &cell->border[LEFT].c); + css_computed_border_left_width(cell->style, &width, &unit); + cell->border[LEFT].width = + FIXTOINT(nscss_len2px(len_ctx, + width, unit, cell->style)); + + /* Top border */ + cell->border[TOP].style = + css_computed_border_top_style(cell->style); + css_computed_border_top_color(cell->style, + &cell->border[TOP].c); + css_computed_border_top_width(cell->style, &width, &unit); + cell->border[TOP].width = + FIXTOINT(nscss_len2px(len_ctx, + width, unit, cell->style)); + + /* Right border */ + cell->border[RIGHT].style = + css_computed_border_right_style(cell->style); + css_computed_border_right_color(cell->style, + &cell->border[RIGHT].c); + css_computed_border_right_width(cell->style, &width, &unit); + cell->border[RIGHT].width = + FIXTOINT(nscss_len2px(len_ctx, + width, unit, cell->style)); + + /* Bottom border */ + cell->border[BOTTOM].style = + css_computed_border_bottom_style(cell->style); + css_computed_border_bottom_color(cell->style, + &cell->border[BOTTOM].c); + css_computed_border_bottom_width(cell->style, &width, &unit); + cell->border[BOTTOM].width = + FIXTOINT(nscss_len2px(len_ctx, + width, unit, cell->style)); + } else { + /* Left border */ + table_used_left_border_for_cell(len_ctx, cell); + + /* Top border */ + table_used_top_border_for_cell(len_ctx, cell); + + /* Right border */ + table_used_right_border_for_cell(len_ctx, cell); + + /* Bottom border */ + table_used_bottom_border_for_cell(len_ctx, cell); + } + + /* Finally, ensure that any borders configured as + * hidden or none have zero width. (c.f. layout_find_dimensions) */ + for (side = 0; side != 4; side++) { + if (cell->border[side].style == CSS_BORDER_STYLE_HIDDEN || + cell->border[side].style == + CSS_BORDER_STYLE_NONE) + cell->border[side].width = 0; + } +} + +/****************************************************************************** + * Helpers for used border calculations * + ******************************************************************************/ + +/** + * Calculate used values of border-left-{style,color,width} + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + */ +void table_used_left_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + struct border a, b; + box_type a_src, b_src; + + /** \todo Need column and column_group, too */ + + /* Initialise to computed left border for cell */ + a.style = css_computed_border_left_style(cell->style); + a.color = css_computed_border_left_color(cell->style, &a.c); + css_computed_border_left_width(cell->style, &a.width, &a.unit); + a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); + a.unit = CSS_UNIT_PX; + a_src = BOX_TABLE_CELL; + + if (cell->prev != NULL || cell->start_column != 0) { + /* Cell to the left -- consider its right border */ + struct box *prev = NULL; + + if (cell->prev == NULL) { + struct box *row; + + /* Spanned from a previous row in current row group */ + for (row = cell->parent; row != NULL; row = row->prev) { + for (prev = row->children; prev != NULL; + prev = prev->next) { + if (prev->start_column + + prev->columns == + cell->start_column) + break; + } + + if (prev != NULL) + break; + } + + assert(prev != NULL); + } else { + prev = cell->prev; + } + + b.style = css_computed_border_right_style(prev->style); + b.color = css_computed_border_right_color(prev->style, &b.c); + css_computed_border_right_width(prev->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, prev->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_CELL; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + } else { + /* First cell in row, so consider rows and row group */ + struct box *row = cell->parent; + struct box *group = row->parent; + struct box *table = group->parent; + unsigned int rows = cell->rows; + + while (rows-- > 0 && row != NULL) { + /* Spanned rows -- consider their left border */ + b.style = css_computed_border_left_style(row->style); + b.color = css_computed_border_left_color( + row->style, &b.c); + css_computed_border_left_width( + row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, + b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + row = row->next; + } + + /** \todo can cells span row groups? */ + + /* Row group -- consider its left border */ + b.style = css_computed_border_left_style(group->style); + b.color = css_computed_border_left_color(group->style, &b.c); + css_computed_border_left_width(group->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + /* The table itself -- consider its left border */ + b.style = css_computed_border_left_style(table->style); + b.color = css_computed_border_left_color(table->style, &b.c); + css_computed_border_left_width(table->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + } + + /* a now contains the used left border for the cell */ + cell->border[LEFT].style = a.style; + cell->border[LEFT].c = a.c; + cell->border[LEFT].width = FIXTOINT(nscss_len2px(len_ctx, + a.width, a.unit, cell->style)); +} + +/** + * Calculate used values of border-top-{style,color,width} + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + */ +void table_used_top_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + struct border a, b; + box_type a_src, b_src; + struct box *row = cell->parent; + bool process_group = false; + + /* Initialise to computed top border for cell */ + a.style = css_computed_border_top_style(cell->style); + css_computed_border_top_color(cell->style, &a.c); + css_computed_border_top_width(cell->style, &a.width, &a.unit); + a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); + a.unit = CSS_UNIT_PX; + a_src = BOX_TABLE_CELL; + + /* Top border of row */ + b.style = css_computed_border_top_style(row->style); + css_computed_border_top_color(row->style, &b.c); + css_computed_border_top_width(row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + if (row->prev != NULL) { + /* Consider row(s) above */ + while (table_cell_top_process_row(len_ctx, cell, row->prev, + &a, &a_src) == false) { + if (row->prev->prev == NULL) { + /* Consider row group */ + process_group = true; + break; + } else { + row = row->prev; + } + } + } else { + process_group = true; + } + + if (process_group) { + struct box *group = row->parent; + + /* Top border of row group */ + b.style = css_computed_border_top_style(group->style); + b.color = css_computed_border_top_color(group->style, &b.c); + css_computed_border_top_width(group->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + if (group->prev == NULL) { + /* Top border of table */ + table_cell_top_process_table(len_ctx, + group->parent, &a, &a_src); + } else { + /* Process previous group(s) */ + while (table_cell_top_process_group(len_ctx, + cell, group->prev, + &a, &a_src) == false) { + if (group->prev->prev == NULL) { + /* Top border of table */ + table_cell_top_process_table(len_ctx, + group->parent, + &a, &a_src); + break; + } else { + group = group->prev; + } + } + } + } + + /* a now contains the used top border for the cell */ + cell->border[TOP].style = a.style; + cell->border[TOP].c = a.c; + cell->border[TOP].width = FIXTOINT(nscss_len2px(len_ctx, + a.width, a.unit, cell->style)); +} + +/** + * Calculate used values of border-right-{style,color,width} + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + */ +void table_used_right_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + struct border a, b; + box_type a_src, b_src; + + /** \todo Need column and column_group, too */ + + /* Initialise to computed right border for cell */ + a.style = css_computed_border_right_style(cell->style); + css_computed_border_right_color(cell->style, &a.c); + css_computed_border_right_width(cell->style, &a.width, &a.unit); + a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); + a.unit = CSS_UNIT_PX; + a_src = BOX_TABLE_CELL; + + if (cell->next != NULL || cell->start_column + cell->columns != + cell->parent->parent->parent->columns) { + /* Cell is not at right edge of table -- no right border */ + a.style = CSS_BORDER_STYLE_NONE; + a.width = 0; + a.unit = CSS_UNIT_PX; + } else { + /* Last cell in row, so consider rows and row group */ + struct box *row = cell->parent; + struct box *group = row->parent; + struct box *table = group->parent; + unsigned int rows = cell->rows; + + while (rows-- > 0 && row != NULL) { + /* Spanned rows -- consider their right border */ + b.style = css_computed_border_right_style(row->style); + b.color = css_computed_border_right_color( + row->style, &b.c); + css_computed_border_right_width( + row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, + b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + row = row->next; + } + + /** \todo can cells span row groups? */ + + /* Row group -- consider its right border */ + b.style = css_computed_border_right_style(group->style); + b.color = css_computed_border_right_color(group->style, &b.c); + css_computed_border_right_width(group->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + /* The table itself -- consider its right border */ + b.style = css_computed_border_right_style(table->style); + b.color = css_computed_border_right_color(table->style, &b.c); + css_computed_border_right_width(table->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + } + + /* a now contains the used right border for the cell */ + cell->border[RIGHT].style = a.style; + cell->border[RIGHT].c = a.c; + cell->border[RIGHT].width = FIXTOINT(nscss_len2px(len_ctx, + a.width, a.unit, cell->style)); +} + +/** + * Calculate used values of border-bottom-{style,color,width} + * + * \param len_ctx Length conversion context + * \param cell Table cell to consider + */ +void table_used_bottom_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell) +{ + struct border a, b; + box_type a_src, b_src; + struct box *row = cell->parent; + unsigned int rows = cell->rows; + + /* Initialise to computed bottom border for cell */ + a.style = css_computed_border_bottom_style(cell->style); + css_computed_border_bottom_color(cell->style, &a.c); + css_computed_border_bottom_width(cell->style, &a.width, &a.unit); + a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); + a.unit = CSS_UNIT_PX; + a_src = BOX_TABLE_CELL; + + while (rows-- > 0 && row != NULL) + row = row->next; + + /** \todo Can cells span row groups? */ + + if (row != NULL) { + /* Cell is not at bottom edge of table -- no bottom border */ + a.style = CSS_BORDER_STYLE_NONE; + a.width = 0; + a.unit = CSS_UNIT_PX; + } else { + /* Cell at bottom of table, so consider row and row group */ + struct box *row = cell->parent; + struct box *group = row->parent; + struct box *table = group->parent; + + /* Bottom border of row */ + b.style = css_computed_border_bottom_style(row->style); + b.color = css_computed_border_bottom_color(row->style, &b.c); + css_computed_border_bottom_width(row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + /* Row group -- consider its bottom border */ + b.style = css_computed_border_bottom_style(group->style); + b.color = css_computed_border_bottom_color(group->style, &b.c); + css_computed_border_bottom_width(group->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + a_src = b_src; + } + + /* The table itself -- consider its bottom border */ + b.style = css_computed_border_bottom_style(table->style); + b.color = css_computed_border_bottom_color(table->style, &b.c); + css_computed_border_bottom_width(table->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(len_ctx, + &a, a_src, &b, b_src)) { + a = b; + } + } + + /* a now contains the used bottom border for the cell */ + cell->border[BOTTOM].style = a.style; + cell->border[BOTTOM].c = a.c; + cell->border[BOTTOM].width = FIXTOINT(nscss_len2px(len_ctx, + a.width, a.unit, cell->style)); +} + +/** + * Determine if a border style is more eyecatching than another + * + * \param len_ctx Length conversion context + * \param a Reference border style + * \param a_src Source of \a a + * \param b Candidate border style + * \param b_src Source of \a b + * \return True if \a b is more eyecatching than \a a + */ +bool table_border_is_more_eyecatching( + const nscss_len_ctx *len_ctx, + const struct border *a, + box_type a_src, + const struct border *b, + box_type b_src) +{ + css_fixed awidth, bwidth; + int impact = 0; + + /* See CSS 2.1 $17.6.2.1 */ + + /* 1 + 2 -- hidden beats everything, none beats nothing */ + if (a->style == CSS_BORDER_STYLE_HIDDEN || + b->style == CSS_BORDER_STYLE_NONE) + return false; + + if (b->style == CSS_BORDER_STYLE_HIDDEN || + a->style == CSS_BORDER_STYLE_NONE) + return true; + + /* 3a -- wider borders beat narrow ones */ + /* The widths must be absolute, which will be the case + * if they've come from a computed style. */ + assert(a->unit != CSS_UNIT_EM && a->unit != CSS_UNIT_EX); + assert(b->unit != CSS_UNIT_EM && b->unit != CSS_UNIT_EX); + awidth = nscss_len2px(len_ctx, a->width, a->unit, NULL); + bwidth = nscss_len2px(len_ctx, b->width, b->unit, NULL); + + if (awidth < bwidth) + return true; + else if (bwidth < awidth) + return false; + + /* 3b -- sort by style */ + switch (a->style) { + case CSS_BORDER_STYLE_DOUBLE: impact++; /* Fall through */ + case CSS_BORDER_STYLE_SOLID: impact++; /* Fall through */ + case CSS_BORDER_STYLE_DASHED: impact++; /* Fall through */ + case CSS_BORDER_STYLE_DOTTED: impact++; /* Fall through */ + case CSS_BORDER_STYLE_RIDGE: impact++; /* Fall through */ + case CSS_BORDER_STYLE_OUTSET: impact++; /* Fall through */ + case CSS_BORDER_STYLE_GROOVE: impact++; /* Fall through */ + case CSS_BORDER_STYLE_INSET: impact++; /* Fall through */ + default: + break; + } + + switch (b->style) { + case CSS_BORDER_STYLE_DOUBLE: impact--; /* Fall through */ + case CSS_BORDER_STYLE_SOLID: impact--; /* Fall through */ + case CSS_BORDER_STYLE_DASHED: impact--; /* Fall through */ + case CSS_BORDER_STYLE_DOTTED: impact--; /* Fall through */ + case CSS_BORDER_STYLE_RIDGE: impact--; /* Fall through */ + case CSS_BORDER_STYLE_OUTSET: impact--; /* Fall through */ + case CSS_BORDER_STYLE_GROOVE: impact--; /* Fall through */ + case CSS_BORDER_STYLE_INSET: impact--; /* Fall through */ + default: + break; + } + + if (impact < 0) + return true; + else if (impact > 0) + return false; + + /* 4a -- sort by origin */ + impact = 0; + + /** \todo COL/COL_GROUP */ + switch (a_src) { + case BOX_TABLE_CELL: impact++; /* Fall through */ + case BOX_TABLE_ROW: impact++; /* Fall through */ + case BOX_TABLE_ROW_GROUP: impact++; /* Fall through */ + case BOX_TABLE: impact++; /* Fall through */ + default: + break; + } + + /** \todo COL/COL_GROUP */ + switch (b_src) { + case BOX_TABLE_CELL: impact--; /* Fall through */ + case BOX_TABLE_ROW: impact--; /* Fall through */ + case BOX_TABLE_ROW_GROUP: impact--; /* Fall through */ + case BOX_TABLE: impact--; /* Fall through */ + default: + break; + } + + if (impact < 0) + return true; + else if (impact > 0) + return false; + + /* 4b -- furthest left (if direction: ltr) and towards top wins */ + /** \todo Currently assumes b satisifies this */ + return true; +} + +/****************************************************************************** + * Helpers for top border collapsing * + ******************************************************************************/ + +/** + * Process a table + * + * \param len_ctx Length conversion context + * \param table Table to process + * \param a Current border style for cell + * \param a_src Source of \a a + * + * \post \a a will be updated with most eyecatching style + * \post \a a_src will be updated also + */ +void table_cell_top_process_table( + const nscss_len_ctx *len_ctx, + struct box *table, + struct border *a, + box_type *a_src) +{ + struct border b; + box_type b_src; + + /* Top border of table */ + b.style = css_computed_border_top_style(table->style); + b.color = css_computed_border_top_color(table->style, &b.c); + css_computed_border_top_width(table->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE; + + if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } +} + +/** + * Process a group + * + * \param len_ctx Length conversion context + * \param cell Cell being considered + * \param group Group to process + * \param a Current border style for cell + * \param a_src Source of \a a + * \return true if group has non-empty rows, false otherwise + * + * \post \a a will be updated with most eyecatching style + * \post \a a_src will be updated also + */ +bool table_cell_top_process_group( + const nscss_len_ctx *len_ctx, + struct box *cell, + struct box *group, + struct border *a, + box_type *a_src) +{ + struct border b; + box_type b_src; + + /* Bottom border of group */ + b.style = css_computed_border_bottom_style(group->style); + b.color = css_computed_border_bottom_color(group->style, &b.c); + css_computed_border_bottom_width(group->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + + if (group->last != NULL) { + /* Process rows in group, starting with last */ + struct box *row = group->last; + + while (table_cell_top_process_row(len_ctx, cell, row, + a, a_src) == false) { + if (row->prev == NULL) { + return false; + } else { + row = row->prev; + } + } + } else { + /* Group is empty, so consider its top border */ + b.style = css_computed_border_top_style(group->style); + b.color = css_computed_border_top_color(group->style, &b.c); + css_computed_border_top_width(group->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW_GROUP; + + if (table_border_is_more_eyecatching(len_ctx, + a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + + return false; + } + + return true; +} + +/** + * Process a row + * + * \param len_ctx Length conversion context + * \param cell Cell being considered + * \param row Row to process + * \param a Current border style for cell + * \param a_src Source of \a a + * \return true if row has cells, false otherwise + * + * \post \a a will be updated with most eyecatching style + * \post \a a_src will be updated also + */ +bool table_cell_top_process_row( + const nscss_len_ctx *len_ctx, + struct box *cell, + struct box *row, + struct border *a, + box_type *a_src) +{ + struct border b; + box_type b_src; + + /* Bottom border of row */ + b.style = css_computed_border_bottom_style(row->style); + b.color = css_computed_border_bottom_color(row->style, &b.c); + css_computed_border_bottom_width(row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + + if (row->children == NULL) { + /* Row is empty, so consider its top border */ + b.style = css_computed_border_top_style(row->style); + b.color = css_computed_border_top_color(row->style, &b.c); + css_computed_border_top_width(row->style, &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_ROW; + + if (table_border_is_more_eyecatching(len_ctx, + a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + + return false; + } else { + /* Process cells that are directly above the cell being + * considered. They may not be in this row, but in one of the + * rows above it in the case where rowspan > 1. */ + struct box *c; + bool processed = false; + + while (processed == false) { + for (c = row->children; c != NULL; c = c->next) { + /* Ignore cells to the left */ + if (c->start_column + c->columns - 1 < + cell->start_column) + continue; + /* Ignore cells to the right */ + if (c->start_column > cell->start_column + + cell->columns - 1) + continue; + + /* Flag that we've processed a cell */ + processed = true; + + /* Consider bottom border */ + b.style = css_computed_border_bottom_style( + c->style); + b.color = css_computed_border_bottom_color( + c->style, &b.c); + css_computed_border_bottom_width(c->style, + &b.width, &b.unit); + b.width = nscss_len2px(len_ctx, + b.width, b.unit, c->style); + b.unit = CSS_UNIT_PX; + b_src = BOX_TABLE_CELL; + + if (table_border_is_more_eyecatching(len_ctx, + a, *a_src, &b, b_src)) { + *a = b; + *a_src = b_src; + } + } + + if (processed == false) { + /* There must be a preceding row */ + assert(row->prev != NULL); + + row = row->prev; + } + } + } + + return true; +} diff --git a/content/handlers/html/table.h b/content/handlers/html/table.h new file mode 100644 index 000000000..11ab653c6 --- /dev/null +++ b/content/handlers/html/table.h @@ -0,0 +1,39 @@ +/* + * Copyright 2005 James Bursa + * Copyright 2005 Richard Wilson + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Interface to HTML table processing and layout. + */ + +#ifndef NETSURF_HTML_TABLE_H +#define NETSURF_HTML_TABLE_H + +#include + +struct box; + +bool table_calculate_column_types( + const nscss_len_ctx *len_ctx, + struct box *table); +void table_used_border_for_cell( + const nscss_len_ctx *len_ctx, + struct box *cell); + +#endif diff --git a/content/handlers/javascript/duktape/Document.bnd b/content/handlers/javascript/duktape/Document.bnd index ece417d0d..5de724538 100644 --- a/content/handlers/javascript/duktape/Document.bnd +++ b/content/handlers/javascript/duktape/Document.bnd @@ -14,7 +14,7 @@ prologue Document() #include "utils/libdom.h" #include "utils/utils.h" #include "content/hlcache.h" -#include "render/html_internal.h" +#include "html/html_internal.h" #include "content/urldb.h" #define HANDLER_MAGIC MAGIC(HANDLER_MAP) diff --git a/content/handlers/javascript/duktape/Window.bnd b/content/handlers/javascript/duktape/Window.bnd index 3f680d47d..f647fd147 100644 --- a/content/handlers/javascript/duktape/Window.bnd +++ b/content/handlers/javascript/duktape/Window.bnd @@ -15,8 +15,8 @@ class Window { #include "utils/nsurl.h" #include "netsurf/browser_window.h" #include "content/hlcache.h" -#include "render/html.h" -#include "render/html_internal.h" +#include "html/html.h" +#include "html/html_internal.h" %}; }; diff --git a/content/handlers/text/Makefile b/content/handlers/text/Makefile new file mode 100644 index 000000000..83d5dbd1e --- /dev/null +++ b/content/handlers/text/Makefile @@ -0,0 +1,3 @@ +# text content handler sources + +S_TEXT := textplain.c diff --git a/content/handlers/text/textplain.c b/content/handlers/text/textplain.c new file mode 100644 index 000000000..e6d167bc2 --- /dev/null +++ b/content/handlers/text/textplain.c @@ -0,0 +1,1576 @@ +/* + * Copyright 2006 James Bursa + * Copyright 2006 Adrian Lees + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * + * plain text content handling implementation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "utils/corestrings.h" +#include "utils/http.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/utils.h" +#include "utils/utf8.h" +#include "netsurf/content.h" +#include "netsurf/keypress.h" +#include "netsurf/browser_window.h" +#include "netsurf/plotters.h" +#include "netsurf/layout.h" +#include "content/content_protected.h" +#include "content/hlcache.h" +#include "css/utils.h" +#include "utils/nsoption.h" +#include "desktop/search.h" +#include "desktop/selection.h" +#include "desktop/gui_internal.h" + +#include "html/search.h" +#include "text/textplain.h" + +struct textplain_line { + size_t start; + size_t length; +}; + +typedef struct textplain_content { + struct content base; + + lwc_string *encoding; + void *inputstream; + char *utf8_data; + size_t utf8_data_size; + size_t utf8_data_allocated; + unsigned long physical_line_count; + struct textplain_line *physical_line; + int formatted_width; + struct browser_window *bw; + + struct selection sel; /** Selection state */ + + /** Context for free text search, or NULL if none */ + struct search_context *search; + /** Current search string, or NULL if none */ + char *search_string; +} textplain_content; + + +#define CHUNK 32768 /* Must be a power of 2 */ +#define MARGIN 4 + +#define TAB_WIDTH 8 /* must be power of 2 currently */ +#define TEXT_SIZE 10 * FONT_SIZE_SCALE /* Unscaled text size in pt */ + +static plot_font_style_t textplain_style = { + .family = PLOT_FONT_FAMILY_MONOSPACE, + .size = TEXT_SIZE, + .weight = 400, + .flags = FONTF_NONE, + .background = 0xffffff, + .foreground = 0x000000, +}; + +static int textplain_tab_width = 256; /* try for a sensible default */ + +static lwc_string *textplain_default_charset; + + +/** + * Clean up after the text content handler + */ +static void textplain_fini(void) +{ + if (textplain_default_charset != NULL) { + lwc_string_unref(textplain_default_charset); + textplain_default_charset = NULL; + } +} + + +/** + * Work around feature in libparserutils + * + * if the client provides an encoding up front, but does not provide a + * charset detection callback, then libparserutils will replace the + * provided encoding with UTF-8. This breaks our input handling. + * + * Avoid this by providing a callback that does precisely nothing, + * thus preserving whatever charset information we decided on in + * textplain_create. + */ +static parserutils_error +textplain_charset_hack(const uint8_t *data, + size_t len, + uint16_t *mibenum, + uint32_t *source) +{ + return PARSERUTILS_OK; +} + + +/** + * setup plain text render. + * + * \param[in] c content object. + * \param[in] encoding the encoding of the content. + * \return NSERROR_OK else appropriate error code. + */ +static nserror +textplain_create_internal(textplain_content *c, lwc_string *encoding) +{ + char *utf8_data; + parserutils_inputstream *stream; + parserutils_error error; + + textplain_style.size = (nsoption_int(font_size) * FONT_SIZE_SCALE) / 10; + + utf8_data = malloc(CHUNK); + if (utf8_data == NULL) + goto no_memory; + + error = parserutils_inputstream_create(lwc_string_data(encoding), 0, + textplain_charset_hack, &stream); + if (error == PARSERUTILS_BADENCODING) { + /* Fall back to Windows-1252 */ + error = parserutils_inputstream_create("Windows-1252", 0, + textplain_charset_hack, &stream); + } + if (error != PARSERUTILS_OK) { + free(utf8_data); + goto no_memory; + } + + c->encoding = lwc_string_ref(encoding); + c->inputstream = stream; + c->utf8_data = utf8_data; + c->utf8_data_size = 0; + c->utf8_data_allocated = CHUNK; + c->physical_line = 0; + c->physical_line_count = 0; + c->formatted_width = 0; + c->bw = NULL; + + selection_prepare(&c->sel, (struct content *)c, false); + + return NSERROR_OK; + +no_memory: + content_broadcast_errorcode(&c->base, NSERROR_NOMEM); + + return NSERROR_NOMEM; +} + + +/** + * Create a CONTENT_TEXTPLAIN. + */ +static nserror +textplain_create(const content_handler *handler, + lwc_string *imime_type, + const http_parameter *params, + llcache_handle *llcache, + const char *fallback_charset, + bool quirks, + struct content **c) +{ + textplain_content *text; + nserror error; + lwc_string *encoding; + + text = calloc(1, sizeof(textplain_content)); + if (text == NULL) { + return NSERROR_NOMEM; + } + + error = content__init(&text->base, handler, imime_type, params, + llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(text); + return error; + } + + error = http_parameter_list_find_item(params, corestring_lwc_charset, + &encoding); + if (error != NSERROR_OK) { + encoding = lwc_string_ref(textplain_default_charset); + } + + error = textplain_create_internal(text, encoding); + if (error != NSERROR_OK) { + lwc_string_unref(encoding); + free(text); + return error; + } + + lwc_string_unref(encoding); + + *c = (struct content *) text; + + return NSERROR_OK; +} + + +/** + * copy utf8 encoded data + */ +static bool +textplain_copy_utf8_data(textplain_content *c, const uint8_t *buf, size_t len) +{ + if (c->utf8_data_size + len >= c->utf8_data_allocated) { + /* Compute next multiple of chunk above the required space */ + size_t allocated; + char *utf8_data; + + allocated = (c->utf8_data_size + len + CHUNK - 1) & ~(CHUNK - 1); + utf8_data = realloc(c->utf8_data, allocated); + if (utf8_data == NULL) + return false; + + c->utf8_data = utf8_data; + c->utf8_data_allocated = allocated; + } + + memcpy(c->utf8_data + c->utf8_data_size, buf, len); + c->utf8_data_size += len; + + return true; +} + + +/** + * drain input + */ +static bool +textplain_drain_input(textplain_content *c, + parserutils_inputstream *stream, + parserutils_error terminator) +{ + static const uint8_t *u_fffd = (const uint8_t *) "\xef\xbf\xfd"; + const uint8_t *ch; + size_t chlen, offset = 0; + + while (parserutils_inputstream_peek(stream, offset, &ch, &chlen) != + terminator) { + /* Replace all instances of NUL with U+FFFD */ + if (chlen == 1 && *ch == 0) { + if (offset > 0) { + /* Obtain pointer to start of input data */ + parserutils_inputstream_peek(stream, 0, + &ch, &chlen); + /* Copy from it up to the start of the NUL */ + if (textplain_copy_utf8_data(c, ch, + offset) == false) + return false; + } + + /* Emit U+FFFD */ + if (textplain_copy_utf8_data(c, u_fffd, 3) == false) + return false; + + /* Advance inputstream past the NUL we just read */ + parserutils_inputstream_advance(stream, offset + 1); + /* Reset the read offset */ + offset = 0; + } else { + /* Accumulate input */ + offset += chlen; + + if (offset > CHUNK) { + /* Obtain pointer to start of input data */ + parserutils_inputstream_peek(stream, 0, + &ch, &chlen); + + /* Emit the data we've read */ + if (textplain_copy_utf8_data(c, ch, + offset) == false) + return false; + + /* Advance the inputstream */ + parserutils_inputstream_advance(stream, offset); + /* Reset the read offset */ + offset = 0; + } + } + } + + if (offset > 0) { + /* Obtain pointer to start of input data */ + parserutils_inputstream_peek(stream, 0, &ch, &chlen); + /* Emit any data remaining */ + if (textplain_copy_utf8_data(c, ch, offset) == false) + return false; + + /* Advance the inputstream past the data we've read */ + parserutils_inputstream_advance(stream, offset); + } + + return true; +} + + +/** + * Process data for CONTENT_TEXTPLAIN. + */ +static bool +textplain_process_data(struct content *c, const char *data, unsigned int size) +{ + textplain_content *text = (textplain_content *) c; + parserutils_inputstream *stream = text->inputstream; + parserutils_error error; + + error = parserutils_inputstream_append(stream, + (const uint8_t *) data, size); + if (error != PARSERUTILS_OK) { + goto no_memory; + } + + if (textplain_drain_input(text, stream, PARSERUTILS_NEEDDATA) == false) + goto no_memory; + + return true; + +no_memory: + content_broadcast_errorcode(c, NSERROR_NOMEM); + return false; +} + + +/** + * Convert a CONTENT_TEXTPLAIN for display. + */ +static bool textplain_convert(struct content *c) +{ + textplain_content *text = (textplain_content *) c; + parserutils_inputstream *stream = text->inputstream; + parserutils_error error; + + error = parserutils_inputstream_append(stream, NULL, 0); + if (error != PARSERUTILS_OK) { + return false; + } + + if (textplain_drain_input(text, stream, PARSERUTILS_EOF) == false) + return false; + + parserutils_inputstream_destroy(stream); + text->inputstream = NULL; + + content_set_ready(c); + content_set_done(c); + content_set_status(c, messages_get("Done")); + + return true; +} + + +/** + * Calculate the line height, in pixels + * + * \return Line height, in pixels + */ +static float textplain_line_height(void) +{ + /* Size is in points, so convert to pixels. + * Then use a constant line height of 1.2 x font size. + */ + return FIXTOFLT(FDIV((FMUL(FLTTOFIX(1.2), FMUL(nscss_screen_dpi, INTTOFIX((textplain_style.size / FONT_SIZE_SCALE))))), F_72)); +} + + +/** + * Reformat a CONTENT_TEXTPLAIN to a new width. + */ +static void textplain_reformat(struct content *c, int width, int height) +{ + textplain_content *text = (textplain_content *) c; + char *utf8_data = text->utf8_data; + size_t utf8_data_size = text->utf8_data_size; + unsigned long line_count = 0; + struct textplain_line *line = text->physical_line; + struct textplain_line *line1; + size_t i, space, col; + size_t columns = 80; + int character_width; + size_t line_start; + nserror res; + + NSLOG(netsurf, INFO, "content %p w:%d h:%d", c, width, height); + + /* compute available columns (assuming monospaced font) - use 8 + * characters for better accuracy + */ + res = guit->layout->width(&textplain_style, + "ABCDEFGH", 8, + &character_width); + if (res != NSERROR_OK) { + return; + } + + columns = (width - MARGIN - MARGIN) * 8 / character_width; + textplain_tab_width = (TAB_WIDTH * character_width) / 8; + + text->formatted_width = width; + + text->physical_line_count = 0; + + if (!line) { + text->physical_line = line = + malloc(sizeof(struct textplain_line) * (1024 + 3)); + if (!line) + goto no_memory; + } + + line[line_count++].start = line_start = 0; + space = 0; + i = 0; + col = 0; + while (i < utf8_data_size) { + size_t csize; /* number of bytes in character */ + uint32_t chr; + bool term; + size_t next_col; + parserutils_error perror; + + perror = parserutils_charset_utf8_to_ucs4((const uint8_t *)utf8_data + i, utf8_data_size - i, &chr, &csize); + if (perror != PARSERUTILS_OK) { + chr = 0xfffd; + } + + term = (chr == '\n' || chr == '\r'); + + next_col = col + 1; + + if (chr == '\t') { + next_col = (next_col + TAB_WIDTH - 1) & ~(TAB_WIDTH - 1); + } + + if (term || next_col >= columns) { + if (line_count % 1024 == 0) { + line1 = realloc(line, + sizeof(struct textplain_line) * + (line_count + 1024 + 3)); + if (!line1) + goto no_memory; + text->physical_line = line = line1; + } + + if (term) { + line[line_count-1].length = i - line_start; + + /* skip second char of CR/LF or LF/CR pair */ + if (i + 1 < utf8_data_size && + utf8_data[i+1] != utf8_data[i] && + (utf8_data[i+1] == '\n' || + utf8_data[i+1] == '\r')) { + i++; + } + } else { + if (space) { + /* break at last space in line */ + i = space; + line[line_count-1].length = (i + 1) - line_start; + } else + line[line_count-1].length = i - line_start; + } + + line[line_count++].start = line_start = i + 1; + col = 0; + space = 0; + } else { + col++; + if (chr == ' ') + space = i; + } + i += csize; + } + line[line_count-1].length = i - line[line_count-1].start; + line[line_count].start = utf8_data_size; + + text->physical_line_count = line_count; + c->width = width; + c->height = line_count * textplain_line_height() + MARGIN + MARGIN; + + return; + +no_memory: + NSLOG(netsurf, INFO, "out of memory (line_count %lu)", line_count); + return; +} + + +/** + * Destroy a CONTENT_TEXTPLAIN and free all resources it owns. + */ + +static void textplain_destroy(struct content *c) +{ + textplain_content *text = (textplain_content *) c; + + lwc_string_unref(text->encoding); + + if (text->inputstream != NULL) { + parserutils_inputstream_destroy(text->inputstream); + } + + if (text->physical_line != NULL) { + free(text->physical_line); + } + + if (text->utf8_data != NULL) { + free(text->utf8_data); + } +} + + +static nserror textplain_clone(const struct content *old, struct content **newc) +{ + const textplain_content *old_text = (textplain_content *) old; + textplain_content *text; + nserror error; + const char *data; + unsigned long size; + + text = calloc(1, sizeof(textplain_content)); + if (text == NULL) + return NSERROR_NOMEM; + + error = content__clone(old, &text->base); + if (error != NSERROR_OK) { + content_destroy(&text->base); + return error; + } + + /* Simply replay create/process/convert */ + error = textplain_create_internal(text, old_text->encoding); + if (error != NSERROR_OK) { + content_destroy(&text->base); + return error; + } + + data = content__get_source_data(&text->base, &size); + if (size > 0) { + if (textplain_process_data(&text->base, data, size) == false) { + content_destroy(&text->base); + return NSERROR_NOMEM; + } + } + + if (old->status == CONTENT_STATUS_READY || + old->status == CONTENT_STATUS_DONE) { + if (textplain_convert(&text->base) == false) { + content_destroy(&text->base); + return NSERROR_CLONE_FAILED; + } + } + + return NSERROR_OK; +} + + +static content_type textplain_content_type(void) +{ + return CONTENT_TEXTPLAIN; +} + + +/** + * Handle mouse clicks and movements in a TEXTPLAIN content window. + * + * \param c content of type textplain + * \param bw browser window + * \param mouse mouse state on action + * \param x coordinate of mouse + * \param y coordinate of mouse + */ +static void +textplain_mouse_action(struct content *c, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + textplain_content *text = (textplain_content *) c; + browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; + union content_msg_data msg_data; + const char *status = 0; + size_t idx; + int dir = 0; + + browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); + + idx = textplain_offset_from_coords(c, x, y, dir); + if (selection_click(&text->sel, mouse, idx)) { + + if (selection_dragging(&text->sel)) { + browser_window_set_drag_type(bw, + DRAGGING_SELECTION, NULL); + status = messages_get("Selecting"); + } + + } else { + if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) { + browser_window_page_drag_start(bw, x, y); + pointer = BROWSER_POINTER_MOVE; + } + } + + msg_data.explicit_status_text = status; + content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); + + msg_data.pointer = pointer; + content_broadcast(c, CONTENT_MSG_POINTER, &msg_data); +} + + +/** + * Handle mouse tracking (including drags) in a TEXTPLAIN content window. + * + * \param c content of type textplain + * \param bw browser window + * \param mouse state of mouse buttons and modifier keys + * \param x coordinate of mouse + * \param y coordinate of mouse + */ +static void +textplain_mouse_track(struct content *c, + struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + textplain_content *text = (textplain_content *) c; + + if (browser_window_get_drag_type(bw) == DRAGGING_SELECTION && !mouse) { + int dir = -1; + size_t idx; + + if (selection_dragging_start(&text->sel)) + dir = 1; + + idx = textplain_offset_from_coords(c, x, y, dir); + selection_track(&text->sel, mouse, idx); + + browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); + } + + switch (browser_window_get_drag_type(bw)) { + + case DRAGGING_SELECTION: { + int dir = -1; + size_t idx; + + if (selection_dragging_start(&text->sel)) dir = 1; + + idx = textplain_offset_from_coords(c, x, y, dir); + selection_track(&text->sel, mouse, idx); + } + break; + + default: + textplain_mouse_action(c, bw, mouse, x, y); + break; + } +} + + +/** + * Handle keypresses. + * + * \param c content of type CONTENT_TEXTPLAIN + * \param key The UCS4 character codepoint + * \return true if key handled, false otherwise + */ +static bool textplain_keypress(struct content *c, uint32_t key) +{ + textplain_content *text = (textplain_content *) c; + struct selection *sel = &text->sel; + + switch (key) { + case NS_KEY_COPY_SELECTION: + selection_copy_to_clipboard(sel); + return true; + + case NS_KEY_CLEAR_SELECTION: + selection_clear(sel, true); + return true; + + case NS_KEY_SELECT_ALL: + selection_select_all(sel); + return true; + + case NS_KEY_ESCAPE: + if (selection_defined(sel)) { + selection_clear(sel, true); + return true; + } + + /* if there's no selection, leave Escape for the caller */ + return false; + } + + return false; +} + + +/** + * Terminate a search. + * + * \param c content of type text + */ +static void textplain_search_clear(struct content *c) +{ + textplain_content *text = (textplain_content *) c; + + assert(c != NULL); + + free(text->search_string); + text->search_string = NULL; + + if (text->search != NULL) { + search_destroy_context(text->search); + } + text->search = NULL; +} + + +/** + * Handle search. + * + * \param c content of type text + * \param gui_data front end private data + * \param flags search flags + * \param string search string + */ +static void textplain_search(struct content *c, void *gui_data, + search_flags_t flags, const char *string) +{ + textplain_content *text = (textplain_content *) c; + + assert(c != NULL); + + if (string != NULL && text->search_string != NULL && + strcmp(string, text->search_string) == 0 && + text->search != NULL) { + /* Continue prev. search */ + search_step(text->search, flags, string); + + } else if (string != NULL) { + /* New search */ + free(text->search_string); + text->search_string = strdup(string); + if (text->search_string == NULL) + return; + + if (text->search != NULL) { + search_destroy_context(text->search); + text->search = NULL; + } + + text->search = search_create_context(c, CONTENT_TEXTPLAIN, + gui_data); + + if (text->search == NULL) + return; + + search_step(text->search, flags, string); + + } else { + /* Clear search */ + textplain_search_clear(c); + + free(text->search_string); + text->search_string = NULL; + } +} + + +/** + * Redraw a text string with highlighting + * (for selection/search) + * + * \param utf8_text pointer to UTF-8 text string + * \param utf8_len length of string, in bytes + * \param offset byte offset within textual representation + * \param x x ordinate at which to plot text + * \param y y ordinate at which to plot text + * \param clip pointer to current clip rectangle + * \param height height of text string + * \param scale current display scale (1.0 = 100%) + * \param text Content being redrawn. + * \param sel Selection context + * \param search Search context + * \param ctx current redraw context + * \return true iff successful and redraw should proceed + */ +static bool +text_draw(const char *utf8_text, + size_t utf8_len, + size_t offset, + int x, + int y, + const struct rect *clip, + int height, + float scale, + textplain_content *text, + const struct selection *sel, + struct search_context *search, + const struct redraw_context *ctx) +{ + bool highlighted = false; + plot_font_style_t plot_fstyle; + nserror res; + + /* Need scaled text size to pass to plotters */ + plot_fstyle = textplain_style; + plot_fstyle.size *= scale; + + /* is this box part of a selection? */ + if (ctx->interactive == true) { + unsigned len = utf8_len; + unsigned start_idx; + unsigned end_idx; + + /* first try the browser window's current selection */ + if (selection_defined(sel) && + selection_highlighted(sel, + offset, + offset + len, + &start_idx, + &end_idx)) { + highlighted = true; + } + + /* what about the current search operation, if any? */ + if (!highlighted && + (search != NULL) && + search_term_highlighted((struct content *)text, + offset, + offset + len, + &start_idx, + &end_idx, + search)) { + highlighted = true; + } + + /* \todo make search terms visible within selected text */ + if (highlighted) { + struct rect r; + unsigned endtxt_idx = end_idx; + bool clip_changed = false; + bool text_visible = true; + int startx, endx; + plot_style_t pstyle_fill_hback = *plot_style_fill_white; + plot_font_style_t fstyle_hback = plot_fstyle; + + if (end_idx > utf8_len) { + /* adjust for trailing space, not present in + * utf8_text + */ + assert(end_idx == utf8_len + 1); + endtxt_idx = utf8_len; + } + + res = guit->layout->width(&textplain_style, + utf8_text, + start_idx, + &startx); + if (res != NSERROR_OK) { + startx = 0; + } + + res = guit->layout->width(&textplain_style, + utf8_text, + endtxt_idx, + &endx); + if (res != NSERROR_OK) { + endx = 0; + } + + if (scale != 1.0) { + startx *= scale; + endx *= scale; + } + + /* draw any text preceding highlighted portion */ + if (start_idx > 0) { + res = ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + start_idx); + if (res != NSERROR_OK) { + return false; + } + } + + pstyle_fill_hback.fill_colour = textplain_style.foreground; + + /* highlighted portion */ + r.x0 = x + startx; + r.y0 = y; + r.x1 = x + endx; + r.y1 = y + height * scale; + res = ctx->plot->rectangle(ctx, &pstyle_fill_hback, &r); + if (res != NSERROR_OK) { + return false; + } + + if (start_idx > 0) { + int px0 = max(x + startx, clip->x0); + int px1 = min(x + endx, clip->x1); + + if (px0 < px1) { + r.x0 = px0; + r.y0 = clip->y0; + r.x1 = px1; + r.y1 = clip->y1; + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + clip_changed = true; + } else { + text_visible = false; + } + } + + fstyle_hback.background = + pstyle_fill_hback.fill_colour; + fstyle_hback.foreground = colour_to_bw_furthest( + pstyle_fill_hback.fill_colour); + + if (text_visible && + (ctx->plot->text(ctx, + &fstyle_hback, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + endtxt_idx) != NSERROR_OK)) { + return false; + } + + /* draw any text succeeding highlighted portion */ + if (endtxt_idx < utf8_len) { + int px0 = max(x + endx, clip->x0); + if (px0 < clip->x1) { + + r.x0 = px0; + r.y0 = clip->y0; + r.x1 = clip->x1; + r.y1 = clip->y1; + res = ctx->plot->clip(ctx, &r); + if (res != NSERROR_OK) { + return false; + } + + clip_changed = true; + + res = ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int)(height * 0.75 * scale), + utf8_text, + utf8_len); + if (res != NSERROR_OK) { + return false; + } + } + } + + if (clip_changed && + (ctx->plot->clip(ctx, clip) != NSERROR_OK)) { + return false; + } + } + } + + if (!highlighted) { + res = ctx->plot->text(ctx, + &plot_fstyle, + x, + y + (int) (height * 0.75 * scale), + utf8_text, + utf8_len); + if (res != NSERROR_OK) { + return false; + } + } + return true; +} + + +/** + * Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot). + * + * x, y, clip_[xy][01] are in target coordinates. + * + * \param c content of type CONTENT_TEXTPLAIN + * \param data redraw data for this content redraw + * \param clip current clip region + * \param ctx current redraw context + * \return true if successful, false otherwise + */ +static bool +textplain_redraw(struct content *c, + struct content_redraw_data *data, + const struct rect *clip, + const struct redraw_context *ctx) +{ + textplain_content *text = (textplain_content *) c; + struct browser_window *bw = text->bw; + char *utf8_data = text->utf8_data; + long lineno; + int x = data->x; + int y = data->y; + unsigned long line_count = text->physical_line_count; + float line_height = textplain_line_height(); + float scaled_line_height = line_height * data->scale; + long line0 = (clip->y0 - y * data->scale) / scaled_line_height - 1; + long line1 = (clip->y1 - y * data->scale) / scaled_line_height + 1; + struct textplain_line *line = text->physical_line; + size_t length; + plot_style_t *plot_style_highlight; + nserror res; + + if (line0 < 0) + line0 = 0; + if (line1 < 0) + line1 = 0; + if (line_count < (unsigned long) line0) + line0 = line_count; + if (line_count < (unsigned long) line1) + line1 = line_count; + if (line1 < line0) + line1 = line0; + + res = ctx->plot->rectangle(ctx, plot_style_fill_white, clip); + if (res != NSERROR_OK) { + return false; + } + + if (!line) + return true; + + /* choose a suitable background colour for any highlighted text */ + if ((data->background_colour & 0x808080) == 0x808080) + plot_style_highlight = plot_style_fill_black; + else + plot_style_highlight = plot_style_fill_white; + + /* Set up font plot style */ + textplain_style.background = data->background_colour; + + x = (x + MARGIN) * data->scale; + y = (y + MARGIN) * data->scale; + for (lineno = line0; lineno != line1; lineno++) { + const char *text_d = utf8_data + line[lineno].start; + int tab_width = textplain_tab_width * data->scale; + size_t offset = 0; + int tx = x; + + if (!tab_width) tab_width = 1; + + length = line[lineno].length; + if (!length) + continue; + + while (offset < length) { + size_t next_offset = offset; + int width; + int ntx; + nserror res; + + while ((next_offset < length) && + (text_d[next_offset] != '\t')) { + next_offset = utf8_next(text_d, + length, + next_offset); + } + + if (!text_draw(text_d + offset, + next_offset - offset, + line[lineno].start + offset, + tx, + y + (lineno * scaled_line_height), + clip, + line_height, + data->scale, + text, + &text->sel, + text->search, + ctx)) { + return false; + } + + if (next_offset >= length) + break; + + res = guit->layout->width(&textplain_style, + &text_d[offset], + next_offset - offset, + &width); + /* locate end of string and align to next tab position */ + if (res == NSERROR_OK) { + tx += (int)(width * data->scale); + } + + ntx = x + ((1 + (tx - x) / tab_width) * tab_width); + + /* if the tab character lies within the + * selection, if any, then we must draw it as + * a filled rectangle so that it's consistent + * with background of the selected text + */ + + if (bw) { + unsigned tab_ofst = line[lineno].start + next_offset; + struct selection *sel = &text->sel; + bool highlighted = false; + + if (selection_defined(sel)) { + unsigned start_idx, end_idx; + if (selection_highlighted(sel, + tab_ofst, + tab_ofst + 1, + &start_idx, + &end_idx)) + highlighted = true; + } + + if (!highlighted && (text->search != NULL)) { + unsigned start_idx, end_idx; + if (search_term_highlighted(c, + tab_ofst, + tab_ofst + 1, + &start_idx, + &end_idx, + text->search)) + highlighted = true; + } + + if (highlighted) { + struct rect rect; + rect.x0 = tx; + rect.y0 = y + (lineno * scaled_line_height); + rect.x1 = ntx; + rect.y1 = rect.y0 + scaled_line_height; + res = ctx->plot->rectangle(ctx, + plot_style_highlight, + &rect); + if (res != NSERROR_OK) { + return false; + } + } + } + + offset = next_offset + 1; + tx = ntx; + } + } + + return true; +} + + +/** + * Handle a window containing a CONTENT_TEXTPLAIN being opened. + */ +static void +textplain_open(struct content *c, + struct browser_window *bw, + struct content *page, + struct object_params *params) +{ + textplain_content *text = (textplain_content *) c; + + text->bw = bw; + + /* text selection */ + selection_init(&text->sel, NULL, NULL); +} + + +/** + * Handle a window containing a CONTENT_TEXTPLAIN being closed. + */ +static void textplain_close(struct content *c) +{ + textplain_content *text = (textplain_content *) c; + + if (text->search != NULL) { + search_destroy_context(text->search); + } + + text->bw = NULL; +} + + +/** + * Return an textplain content's selection context + */ +static char *textplain_get_selection(struct content *c) +{ + textplain_content *text = (textplain_content *) c; + + return selection_get_copy(&text->sel); +} + + +/** + * Convert a character offset within a line of text into the + * horizontal co-ordinate + * + * The conversion takes into account the font being used and any tabs + * in the text + * + * \param text line of text + * \param offset char offset within text + * \param length line length + * \return x ordinate + */ +static int +textplain_coord_from_offset(const char *text, size_t offset, size_t length) +{ + int x = 0; + + while (offset > 0) { + size_t next_offset = 0; + int tx; + + while (next_offset < offset && text[next_offset] != '\t') { + next_offset = utf8_next(text, length, next_offset); + } + + guit->layout->width(&textplain_style, text, next_offset, &tx); + + x += tx; + + if (next_offset >= offset) + break; + + /* align to next tab boundary */ + next_offset++; + x = (1 + (x / textplain_tab_width)) * textplain_tab_width; + offset -= next_offset; + text += next_offset; + length -= next_offset; + } + + return x; +} + + +/** + * plain text content handler table + */ +static const content_handler textplain_content_handler = { + .fini = textplain_fini, + .create = textplain_create, + .process_data = textplain_process_data, + .data_complete = textplain_convert, + .reformat = textplain_reformat, + .destroy = textplain_destroy, + .mouse_track = textplain_mouse_track, + .mouse_action = textplain_mouse_action, + .keypress = textplain_keypress, + .search = textplain_search, + .search_clear = textplain_search_clear, + .redraw = textplain_redraw, + .open = textplain_open, + .close = textplain_close, + .get_selection = textplain_get_selection, + .clone = textplain_clone, + .type = textplain_content_type, + .no_share = true, +}; + + +/* exported interface documented in html/textplain.h */ +nserror textplain_init(void) +{ + lwc_error lerror; + nserror error; + + lerror = lwc_intern_string("Windows-1252", + SLEN("Windows-1252"), + &textplain_default_charset); + if (lerror != lwc_error_ok) { + return NSERROR_NOMEM; + } + + error = content_factory_register_handler("text/plain", + &textplain_content_handler); + if (error != NSERROR_OK) { + lwc_string_unref(textplain_default_charset); + } + + return error; +} + + +/* exported interface documented in html/textplain.h */ +unsigned long textplain_line_count(struct content *c) +{ + textplain_content *text = (textplain_content *) c; + + assert(c != NULL); + + return text->physical_line_count; +} + + +/* exported interface documented in html/textplain.h */ +size_t textplain_size(struct content *c) +{ + textplain_content *text = (textplain_content *) c; + + assert(c != NULL); + + return text->utf8_data_size; +} + + +/* exported interface documented in html/textplain.h */ +size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir) +{ + textplain_content *textc = (textplain_content *) c; + float line_height = textplain_line_height(); + struct textplain_line *line; + const char *text; + unsigned nlines; + size_t length; + int idx; + + assert(c != NULL); + + y = (int)((float)(y - MARGIN) / line_height); + x -= MARGIN; + + nlines = textc->physical_line_count; + if (!nlines) + return 0; + + if (y <= 0) y = 0; + else if ((unsigned)y >= nlines) + y = nlines - 1; + + line = &textc->physical_line[y]; + text = textc->utf8_data + line->start; + length = line->length; + idx = 0; + + while (x > 0) { + size_t next_offset = 0; + int width = INT_MAX; + + while (next_offset < length && text[next_offset] != '\t') { + next_offset = utf8_next(text, length, next_offset); + } + + if (next_offset < length) { + guit->layout->width(&textplain_style, + text, + next_offset, + &width); + } + + if (x <= width) { + int pixel_offset; + size_t char_offset; + + guit->layout->position(&textplain_style, + text, next_offset, x, + &char_offset, &pixel_offset); + + idx += char_offset; + break; + } + + x -= width; + length -= next_offset; + text += next_offset; + idx += next_offset; + + /* check if it's within the tab */ + width = textplain_tab_width - (width % textplain_tab_width); + if (x <= width) break; + + x -= width; + length--; + text++; + idx++; + } + + return line->start + idx; +} + + +/* exported interface documented in html/textplain.h */ +void +textplain_coords_from_range(struct content *c, + unsigned start, + unsigned end, + struct rect *r) +{ + textplain_content *text = (textplain_content *) c; + float line_height = textplain_line_height(); + char *utf8_data; + struct textplain_line *line; + unsigned lineno = 0; + unsigned nlines; + + assert(c != NULL); + assert(start <= end); + assert(end <= text->utf8_data_size); + + utf8_data = text->utf8_data; + nlines = text->physical_line_count; + line = text->physical_line; + + /* find start */ + lineno = textplain_find_line(c, start); + + r->y0 = (int)(MARGIN + lineno * line_height); + + if (lineno + 1 <= nlines || line[lineno + 1].start >= end) { + /* \todo - it may actually be more efficient just to + * run forwards most of the time + */ + + /* find end */ + lineno = textplain_find_line(c, end); + + r->x0 = 0; + r->x1 = text->formatted_width; + } else { + /* single line */ + const char *text = utf8_data + line[lineno].start; + + r->x0 = textplain_coord_from_offset(text, + start - line[lineno].start, + line[lineno].length); + + r->x1 = textplain_coord_from_offset(text, + end - line[lineno].start, + line[lineno].length); + } + + r->y1 = (int)(MARGIN + (lineno + 1) * line_height); +} + + +/* exported interface documented in html/textplain.h */ +char * +textplain_get_line(struct content *c, + unsigned lineno, + size_t *poffset, + size_t *plen) +{ + textplain_content *text = (textplain_content *) c; + struct textplain_line *line; + + assert(c != NULL); + + if (lineno >= text->physical_line_count) + return NULL; + line = &text->physical_line[lineno]; + + *poffset = line->start; + *plen = line->length; + return text->utf8_data + line->start; +} + + +/* exported interface documented in html/textplain.h */ +int textplain_find_line(struct content *c, unsigned offset) +{ + textplain_content *text = (textplain_content *) c; + struct textplain_line *line; + int nlines; + int lineno = 0; + + assert(c != NULL); + + line = text->physical_line; + nlines = text->physical_line_count; + + if (offset > text->utf8_data_size) { + return -1; + } + +/* \todo - implement binary search here */ + while (lineno < nlines && line[lineno].start < offset) { + lineno++; + } + if (line[lineno].start > offset) { + lineno--; + } + + return lineno; +} + + +/* exported interface documented in html/textplain.h */ +char * +textplain_get_raw_data(struct content *c, + unsigned start, + unsigned end, + size_t *plen) +{ + textplain_content *text = (textplain_content *) c; + size_t utf8_size; + + assert(c != NULL); + + utf8_size = text->utf8_data_size; + + /* any text at all? */ + if (!utf8_size) return NULL; + + /* clamp to valid offset range */ + if (start >= utf8_size) start = utf8_size; + if (end >= utf8_size) end = utf8_size; + + *plen = end - start; + + return text->utf8_data + start; +} + + +/* exported interface documented in html/textplain.h */ +struct browser_window *textplain_get_browser_window(struct content *c) +{ + textplain_content *text = (textplain_content *) c; + + assert(c != NULL); + assert(c->handler == &textplain_content_handler); + + return text->bw; +} diff --git a/content/handlers/text/textplain.h b/content/handlers/text/textplain.h new file mode 100644 index 000000000..23917fb6c --- /dev/null +++ b/content/handlers/text/textplain.h @@ -0,0 +1,139 @@ +/* + * Copyright 2006 James Bursa + * Copyright 2006 Adrian Lees + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * + * Interface to content handler for plain text. + */ + +#ifndef NETSURF_HTML_TEXTPLAIN_H +#define NETSURF_HTML_TEXTPLAIN_H + +#include +#include "netsurf/mouse.h" + +struct content; +struct hlcache_handle; +struct http_parameter; +struct rect; + +/** + * Initialise the text content handler + * + * \return NSERROR_OK on success else appropriate error code. + */ +nserror textplain_init(void); + + +/** + * Retrieve number of lines in content + * + * \param[in] c Content to retrieve line count from + * \return Number of lines + */ +unsigned long textplain_line_count(struct content *c); + + +/** + * Retrieve the size (in bytes) of text data + * + * \param[in] c Content to retrieve size of + * \return Size, in bytes, of data + */ +size_t textplain_size(struct content *c); + + +/** + * Return byte offset within UTF8 textplain content. + * + * given the co-ordinates of a point within a textplain content. 'dir' + * specifies the direction in which to search (-1 = above-left, +1 = + * below-right) if the co-ordinates are not contained within a line. + * + * \param[in] c content of type CONTENT_TEXTPLAIN + * \param[in] x x ordinate of point + * \param[in] y y ordinate of point + * \param[in] dir direction of search if not within line + * \return byte offset of character containing (or nearest to) point + */ +size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir); + + +/** + * Given a range of byte offsets within a UTF8 textplain content, + * return a box that fully encloses the text + * + * \param[in] c content of type CONTENT_TEXTPLAIN + * \param[in] start byte offset of start of text range + * \param[in] end byte offset of end + * \param[out] r rectangle to be completed + */ +void textplain_coords_from_range(struct content *c, + unsigned start, unsigned end, struct rect *r); + +/** + * Return a pointer to the requested line of text. + * + * \param[in] c content of type CONTENT_TEXTPLAIN + * \param[in] lineno line number + * \param[out] poffset receives byte offset of line start within text + * \param[out] plen receives length of returned line + * \return pointer to text, or NULL if invalid line number + */ +char *textplain_get_line(struct content *c, unsigned lineno, + size_t *poffset, size_t *plen); + + +/** + * Find line number of byte in text + * + * Given a byte offset within the text, return the line number + * of the line containing that offset. + * + * \param[in] c content of type CONTENT_TEXTPLAIN + * \param[in] offset byte offset within textual representation + * \return line number, or -1 if offset invalid (larger than size) + */ +int textplain_find_line(struct content *c, unsigned offset); + + +/** + * Return a pointer to the raw UTF-8 data, as opposed to the reformatted + * text to fit the window width. Thus only hard newlines are preserved + * in the saved/copied text of a selection. + * + * \param[in] c content of type CONTENT_TEXTPLAIN + * \param[in] start starting byte offset within UTF-8 text + * \param[in] end ending byte offset + * \param[out] plen receives validated length + * \return pointer to text, or NULL if no text + */ +char *textplain_get_raw_data(struct content *c, unsigned start, unsigned end, size_t *plen); + + +/** + * Get the browser window containing a textplain content + * + * \param[in] c text/plain content + * \return the browser window + */ +struct browser_window *textplain_get_browser_window(struct content *c); + +#endif diff --git a/desktop/browser.c b/desktop/browser.c index 88d78ba95..589016216 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -56,9 +56,9 @@ #include "content/hlcache.h" #include "content/urldb.h" #include "css/utils.h" -#include "render/form_internal.h" -#include "render/html.h" -#include "render/box.h" +#include "html/form_internal.h" +#include "html/html.h" +#include "html/box.h" #include "javascript/js.h" #include "desktop/browser_history.h" diff --git a/desktop/frames.c b/desktop/frames.c index e22287630..ebc54c6d5 100644 --- a/desktop/frames.c +++ b/desktop/frames.c @@ -33,8 +33,8 @@ #include "utils/utils.h" #include "netsurf/content.h" #include "content/hlcache.h" -#include "render/html.h" -#include "render/box.h" +#include "html/html.h" +#include "html/box.h" #include "desktop/browser_private.h" #include "desktop/frames.h" diff --git a/desktop/netsurf.c b/desktop/netsurf.c index 8aa949a5a..76ff4b19a 100644 --- a/desktop/netsurf.c +++ b/desktop/netsurf.c @@ -41,8 +41,8 @@ #include "image/image.h" #include "image/image_cache.h" #include "javascript/js.h" -#include "render/html.h" -#include "render/textplain.h" +#include "html/html.h" +#include "text/textplain.h" #include "netsurf/browser_window.h" #include "desktop/system_colour.h" diff --git a/desktop/print.c b/desktop/print.c index 5c0333a96..de579dcf2 100644 --- a/desktop/print.c +++ b/desktop/print.c @@ -34,7 +34,7 @@ #include "netsurf/plotters.h" #include "content/hlcache.h" #include "css/utils.h" -#include "render/box.h" +#include "html/box.h" #include "desktop/print.h" #include "desktop/printer.h" diff --git a/desktop/save_complete.c b/desktop/save_complete.c index 9a88ad180..cd4ab30cf 100644 --- a/desktop/save_complete.c +++ b/desktop/save_complete.c @@ -43,8 +43,8 @@ #include "netsurf/content.h" #include "content/hlcache.h" #include "css/css.h" -#include "render/box.h" -#include "render/html.h" +#include "html/box.h" +#include "html/html.h" #include "netsurf/misc.h" #include "desktop/gui_internal.h" diff --git a/desktop/save_text.c b/desktop/save_text.c index 791ae9201..c4abb16f1 100644 --- a/desktop/save_text.c +++ b/desktop/save_text.c @@ -32,8 +32,8 @@ #include "utils/utf8.h" #include "utils/utils.h" #include "netsurf/content.h" -#include "render/box.h" -#include "render/html.h" +#include "html/box.h" +#include "html/html.h" #include "netsurf/utf8.h" #include "desktop/gui_internal.h" diff --git a/desktop/selection.c b/desktop/selection.c index 5cb43b8c3..35eabb2a9 100644 --- a/desktop/selection.c +++ b/desktop/selection.c @@ -32,10 +32,10 @@ #include "utils/utf8.h" #include "utils/utils.h" #include "netsurf/form.h" -#include "render/box.h" -#include "render/html_internal.h" -#include "render/font.h" -#include "render/textplain.h" +#include "html/box.h" +#include "html/html_internal.h" +#include "html/font.h" +#include "text/textplain.h" #include "netsurf/mouse.h" #include "desktop/browser_private.h" diff --git a/desktop/textinput.c b/desktop/textinput.c index c0e0ba8f7..7fc95f792 100644 --- a/desktop/textinput.c +++ b/desktop/textinput.c @@ -37,9 +37,9 @@ #include "netsurf/form.h" #include "netsurf/window.h" #include "netsurf/keypress.h" -#include "render/box.h" -#include "render/html_internal.h" -#include "render/layout.h" +#include "html/box.h" +#include "html/html_internal.h" +#include "html/layout.h" #include "desktop/browser_private.h" #include "desktop/textinput.h" diff --git a/frontends/amiga/dt_sound.c b/frontends/amiga/dt_sound.c index e0f48da83..ae313bb51 100644 --- a/frontends/amiga/dt_sound.c +++ b/frontends/amiga/dt_sound.c @@ -33,7 +33,7 @@ #include "utils/messages.h" #include "netsurf/plotters.h" #include "netsurf/content.h" -#include "render/box.h" +#include "html/box.h" #include "content/llcache.h" #include "content/content_protected.h" diff --git a/render/Makefile b/render/Makefile deleted file mode 100644 index dc2e31c9e..000000000 --- a/render/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# Render sources - -S_RENDER := box.c box_construct.c box_normalise.c box_textarea.c \ - font.c form.c imagemap.c layout.c search.c table.c textplain.c \ - html.c html_css.c html_css_fetcher.c html_script.c \ - html_interaction.c html_redraw.c html_redraw_border.c \ - html_forms.c html_object.c - - -S_RENDER := $(addprefix render/,$(S_RENDER)) diff --git a/render/box.c b/render/box.c deleted file mode 100644 index c97e8982b..000000000 --- a/render/box.c +++ /dev/null @@ -1,1242 +0,0 @@ -/* - * Copyright 2005-2007 James Bursa - * Copyright 2003 Phil Mellor - * Copyright 2005 John M Bell - * Copyright 2008 Michael Drake - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * implementation of box tree manipulation. - */ - -#include -#include -#include -#include -#include - -#include "utils/nsoption.h" -#include "utils/log.h" -#include "utils/talloc.h" -#include "netsurf/misc.h" -#include "netsurf/content.h" -#include "netsurf/mouse.h" -#include "css/utils.h" -#include "css/dump.h" -#include "desktop/scrollbar.h" -#include "desktop/gui_internal.h" - -#include "render/box.h" -#include "render/form_internal.h" -#include "render/html_internal.h" - -#define box_is_float(box) (box->type == BOX_FLOAT_LEFT || \ - box->type == BOX_FLOAT_RIGHT) - -/** - * Destructor for box nodes which own styles - * - * \param b The box being destroyed. - * \return 0 to allow talloc to continue destroying the tree. - */ -static int box_talloc_destructor(struct box *b) -{ - struct html_scrollbar_data *data; - - if ((b->flags & STYLE_OWNED) && b->style != NULL) { - css_computed_style_destroy(b->style); - b->style = NULL; - } - - if (b->styles != NULL) { - css_select_results_destroy(b->styles); - b->styles = NULL; - } - - if (b->href != NULL) - nsurl_unref(b->href); - - if (b->id != NULL) { - lwc_string_unref(b->id); - } - - if (b->node != NULL) { - dom_node_unref(b->node); - } - - if (b->scroll_x != NULL) { - data = scrollbar_get_data(b->scroll_x); - scrollbar_destroy(b->scroll_x); - free(data); - } - - if (b->scroll_y != NULL) { - data = scrollbar_get_data(b->scroll_y); - scrollbar_destroy(b->scroll_y); - free(data); - } - - return 0; -} - -/** - * Create a box tree node. - * - * \param styles selection results for the box, or NULL - * \param style computed style for the box (not copied), or 0 - * \param style_owned whether style is owned by this box - * \param href href for the box (copied), or 0 - * \param target target for the box (not copied), or 0 - * \param title title for the box (not copied), or 0 - * \param id id for the box (not copied), or 0 - * \param context context for allocations - * \return allocated and initialised box, or 0 on memory exhaustion - * - * styles is always owned by the box, if it is set. - * style is only owned by the box in the case of implied boxes. - */ - -struct box * box_create(css_select_results *styles, css_computed_style *style, - bool style_owned, nsurl *href, const char *target, - const char *title, lwc_string *id, void *context) -{ - unsigned int i; - struct box *box; - - box = talloc(context, struct box); - if (!box) { - return 0; - } - - talloc_set_destructor(box, box_talloc_destructor); - - box->type = BOX_INLINE; - box->flags = 0; - box->flags = style_owned ? (box->flags | STYLE_OWNED) : box->flags; - box->styles = styles; - box->style = style; - box->x = box->y = 0; - box->width = UNKNOWN_WIDTH; - box->height = 0; - box->descendant_x0 = box->descendant_y0 = 0; - box->descendant_x1 = box->descendant_y1 = 0; - for (i = 0; i != 4; i++) - box->margin[i] = box->padding[i] = box->border[i].width = 0; - box->scroll_x = box->scroll_y = NULL; - box->min_width = 0; - box->max_width = UNKNOWN_MAX_WIDTH; - box->byte_offset = 0; - box->text = NULL; - box->length = 0; - box->space = 0; - box->href = (href == NULL) ? NULL : nsurl_ref(href); - box->target = target; - box->title = title; - box->columns = 1; - box->rows = 1; - box->start_column = 0; - box->next = NULL; - box->prev = NULL; - box->children = NULL; - box->last = NULL; - box->parent = NULL; - box->inline_end = NULL; - box->float_children = NULL; - box->float_container = NULL; - box->next_float = NULL; - box->cached_place_below_level = 0; - box->list_marker = NULL; - box->col = NULL; - box->gadget = NULL; - box->usemap = NULL; - box->id = id; - box->background = NULL; - box->object = NULL; - box->object_params = NULL; - box->iframe = NULL; - box->node = NULL; - - return box; -} - -/** - * Add a child to a box tree node. - * - * \param parent box giving birth - * \param child box to link as last child of parent - */ - -void box_add_child(struct box *parent, struct box *child) -{ - assert(parent); - assert(child); - - if (parent->children != 0) { /* has children already */ - parent->last->next = child; - child->prev = parent->last; - } else { /* this is the first child */ - parent->children = child; - child->prev = 0; - } - - parent->last = child; - child->parent = parent; -} - - -/** - * Insert a new box as a sibling to a box in a tree. - * - * \param box box already in tree - * \param new_box box to link into tree as next sibling - */ - -void box_insert_sibling(struct box *box, struct box *new_box) -{ - new_box->parent = box->parent; - new_box->prev = box; - new_box->next = box->next; - box->next = new_box; - if (new_box->next) - new_box->next->prev = new_box; - else if (new_box->parent) - new_box->parent->last = new_box; -} - - -/** - * Unlink a box from the box tree and then free it recursively. - * - * \param box box to unlink and free recursively. - */ - -void box_unlink_and_free(struct box *box) -{ - struct box *parent = box->parent; - struct box *next = box->next; - struct box *prev = box->prev; - - if (parent) { - if (parent->children == box) - parent->children = next; - if (parent->last == box) - parent->last = next ? next : prev; - } - - if (prev) - prev->next = next; - if (next) - next->prev = prev; - - box_free(box); -} - - -/** - * Free a box tree recursively. - * - * \param box box to free recursively - * - * The box and all its children is freed. - */ - -void box_free(struct box *box) -{ - struct box *child, *next; - - /* free children first */ - for (child = box->children; child; child = next) { - next = child->next; - box_free(child); - } - - /* last this box */ - box_free_box(box); -} - - -/** - * Free the data in a single box structure. - * - * \param box box to free - */ - -void box_free_box(struct box *box) -{ - if (!(box->flags & CLONE)) { - if (box->gadget) - form_free_control(box->gadget); - if (box->scroll_x != NULL) - scrollbar_destroy(box->scroll_x); - if (box->scroll_y != NULL) - scrollbar_destroy(box->scroll_y); - if (box->styles != NULL) - css_select_results_destroy(box->styles); - } - - talloc_free(box); -} - - -/** - * Find the absolute coordinates of a box. - * - * \param box the box to calculate coordinates of - * \param x updated to x coordinate - * \param y updated to y coordinate - */ - -void box_coords(struct box *box, int *x, int *y) -{ - *x = box->x; - *y = box->y; - while (box->parent) { - if (box_is_float(box)) { - do { - box = box->parent; - } while (!box->float_children); - } else - box = box->parent; - *x += box->x - scrollbar_get_offset(box->scroll_x); - *y += box->y - scrollbar_get_offset(box->scroll_y); - } -} - - -/** - * Find the bounds of a box. - * - * \param box the box to calculate bounds of - * \param r receives bounds - */ - -void box_bounds(struct box *box, struct rect *r) -{ - int width, height; - - box_coords(box, &r->x0, &r->y0); - - width = box->padding[LEFT] + box->width + box->padding[RIGHT]; - height = box->padding[TOP] + box->height + box->padding[BOTTOM]; - - r->x1 = r->x0 + width; - r->y1 = r->y0 + height; -} - - -/** - * Determine if a point lies within a box. - * - * \param[in] len_ctx CSS length conversion context to use. - * \param[in] box Box to consider - * \param[in] x Coordinate relative to box - * \param[in] y Coordinate relative to box - * \param[out] physically If function returning true, physically is set true - * iff point is within the box's physical dimensions and - * false if the point is not within the box's physical - * dimensions but is in the area defined by the box's - * descendants. If function returns false, physically - * is undefined. - * \return true if the point is within the box or a descendant box - * - * This is a helper function for box_at_point(). - */ - -static bool box_contains_point( - const nscss_len_ctx *len_ctx, - const struct box *box, - int x, - int y, - bool *physically) -{ - css_computed_clip_rect css_rect; - - if (box->style != NULL && - css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE && - css_computed_clip(box->style, &css_rect) == - CSS_CLIP_RECT) { - /* We have an absolutly positioned box with a clip rect */ - struct rect r = { - .x0 = box->border[LEFT].width, - .y0 = box->border[TOP].width, - .x1 = box->padding[LEFT] + box->width + - box->border[RIGHT].width + - box->padding[RIGHT], - .y1 = box->padding[TOP] + box->height + - box->border[BOTTOM].width + - box->padding[BOTTOM] - }; - if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) - *physically = true; - else - *physically = false; - - /* Adjust rect to css clip region */ - if (css_rect.left_auto == false) { - r.x0 += FIXTOINT(nscss_len2px(len_ctx, - css_rect.left, css_rect.lunit, - box->style)); - } - if (css_rect.top_auto == false) { - r.y0 += FIXTOINT(nscss_len2px(len_ctx, - css_rect.top, css_rect.tunit, - box->style)); - } - if (css_rect.right_auto == false) { - r.x1 = box->border[LEFT].width + - FIXTOINT(nscss_len2px(len_ctx, - css_rect.right, - css_rect.runit, - box->style)); - } - if (css_rect.bottom_auto == false) { - r.y1 = box->border[TOP].width + - FIXTOINT(nscss_len2px(len_ctx, - css_rect.bottom, - css_rect.bunit, - box->style)); - } - - /* Test if point is in clipped box */ - if (x >= r.x0 && x < r.x1 && y >= r.y0 && y < r.y1) { - /* inside clip area */ - return true; - } - - /* Not inside clip area */ - return false; - } - if (x >= -box->border[LEFT].width && - x < box->padding[LEFT] + box->width + - box->padding[RIGHT] + box->border[RIGHT].width && - y >= -box->border[TOP].width && - y < box->padding[TOP] + box->height + - box->padding[BOTTOM] + box->border[BOTTOM].width) { - *physically = true; - return true; - } - if (box->list_marker && box->list_marker->x - box->x <= x + - box->list_marker->border[LEFT].width && - x < box->list_marker->x - box->x + - box->list_marker->padding[LEFT] + - box->list_marker->width + - box->list_marker->border[RIGHT].width + - box->list_marker->padding[RIGHT] && - box->list_marker->y - box->y <= y + - box->list_marker->border[TOP].width && - y < box->list_marker->y - box->y + - box->list_marker->padding[TOP] + - box->list_marker->height + - box->list_marker->border[BOTTOM].width + - box->list_marker->padding[BOTTOM]) { - *physically = true; - return true; - } - if ((box->style && css_computed_overflow_x(box->style) == - CSS_OVERFLOW_VISIBLE) || !box->style) { - if (box->descendant_x0 <= x && - x < box->descendant_x1) { - *physically = false; - return true; - } - } - if ((box->style && css_computed_overflow_y(box->style) == - CSS_OVERFLOW_VISIBLE) || !box->style) { - if (box->descendant_y0 <= y && - y < box->descendant_y1) { - *physically = false; - return true; - } - } - return false; -} - - -/** Direction to move in a box-tree walk */ -enum box_walk_dir { - BOX_WALK_CHILDREN, - BOX_WALK_PARENT, - BOX_WALK_NEXT_SIBLING, - BOX_WALK_FLOAT_CHILDREN, - BOX_WALK_NEXT_FLOAT_SIBLING, - BOX_WALK_FLOAT_CONTAINER -}; - - -/** - * Move from box to next box in given direction, adjusting for box coord change - * - * \param b box to move from from - * \param dir direction to move in - * \param x box's global x-coord, updated to position of next box - * \param y box's global y-coord, updated to position of next box - * - * If no box can be found in given direction, NULL is returned. - */ -static inline struct box *box_move_xy(struct box *b, enum box_walk_dir dir, - int *x, int *y) -{ - struct box *rb = NULL; - - switch (dir) { - case BOX_WALK_CHILDREN: - b = b->children; - if (b == NULL) - break; - *x += b->x; - *y += b->y; - if (!box_is_float(b)) { - rb = b; - break; - } - /* Fall through */ - - case BOX_WALK_NEXT_SIBLING: - do { - *x -= b->x; - *y -= b->y; - b = b->next; - if (b == NULL) - break; - *x += b->x; - *y += b->y; - } while (box_is_float(b)); - rb = b; - break; - - case BOX_WALK_PARENT: - *x -= b->x; - *y -= b->y; - rb = b->parent; - break; - - case BOX_WALK_FLOAT_CHILDREN: - b = b->float_children; - if (b == NULL) - break; - *x += b->x; - *y += b->y; - rb = b; - break; - - case BOX_WALK_NEXT_FLOAT_SIBLING: - *x -= b->x; - *y -= b->y; - b = b->next_float; - if (b == NULL) - break; - *x += b->x; - *y += b->y; - rb = b; - break; - - case BOX_WALK_FLOAT_CONTAINER: - *x -= b->x; - *y -= b->y; - rb = b->float_container; - break; - - default: - assert(0 && "Bad box walk type."); - } - - return rb; -} - - -/** - * Itterator for walking to next box in interaction order - * - * \param b box to find next box from - * \param x box's global x-coord, updated to position of next box - * \param y box's global y-coord, updated to position of next box - * \param skip_children whether to skip box's children - * - * This walks to a boxes float children before its children. When walking - * children, floating boxes are skipped. - */ -static inline struct box *box_next_xy(struct box *b, int *x, int *y, - bool skip_children) -{ - struct box *n; - int tx, ty; - - assert(b != NULL); - - if (skip_children) { - /* Caller is not interested in any kind of children */ - goto skip_children; - } - - tx = *x; ty = *y; - n = box_move_xy(b, BOX_WALK_FLOAT_CHILDREN, &tx, &ty); - if (n) { - /* Next node is float child */ - *x = tx; - *y = ty; - return n; - } -done_float_children: - - tx = *x; ty = *y; - n = box_move_xy(b, BOX_WALK_CHILDREN, &tx, &ty); - if (n) { - /* Next node is child */ - *x = tx; - *y = ty; - return n; - } - -skip_children: - tx = *x; ty = *y; - n = box_move_xy(b, BOX_WALK_NEXT_FLOAT_SIBLING, &tx, &ty); - if (n) { - /* Go to next float sibling */ - *x = tx; - *y = ty; - return n; - } - - if (box_is_float(b)) { - /* Done floats, but the float container may have children, - * or siblings, or ansestors with siblings. Change to - * float container and move past handling its float children. - */ - b = box_move_xy(b, BOX_WALK_FLOAT_CONTAINER, x, y); - goto done_float_children; - } - - /* Go to next sibling, or nearest ancestor with next sibling. */ - while (b) { - while (!b->next && b->parent) { - b = box_move_xy(b, BOX_WALK_PARENT, x, y); - if (box_is_float(b)) { - /* Go on to next float, if there is one */ - goto skip_children; - } - } - if (!b->next) { - /* No more boxes */ - return NULL; - } - - tx = *x; ty = *y; - n = box_move_xy(b, BOX_WALK_NEXT_SIBLING, &tx, &ty); - if (n) { - /* Go to non-float (ancestor) sibling */ - *x = tx; - *y = ty; - return n; - - } else if (b->parent) { - b = box_move_xy(b, BOX_WALK_PARENT, x, y); - if (box_is_float(b)) { - /* Go on to next float, if there is one */ - goto skip_children; - } - - } else { - /* No more boxes */ - return NULL; - } - } - - assert(b != NULL); - return NULL; -} - - - -/** - * Find the boxes at a point. - * - * \param len_ctx CSS length conversion context for document. - * \param box box to search children of - * \param x point to find, in global document coordinates - * \param y point to find, in global document coordinates - * \param box_x position of box, in global document coordinates, updated - * to position of returned box, if any - * \param box_y position of box, in global document coordinates, updated - * to position of returned box, if any - * \return box at given point, or 0 if none found - * - * To find all the boxes in the hierarchy at a certain point, use code like - * this: - * \code - * struct box *box = top_of_document_to_search; - * int box_x = 0, box_y = 0; - * - * while ((box = box_at_point(len_ctx, box, x, y, &box_x, &box_y))) { - * // process box - * } - * \endcode - */ - -struct box *box_at_point(const nscss_len_ctx *len_ctx, - struct box *box, const int x, const int y, - int *box_x, int *box_y) -{ - bool skip_children; - bool physically; - - assert(box); - - skip_children = false; - while ((box = box_next_xy(box, box_x, box_y, skip_children))) { - if (box_contains_point(len_ctx, box, x - *box_x, y - *box_y, - &physically)) { - *box_x -= scrollbar_get_offset(box->scroll_x); - *box_y -= scrollbar_get_offset(box->scroll_y); - - if (physically) - return box; - - skip_children = false; - } else { - skip_children = true; - } - } - - return NULL; -} - - -/** - * Check whether box is nearer mouse coordinates than current nearest box - * - * \param box box to test - * \param bx position of box, in global document coordinates - * \param by position of box, in global document coordinates - * \param x mouse point, in global document coordinates - * \param y mouse point, in global document coordinates - * \param dir direction in which to search (-1 = above-left, - * +1 = below-right) - * \param nearest nearest text box found, or NULL if none - * updated if box is nearer than existing nearest - * \param tx position of text_box, in global document coordinates - * updated if box is nearer than existing nearest - * \param ty position of text_box, in global document coordinates - * updated if box is nearer than existing nearest - * \param nr_xd distance to nearest text box found - * updated if box is nearer than existing nearest - * \param nr_yd distance to nearest text box found - * updated if box is nearer than existing nearest - * \return true if mouse point is inside box - */ - -static bool box_nearer_text_box(struct box *box, int bx, int by, - int x, int y, int dir, struct box **nearest, int *tx, int *ty, - int *nr_xd, int *nr_yd) -{ - int w = box->padding[LEFT] + box->width + box->padding[RIGHT]; - int h = box->padding[TOP] + box->height + box->padding[BOTTOM]; - int y1 = by + h; - int x1 = bx + w; - int yd = INT_MAX; - int xd = INT_MAX; - - if (x >= bx && x1 > x && y >= by && y1 > y) { - *nearest = box; - *tx = bx; - *ty = by; - return true; - } - - if (box->parent->list_marker != box) { - if (dir < 0) { - /* consider only those children (partly) above-left */ - if (by <= y && bx < x) { - yd = y <= y1 ? 0 : y - y1; - xd = x <= x1 ? 0 : x - x1; - } - } else { - /* consider only those children (partly) below-right */ - if (y1 > y && x1 > x) { - yd = y > by ? 0 : by - y; - xd = x > bx ? 0 : bx - x; - } - } - - /* give y displacement precedence over x */ - if (yd < *nr_yd || (yd == *nr_yd && xd <= *nr_xd)) { - *nr_yd = yd; - *nr_xd = xd; - *nearest = box; - *tx = bx; - *ty = by; - } - } - return false; -} - - -/** - * Pick the text box child of 'box' that is closest to and above-left - * (dir -ve) or below-right (dir +ve) of the point 'x,y' - * - * \param box parent box - * \param bx position of box, in global document coordinates - * \param by position of box, in global document coordinates - * \param fx position of float parent, in global document coordinates - * \param fy position of float parent, in global document coordinates - * \param x mouse point, in global document coordinates - * \param y mouse point, in global document coordinates - * \param dir direction in which to search (-1 = above-left, - * +1 = below-right) - * \param nearest nearest text box found, or NULL if none - * updated if a descendant of box is nearer than old nearest - * \param tx position of nearest, in global document coordinates - * updated if a descendant of box is nearer than old nearest - * \param ty position of nearest, in global document coordinates - * updated if a descendant of box is nearer than old nearest - * \param nr_xd distance to nearest text box found - * updated if a descendant of box is nearer than old nearest - * \param nr_yd distance to nearest text box found - * updated if a descendant of box is nearer than old nearest - * \return true if mouse point is inside text_box - */ - -static bool box_nearest_text_box(struct box *box, int bx, int by, - int fx, int fy, int x, int y, int dir, struct box **nearest, - int *tx, int *ty, int *nr_xd, int *nr_yd) -{ - struct box *child = box->children; - int c_bx, c_by; - int c_fx, c_fy; - bool in_box = false; - - if (*nearest == NULL) { - *nr_xd = INT_MAX / 2; /* displacement of 'nearest so far' */ - *nr_yd = INT_MAX / 2; - } - if (box->type == BOX_INLINE_CONTAINER) { - int bw = box->padding[LEFT] + box->width + box->padding[RIGHT]; - int bh = box->padding[TOP] + box->height + box->padding[BOTTOM]; - int b_y1 = by + bh; - int b_x1 = bx + bw; - if (x >= bx && b_x1 > x && y >= by && b_y1 > y) { - in_box = true; - } - } - - while (child) { - if (child->type == BOX_FLOAT_LEFT || - child->type == BOX_FLOAT_RIGHT) { - c_bx = fx + child->x - - scrollbar_get_offset(child->scroll_x); - c_by = fy + child->y - - scrollbar_get_offset(child->scroll_y); - } else { - c_bx = bx + child->x - - scrollbar_get_offset(child->scroll_x); - c_by = by + child->y - - scrollbar_get_offset(child->scroll_y); - } - if (child->float_children) { - c_fx = c_bx; - c_fy = c_by; - } else { - c_fx = fx; - c_fy = fy; - } - if (in_box && child->text && !child->object) { - if (box_nearer_text_box(child, - c_bx, c_by, x, y, dir, nearest, - tx, ty, nr_xd, nr_yd)) - return true; - } else { - if (child->list_marker) { - if (box_nearer_text_box( - child->list_marker, - c_bx + child->list_marker->x, - c_by + child->list_marker->y, - x, y, dir, nearest, - tx, ty, nr_xd, nr_yd)) - return true; - } - if (box_nearest_text_box(child, c_bx, c_by, - c_fx, c_fy, x, y, dir, nearest, tx, ty, - nr_xd, nr_yd)) - return true; - } - child = child->next; - } - - return false; -} - - -/** - * Peform pick text on browser window contents to locate the box under - * the mouse pointer, or nearest in the given direction if the pointer is - * not over a text box. - * - * \param html an HTML content - * \param x coordinate of mouse - * \param y coordinate of mouse - * \param dir direction to search (-1 = above-left, +1 = below-right) - * \param dx receives x ordinate of mouse relative to text box - * \param dy receives y ordinate of mouse relative to text box - */ - -struct box *box_pick_text_box(struct html_content *html, - int x, int y, int dir, int *dx, int *dy) -{ - struct box *text_box = NULL; - struct box *box; - int nr_xd, nr_yd; - int bx, by; - int fx, fy; - int tx, ty; - - if (html == NULL) - return NULL; - - box = html->layout; - bx = box->margin[LEFT]; - by = box->margin[TOP]; - fx = bx; - fy = by; - - if (!box_nearest_text_box(box, bx, by, fx, fy, x, y, - dir, &text_box, &tx, &ty, &nr_xd, &nr_yd)) { - if (text_box && text_box->text && !text_box->object) { - int w = (text_box->padding[LEFT] + - text_box->width + - text_box->padding[RIGHT]); - int h = (text_box->padding[TOP] + - text_box->height + - text_box->padding[BOTTOM]); - int x1, y1; - - y1 = ty + h; - x1 = tx + w; - - /* ensure point lies within the text box */ - if (x < tx) x = tx; - if (y < ty) y = ty; - if (y > y1) y = y1; - if (x > x1) x = x1; - } - } - - /* return coordinates relative to box */ - *dx = x - tx; - *dy = y - ty; - - return text_box; -} - - -/** - * Find a box based upon its id attribute. - * - * \param box box tree to search - * \param id id to look for - * \return the box or 0 if not found - */ - -struct box *box_find_by_id(struct box *box, lwc_string *id) -{ - struct box *a, *b; - bool m; - - if (box->id != NULL && - lwc_string_isequal(id, box->id, &m) == lwc_error_ok && - m == true) - return box; - - for (a = box->children; a; a = a->next) { - if ((b = box_find_by_id(a, id)) != NULL) - return b; - } - - return NULL; -} - - -/** - * Determine if a box is visible when the tree is rendered. - * - * \param box box to check - * \return true iff the box is rendered - */ - -bool box_visible(struct box *box) -{ - /* visibility: hidden */ - if (box->style && css_computed_visibility(box->style) == - CSS_VISIBILITY_HIDDEN) - return false; - - return true; -} - - -/** - * Print a box tree to a file. - */ - -void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style) -{ - unsigned int i; - struct box *c, *prev; - - for (i = 0; i != depth; i++) - fprintf(stream, " "); - - fprintf(stream, "%p ", box); - fprintf(stream, "x%i y%i w%i h%i ", box->x, box->y, - box->width, box->height); - if (box->max_width != UNKNOWN_MAX_WIDTH) - fprintf(stream, "min%i max%i ", box->min_width, box->max_width); - fprintf(stream, "(%i %i %i %i) ", - box->descendant_x0, box->descendant_y0, - box->descendant_x1, box->descendant_y1); - - fprintf(stream, "m(%i %i %i %i) ", - box->margin[TOP], box->margin[LEFT], - box->margin[BOTTOM], box->margin[RIGHT]); - - switch (box->type) { - case BOX_BLOCK: fprintf(stream, "BLOCK "); break; - case BOX_INLINE_CONTAINER: fprintf(stream, "INLINE_CONTAINER "); break; - case BOX_INLINE: fprintf(stream, "INLINE "); break; - case BOX_INLINE_END: fprintf(stream, "INLINE_END "); break; - case BOX_INLINE_BLOCK: fprintf(stream, "INLINE_BLOCK "); break; - case BOX_TABLE: fprintf(stream, "TABLE [columns %i] ", - box->columns); break; - case BOX_TABLE_ROW: fprintf(stream, "TABLE_ROW "); break; - case BOX_TABLE_CELL: fprintf(stream, "TABLE_CELL [columns %i, " - "start %i, rows %i] ", box->columns, - box->start_column, box->rows); break; - case BOX_TABLE_ROW_GROUP: fprintf(stream, "TABLE_ROW_GROUP "); break; - case BOX_FLOAT_LEFT: fprintf(stream, "FLOAT_LEFT "); break; - case BOX_FLOAT_RIGHT: fprintf(stream, "FLOAT_RIGHT "); break; - case BOX_BR: fprintf(stream, "BR "); break; - case BOX_TEXT: fprintf(stream, "TEXT "); break; - default: fprintf(stream, "Unknown box type "); - } - - if (box->text) - fprintf(stream, "%li '%.*s' ", (unsigned long) box->byte_offset, - (int) box->length, box->text); - if (box->space) - fprintf(stream, "space "); - if (box->object) { - fprintf(stream, "(object '%s') ", - nsurl_access(hlcache_handle_get_url(box->object))); - } - if (box->iframe) { - fprintf(stream, "(iframe) "); - } - if (box->gadget) - fprintf(stream, "(gadget) "); - if (style && box->style) - nscss_dump_computed_style(stream, box->style); - if (box->href) - fprintf(stream, " -> '%s'", nsurl_access(box->href)); - if (box->target) - fprintf(stream, " |%s|", box->target); - if (box->title) - fprintf(stream, " [%s]", box->title); - if (box->id) - fprintf(stream, " ID:%s", lwc_string_data(box->id)); - if (box->type == BOX_INLINE || box->type == BOX_INLINE_END) - fprintf(stream, " inline_end %p", box->inline_end); - if (box->float_children) - fprintf(stream, " float_children %p", box->float_children); - if (box->next_float) - fprintf(stream, " next_float %p", box->next_float); - if (box->float_container) - fprintf(stream, " float_container %p", box->float_container); - if (box->col) { - fprintf(stream, " (columns"); - for (i = 0; i != box->columns; i++) - fprintf(stream, " (%s %s %i %i %i)", - ((const char *[]) {"UNKNOWN", "FIXED", - "AUTO", "PERCENT", "RELATIVE"}) - [box->col[i].type], - ((const char *[]) {"normal", - "positioned"}) - [box->col[i].positioned], - box->col[i].width, - box->col[i].min, box->col[i].max); - fprintf(stream, ")"); - } - if (box->node != NULL) { - dom_string *name; - if (dom_node_get_node_name(box->node, &name) == DOM_NO_ERR) { - fprintf(stream, " <%s>", dom_string_data(name)); - dom_string_unref(name); - } - } - fprintf(stream, "\n"); - - if (box->list_marker) { - for (i = 0; i != depth; i++) - fprintf(stream, " "); - fprintf(stream, "list_marker:\n"); - box_dump(stream, box->list_marker, depth + 1, style); - } - - for (c = box->children; c && c->next; c = c->next) - ; - if (box->last != c) - fprintf(stream, "warning: box->last %p (should be %p) " - "(box %p)\n", box->last, c, box); - for (prev = 0, c = box->children; c; prev = c, c = c->next) { - if (c->parent != box) - fprintf(stream, "warning: box->parent %p (should be " - "%p) (box on next line)\n", - c->parent, box); - if (c->prev != prev) - fprintf(stream, "warning: box->prev %p (should be " - "%p) (box on next line)\n", - c->prev, prev); - box_dump(stream, c, depth + 1, style); - } -} - -/** - * Applies the given scroll setup to a box. This includes scroll - * creation/deletion as well as scroll dimension updates. - * - * \param c content in which the box is located - * \param box the box to handle the scrolls for - * \param bottom whether the horizontal scrollbar should be present - * \param right whether the vertical scrollbar should be present - * \return true on success false otherwise - */ -bool box_handle_scrollbars(struct content *c, struct box *box, - bool bottom, bool right) -{ - struct html_scrollbar_data *data; - int visible_width, visible_height; - int full_width, full_height; - - if (!bottom && box->scroll_x != NULL) { - data = scrollbar_get_data(box->scroll_x); - scrollbar_destroy(box->scroll_x); - free(data); - box->scroll_x = NULL; - } - - if (!right && box->scroll_y != NULL) { - data = scrollbar_get_data(box->scroll_y); - scrollbar_destroy(box->scroll_y); - free(data); - box->scroll_y = NULL; - } - - if (!bottom && !right) - return true; - - visible_width = box->width + box->padding[RIGHT] + box->padding[LEFT]; - visible_height = box->height + box->padding[TOP] + box->padding[BOTTOM]; - - full_width = ((box->descendant_x1 - box->border[RIGHT].width) > - visible_width) ? - box->descendant_x1 + box->padding[RIGHT] : - visible_width; - full_height = ((box->descendant_y1 - box->border[BOTTOM].width) > - visible_height) ? - box->descendant_y1 + box->padding[BOTTOM] : - visible_height; - - if (right) { - if (box->scroll_y == NULL) { - data = malloc(sizeof(struct html_scrollbar_data)); - if (data == NULL) { - NSLOG(netsurf, INFO, "malloc failed"); - guit->misc->warning("NoMemory", 0); - return false; - } - data->c = c; - data->box = box; - if (scrollbar_create(false, visible_height, - full_height, visible_height, - data, html_overflow_scroll_callback, - &(box->scroll_y)) != NSERROR_OK) { - return false; - } - } else { - scrollbar_set_extents(box->scroll_y, visible_height, - visible_height, full_height); - } - } - if (bottom) { - if (box->scroll_x == NULL) { - data = malloc(sizeof(struct html_scrollbar_data)); - if (data == NULL) { - NSLOG(netsurf, INFO, "malloc failed"); - guit->misc->warning("NoMemory", 0); - return false; - } - data->c = c; - data->box = box; - if (scrollbar_create(true, - visible_width - - (right ? SCROLLBAR_WIDTH : 0), - full_width, visible_width, - data, html_overflow_scroll_callback, - &box->scroll_x) != NSERROR_OK) { - return false; - } - } else { - scrollbar_set_extents(box->scroll_x, - visible_width - - (right ? SCROLLBAR_WIDTH : 0), - visible_width, full_width); - } - } - - if (right && bottom) - scrollbar_make_pair(box->scroll_x, box->scroll_y); - - return true; -} - -/** - * Determine if a box has a vertical scrollbar. - * - * \param box scrolling box - * \return the box has a vertical scrollbar - */ - -bool box_vscrollbar_present(const struct box * const box) -{ - return box->padding[TOP] + box->height + box->padding[BOTTOM] + - box->border[BOTTOM].width < box->descendant_y1; -} - - -/** - * Determine if a box has a horizontal scrollbar. - * - * \param box scrolling box - * \return the box has a horizontal scrollbar - */ - -bool box_hscrollbar_present(const struct box * const box) -{ - return box->padding[LEFT] + box->width + box->padding[RIGHT] + - box->border[RIGHT].width < box->descendant_x1; -} - diff --git a/render/box.h b/render/box.h deleted file mode 100644 index 1af0a8b73..000000000 --- a/render/box.h +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright 2005 James Bursa - * Copyright 2003 Phil Mellor - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Box tree construction and manipulation (interface). - * - * This stage of rendering converts a tree of dom_nodes (produced by libdom) - * to a tree of struct box. The box tree represents the structure of the - * document as given by the CSS display and float properties. - * - * For example, consider the following HTML: - * \code - *

Example Heading

- *

Example paragraph with emphasised text etc.

\endcode - * - * This would produce approximately the following box tree with default CSS - * rules: - * \code - * BOX_BLOCK (corresponds to h1) - * BOX_INLINE_CONTAINER - * BOX_INLINE "Example Heading" - * BOX_BLOCK (p) - * BOX_INLINE_CONTAINER - * BOX_INLINE "Example paragraph " - * BOX_INLINE "with emphasised text" (em) - * BOX_INLINE "etc." \endcode - * - * Note that the em has been collapsed into the INLINE_CONTAINER. - * - * If these CSS rules were applied: - * \code - * h1 { display: table-cell } - * p { display: table-cell } - * em { float: left; width: 5em } \endcode - * - * then the box tree would instead look like this: - * \code - * BOX_TABLE - * BOX_TABLE_ROW_GROUP - * BOX_TABLE_ROW - * BOX_TABLE_CELL (h1) - * BOX_INLINE_CONTAINER - * BOX_INLINE "Example Heading" - * BOX_TABLE_CELL (p) - * BOX_INLINE_CONTAINER - * BOX_INLINE "Example paragraph " - * BOX_FLOAT_LEFT (em) - * BOX_BLOCK - * BOX_INLINE_CONTAINER - * BOX_INLINE "with emphasised text" - * BOX_INLINE "etc." \endcode - * - * Here implied boxes have been added and a float is present. - * - * A box tree is "normalized" if the following is satisfied: - * \code - * parent permitted child nodes - * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE - * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT, - * INLINE_END - * INLINE none - * TABLE at least 1 TABLE_ROW_GROUP - * TABLE_ROW_GROUP at least 1 TABLE_ROW - * TABLE_ROW at least 1 TABLE_CELL - * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK) - * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE - * \endcode - */ - -#ifndef _NETSURF_RENDER_BOX_H_ -#define _NETSURF_RENDER_BOX_H_ - -#include -#include -#include -#include - -#include "content/handlers/css/utils.h" - -struct content; -struct box; -struct browser_window; -struct column; -struct object_params; -struct object_param; -struct html_content; -struct nsurl; -struct dom_node; -struct dom_string; -struct rect; - -#define UNKNOWN_WIDTH INT_MAX -#define UNKNOWN_MAX_WIDTH INT_MAX - -typedef void (*box_construct_complete_cb)(struct html_content *c, bool success); - -/** Type of a struct box. */ -typedef enum { - BOX_BLOCK, BOX_INLINE_CONTAINER, BOX_INLINE, - BOX_TABLE, BOX_TABLE_ROW, BOX_TABLE_CELL, - BOX_TABLE_ROW_GROUP, - BOX_FLOAT_LEFT, BOX_FLOAT_RIGHT, - BOX_INLINE_BLOCK, BOX_BR, BOX_TEXT, - BOX_INLINE_END, BOX_NONE -} box_type; - - -/** Flags for a struct box. */ -typedef enum { - NEW_LINE = 1 << 0, /* first inline on a new line */ - STYLE_OWNED = 1 << 1, /* style is owned by this box */ - PRINTED = 1 << 2, /* box has already been printed */ - PRE_STRIP = 1 << 3, /* PRE tag needing leading newline stripped */ - CLONE = 1 << 4, /* continuation of previous box from wrapping */ - MEASURED = 1 << 5, /* text box width has been measured */ - HAS_HEIGHT = 1 << 6, /* box has height (perhaps due to children) */ - MAKE_HEIGHT = 1 << 7, /* box causes its own height */ - NEED_MIN = 1 << 8, /* minimum width is required for layout */ - REPLACE_DIM = 1 << 9, /* replaced element has given dimensions */ - IFRAME = 1 << 10, /* box contains an iframe */ - CONVERT_CHILDREN = 1 << 11, /* wanted children converting */ - IS_REPLACED = 1 << 12 /* box is a replaced element */ -} box_flags; - -/* Sides of a box */ -enum box_side { TOP, RIGHT, BOTTOM, LEFT }; - -/** - * Container for box border details - */ -struct box_border { - enum css_border_style_e style; /**< border-style */ - css_color c; /**< border-color value */ - int width; /**< border-width (pixels) */ -}; - -/** Node in box tree. All dimensions are in pixels. */ -struct box { - /** Type of box. */ - box_type type; - - /** Box flags */ - box_flags flags; - - /** Computed styles for elements and their pseudo elements. NULL on - * non-element boxes. */ - css_select_results *styles; - - /** Style for this box. 0 for INLINE_CONTAINER and FLOAT_*. Pointer into - * a box's 'styles' select results, except for implied boxes, where it - * is a pointer to an owned computed style. */ - css_computed_style *style; - - /** Coordinate of left padding edge relative to parent box, or relative - * to ancestor that contains this box in float_children for FLOAT_. */ - int x; - /** Coordinate of top padding edge, relative as for x. */ - int y; - - int width; /**< Width of content box (excluding padding etc.). */ - int height; /**< Height of content box (excluding padding etc.). */ - - /* These four variables determine the maximum extent of a box's - * descendants. They are relative to the x,y coordinates of the box. - * - * Their use depends on the overflow CSS property: - * - * Overflow: Usage: - * visible The content of the box is displayed within these - * dimensions. - * hidden These are ignored. Content is plotted within the box - * dimensions. - * scroll These are used to determine the extent of the - * scrollable area. - * auto As "scroll". - */ - int descendant_x0; /**< left edge of descendants */ - int descendant_y0; /**< top edge of descendants */ - int descendant_x1; /**< right edge of descendants */ - int descendant_y1; /**< bottom edge of descendants */ - - int margin[4]; /**< Margin: TOP, RIGHT, BOTTOM, LEFT. */ - int padding[4]; /**< Padding: TOP, RIGHT, BOTTOM, LEFT. */ - struct box_border border[4]; /**< Border: TOP, RIGHT, BOTTOM, LEFT. */ - - struct scrollbar *scroll_x; /**< Horizontal scroll. */ - struct scrollbar *scroll_y; /**< Vertical scroll. */ - - /** Width of box taking all line breaks (including margins etc). Must - * be non-negative. */ - int min_width; - /** Width that would be taken with no line breaks. Must be - * non-negative. */ - int max_width; - - /**< Byte offset within a textual representation of this content. */ - size_t byte_offset; - - char *text; /**< Text, or 0 if none. Unterminated. */ - size_t length; /**< Length of text. */ - - /** Width of space after current text (depends on font and size). */ - int space; - - struct nsurl *href; /**< Link, or 0. */ - const char *target; /**< Link target, or 0. */ - const char *title; /**< Title, or 0. */ - - unsigned int columns; /**< Number of columns for TABLE / TABLE_CELL. */ - unsigned int rows; /**< Number of rows for TABLE only. */ - unsigned int start_column; /**< Start column for TABLE_CELL only. */ - - struct box *next; /**< Next sibling box, or 0. */ - struct box *prev; /**< Previous sibling box, or 0. */ - struct box *children; /**< First child box, or 0. */ - struct box *last; /**< Last child box, or 0. */ - struct box *parent; /**< Parent box, or 0. */ - /** INLINE_END box corresponding to this INLINE box, or INLINE box - * corresponding to this INLINE_END box. */ - struct box *inline_end; - - /** First float child box, or 0. Float boxes are in the tree twice, in - * this list for the block box which defines the area for floats, and - * also in the standard tree given by children, next, prev, etc. */ - struct box *float_children; - /** Next sibling float box. */ - struct box *next_float; - /** If box is a float, points to box's containing block */ - struct box *float_container; - /** Level below which subsequent floats must be cleared. - * This is used only for boxes with float_children */ - int clear_level; - - /* Level below which floats have been placed. */ - int cached_place_below_level; - - /** List marker box if this is a list-item, or 0. */ - struct box *list_marker; - - struct column *col; /**< Array of table column data for TABLE only. */ - - /** Form control data, or 0 if not a form control. */ - struct form_control* gadget; - - char *usemap; /** (Image)map to use with this object, or 0 if none */ - lwc_string *id; /**< value of id attribute (or name for anchors) */ - - /** Background image for this box, or 0 if none */ - struct hlcache_handle *background; - - /** Object in this box (usually an image), or 0 if none. */ - struct hlcache_handle* object; - /** Parameters for the object, or 0. */ - struct object_params *object_params; - - /** Iframe's browser_window, or NULL if none */ - struct browser_window *iframe; - - struct dom_node *node; /**< DOM node that generated this box or NULL */ -}; - -/** Table column data. */ -struct column { - /** Type of column. */ - enum { COLUMN_WIDTH_UNKNOWN, COLUMN_WIDTH_FIXED, - COLUMN_WIDTH_AUTO, COLUMN_WIDTH_PERCENT, - COLUMN_WIDTH_RELATIVE } type; - /** Preferred width of column. Pixels for FIXED, percentage for PERCENT, - * relative units for RELATIVE, unused for AUTO. */ - int width; - /** Minimum width of content. */ - int min; - /** Maximum width of content. */ - int max; - /** Whether all of column's cells are css positioned. */ - bool positioned; -}; - -/** Parameters for object element and similar elements. */ -struct object_params { - struct nsurl *data; - char *type; - char *codetype; - struct nsurl *codebase; - struct nsurl *classid; - struct object_param *params; -}; - -/** Linked list of object element parameters. */ -struct object_param { - char *name; - char *value; - char *type; - char *valuetype; - struct object_param *next; -}; - -/** Frame target names (constant pointers to save duplicating the strings many - * times). We convert _blank to _top for user-friendliness. */ -extern const char *TARGET_SELF; -extern const char *TARGET_PARENT; -extern const char *TARGET_TOP; -extern const char *TARGET_BLANK; - - - -struct box * box_create(css_select_results *styles, css_computed_style *style, - bool style_owned, struct nsurl *href, const char *target, - const char *title, lwc_string *id, void *context); -void box_add_child(struct box *parent, struct box *child); -void box_insert_sibling(struct box *box, struct box *new_box); -void box_unlink_and_free(struct box *box); -void box_free(struct box *box); -void box_free_box(struct box *box); -void box_bounds(struct box *box, struct rect *r); -void box_coords(struct box *box, int *x, int *y); -struct box *box_at_point( - const nscss_len_ctx *len_ctx, - struct box *box, const int x, const int y, - int *box_x, int *box_y); -struct box *box_pick_text_box(struct html_content *html, - int x, int y, int dir, int *dx, int *dy); -struct box *box_find_by_id(struct box *box, lwc_string *id); -bool box_visible(struct box *box); -void box_dump(FILE *stream, struct box *box, unsigned int depth, bool style); - -/** - * Extract a URL from a relative link, handling junk like whitespace and - * attempting to read a real URL from "javascript:" links. - * - * \param content html content - * \param dsrel relative URL text taken from page - * \param base base for relative URLs - * \param result updated to target URL on heap, unchanged if extract failed - * \return true on success, false on memory exhaustion - */ -bool box_extract_link(const struct html_content *content, const struct dom_string *dsrel, struct nsurl *base, struct nsurl **result); - -bool box_handle_scrollbars(struct content *c, struct box *box, - bool bottom, bool right); -bool box_vscrollbar_present(const struct box *box); -bool box_hscrollbar_present(const struct box *box); - -nserror dom_to_box(struct dom_node *n, struct html_content *c, - box_construct_complete_cb cb); - -bool box_normalise_block( - struct box *block, - const struct box *root, - struct html_content *c); - -#endif diff --git a/render/box_construct.c b/render/box_construct.c deleted file mode 100644 index 1aa99e2d1..000000000 --- a/render/box_construct.c +++ /dev/null @@ -1,3137 +0,0 @@ -/* - * Copyright 2005 James Bursa - * Copyright 2003 Phil Mellor - * Copyright 2005 John M Bell - * Copyright 2006 Richard Wilson - * Copyright 2008 Michael Drake - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * Implementation of conversion from DOM tree to box tree. - */ - -#include -#include -#include -#include -#include -#include - -#include "utils/config.h" -#include "utils/nsoption.h" -#include "utils/corestrings.h" -#include "utils/log.h" -#include "utils/messages.h" -#include "utils/talloc.h" -#include "utils/string.h" -#include "utils/ascii.h" -#include "netsurf/css.h" -#include "netsurf/misc.h" -#include "netsurf/plot_style.h" -#include "content/content_protected.h" -#include "css/hints.h" -#include "css/select.h" -#include "css/utils.h" -#include "desktop/gui_internal.h" - -#include "render/box.h" -#include "render/box_textarea.h" -#include "render/form_internal.h" -#include "render/html_internal.h" - -/** - * Context for box tree construction - */ -struct box_construct_ctx { - html_content *content; /**< Content we're constructing for */ - - dom_node *n; /**< Current node to process */ - - struct box *root_box; /**< Root box in the tree */ - - box_construct_complete_cb cb; /**< Callback to invoke on completion */ - - int *bctx; /**< talloc context */ -}; - -/** - * Transient properties for construction of current node - */ -struct box_construct_props { - /** Style from which to inherit, or NULL if none */ - const css_computed_style *parent_style; - /** Current link target, or NULL if none */ - nsurl *href; - /** Current frame target, or NULL if none */ - const char *target; - /** Current title attribute, or NULL if none */ - const char *title; - /** Identity of the current block-level container */ - struct box *containing_block; - /** Current container for inlines, or NULL if none - * \note If non-NULL, will be the last child of containing_block */ - struct box *inline_container; - /** Whether the current node is the root of the DOM tree */ - bool node_is_root; -}; - -static const content_type image_types = CONTENT_IMAGE; - -/* the strings are not important, since we just compare the pointers */ -const char *TARGET_SELF = "_self"; -const char *TARGET_PARENT = "_parent"; -const char *TARGET_TOP = "_top"; -const char *TARGET_BLANK = "_blank"; - -static void convert_xml_to_box(struct box_construct_ctx *ctx); -static bool box_construct_element(struct box_construct_ctx *ctx, - bool *convert_children); -static void box_construct_element_after(dom_node *n, html_content *content); -static bool box_construct_text(struct box_construct_ctx *ctx); -static css_select_results * box_get_style(html_content *c, - const css_computed_style *parent_style, - const css_computed_style *root_style, dom_node *n); -static void box_text_transform(char *s, unsigned int len, - enum css_text_transform_e tt); -#define BOX_SPECIAL_PARAMS dom_node *n, html_content *content, \ - struct box *box, bool *convert_children -static bool box_a(BOX_SPECIAL_PARAMS); -static bool box_body(BOX_SPECIAL_PARAMS); -static bool box_br(BOX_SPECIAL_PARAMS); -static bool box_image(BOX_SPECIAL_PARAMS); -static bool box_textarea(BOX_SPECIAL_PARAMS); -static bool box_select(BOX_SPECIAL_PARAMS); -static bool box_input(BOX_SPECIAL_PARAMS); -static bool box_button(BOX_SPECIAL_PARAMS); -static bool box_frameset(BOX_SPECIAL_PARAMS); -static bool box_create_frameset(struct content_html_frames *f, dom_node *n, - html_content *content); -static bool box_select_add_option(struct form_control *control, dom_node *n); -static bool box_noscript(BOX_SPECIAL_PARAMS); -static bool box_object(BOX_SPECIAL_PARAMS); -static bool box_embed(BOX_SPECIAL_PARAMS); -static bool box_pre(BOX_SPECIAL_PARAMS); -static bool box_iframe(BOX_SPECIAL_PARAMS); -static bool box_get_attribute(dom_node *n, const char *attribute, - void *context, char **value); - -/* element_table must be sorted by name */ -struct element_entry { - char name[10]; /* element type */ - bool (*convert)(BOX_SPECIAL_PARAMS); -}; -static const struct element_entry element_table[] = { - {"a", box_a}, - {"body", box_body}, - {"br", box_br}, - {"button", box_button}, - {"embed", box_embed}, - {"frameset", box_frameset}, - {"iframe", box_iframe}, - {"image", box_image}, - {"img", box_image}, - {"input", box_input}, - {"noscript", box_noscript}, - {"object", box_object}, - {"pre", box_pre}, - {"select", box_select}, - {"textarea", box_textarea} -}; -#define ELEMENT_TABLE_COUNT (sizeof(element_table) / sizeof(element_table[0])) - -/** - * Construct a box tree from an xml tree and stylesheets. - * - * \param n xml tree - * \param c content of type CONTENT_HTML to construct box tree in - * \param cb callback to report conversion completion - * \return netsurf error code indicating status of call - */ - -nserror dom_to_box(dom_node *n, html_content *c, box_construct_complete_cb cb) -{ - struct box_construct_ctx *ctx; - - if (c->bctx == NULL) { - /* create a context allocation for this box tree */ - c->bctx = talloc_zero(0, int); - if (c->bctx == NULL) { - return NSERROR_NOMEM; - } - } - - ctx = malloc(sizeof(*ctx)); - if (ctx == NULL) { - return NSERROR_NOMEM; - } - - ctx->content = c; - ctx->n = dom_node_ref(n); - ctx->root_box = NULL; - ctx->cb = cb; - ctx->bctx = c->bctx; - - return guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); -} - -/* mapping from CSS display to box type - * this table must be in sync with libcss' css_display enum */ -static const box_type box_map[] = { - 0, /*CSS_DISPLAY_INHERIT,*/ - BOX_INLINE, /*CSS_DISPLAY_INLINE,*/ - BOX_BLOCK, /*CSS_DISPLAY_BLOCK,*/ - BOX_BLOCK, /*CSS_DISPLAY_LIST_ITEM,*/ - BOX_INLINE, /*CSS_DISPLAY_RUN_IN,*/ - BOX_INLINE_BLOCK, /*CSS_DISPLAY_INLINE_BLOCK,*/ - BOX_TABLE, /*CSS_DISPLAY_TABLE,*/ - BOX_TABLE, /*CSS_DISPLAY_INLINE_TABLE,*/ - BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_ROW_GROUP,*/ - BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_HEADER_GROUP,*/ - BOX_TABLE_ROW_GROUP, /*CSS_DISPLAY_TABLE_FOOTER_GROUP,*/ - BOX_TABLE_ROW, /*CSS_DISPLAY_TABLE_ROW,*/ - BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN_GROUP,*/ - BOX_NONE, /*CSS_DISPLAY_TABLE_COLUMN,*/ - BOX_TABLE_CELL, /*CSS_DISPLAY_TABLE_CELL,*/ - BOX_INLINE, /*CSS_DISPLAY_TABLE_CAPTION,*/ - BOX_NONE /*CSS_DISPLAY_NONE*/ -}; - -static inline struct box *box_for_node(dom_node *n) -{ - struct box *box = NULL; - dom_exception err; - - err = dom_node_get_user_data(n, corestring_dom___ns_key_box_node_data, - (void *) &box); - if (err != DOM_NO_ERR) - return NULL; - - return box; -} - -static inline bool box_is_root(dom_node *n) -{ - dom_node *parent; - dom_node_type type; - dom_exception err; - - err = dom_node_get_parent_node(n, &parent); - if (err != DOM_NO_ERR) - return false; - - if (parent != NULL) { - err = dom_node_get_node_type(parent, &type); - - dom_node_unref(parent); - - if (err != DOM_NO_ERR) - return false; - - if (type != DOM_DOCUMENT_NODE) - return false; - } - - return true; -} - -/** - * Find the next node in the DOM tree, completing - * element construction where appropriate. - * - * \param n Current node - * \param content Containing content - * \param convert_children Whether to consider children of \a n - * \return Next node to process, or NULL if complete - * - * \note \a n will be unreferenced - */ -static dom_node *next_node(dom_node *n, html_content *content, - bool convert_children) -{ - dom_node *next = NULL; - bool has_children; - dom_exception err; - - err = dom_node_has_child_nodes(n, &has_children); - if (err != DOM_NO_ERR) { - dom_node_unref(n); - return NULL; - } - - if (convert_children && has_children) { - err = dom_node_get_first_child(n, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(n); - return NULL; - } - dom_node_unref(n); - } else { - err = dom_node_get_next_sibling(n, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(n); - return NULL; - } - - if (next != NULL) { - if (box_for_node(n) != NULL) - box_construct_element_after(n, content); - dom_node_unref(n); - } else { - if (box_for_node(n) != NULL) - box_construct_element_after(n, content); - - while (box_is_root(n) == false) { - dom_node *parent = NULL; - dom_node *parent_next = NULL; - - err = dom_node_get_parent_node(n, &parent); - if (err != DOM_NO_ERR) { - dom_node_unref(n); - return NULL; - } - - assert(parent != NULL); - - err = dom_node_get_next_sibling(parent, - &parent_next); - if (err != DOM_NO_ERR) { - dom_node_unref(parent); - dom_node_unref(n); - return NULL; - } - - if (parent_next != NULL) { - dom_node_unref(parent_next); - dom_node_unref(parent); - break; - } - - dom_node_unref(n); - n = parent; - parent = NULL; - - if (box_for_node(n) != NULL) { - box_construct_element_after( - n, content); - } - } - - if (box_is_root(n) == false) { - dom_node *parent = NULL; - - err = dom_node_get_parent_node(n, &parent); - if (err != DOM_NO_ERR) { - dom_node_unref(n); - return NULL; - } - - assert(parent != NULL); - - err = dom_node_get_next_sibling(parent, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(parent); - dom_node_unref(n); - return NULL; - } - - if (box_for_node(parent) != NULL) { - box_construct_element_after(parent, - content); - } - - dom_node_unref(parent); - } - - dom_node_unref(n); - } - } - - return next; -} - -/** - * Convert an ELEMENT node to a box tree fragment, - * then schedule conversion of the next ELEMENT node - */ -void convert_xml_to_box(struct box_construct_ctx *ctx) -{ - dom_node *next; - bool convert_children; - uint32_t num_processed = 0; - const uint32_t max_processed_before_yield = 10; - - do { - convert_children = true; - - assert(ctx->n != NULL); - - if (box_construct_element(ctx, &convert_children) == false) { - ctx->cb(ctx->content, false); - dom_node_unref(ctx->n); - free(ctx); - return; - } - - /* Find next element to process, converting text nodes as we go */ - next = next_node(ctx->n, ctx->content, convert_children); - while (next != NULL) { - dom_node_type type; - dom_exception err; - - err = dom_node_get_node_type(next, &type); - if (err != DOM_NO_ERR) { - ctx->cb(ctx->content, false); - dom_node_unref(next); - free(ctx); - return; - } - - if (type == DOM_ELEMENT_NODE) - break; - - if (type == DOM_TEXT_NODE) { - ctx->n = next; - if (box_construct_text(ctx) == false) { - ctx->cb(ctx->content, false); - dom_node_unref(ctx->n); - free(ctx); - return; - } - } - - next = next_node(next, ctx->content, true); - } - - ctx->n = next; - - if (next == NULL) { - /* Conversion complete */ - struct box root; - - memset(&root, 0, sizeof(root)); - - root.type = BOX_BLOCK; - root.children = root.last = ctx->root_box; - root.children->parent = &root; - - /** \todo Remove box_normalise_block */ - if (box_normalise_block(&root, ctx->root_box, - ctx->content) == false) { - ctx->cb(ctx->content, false); - } else { - ctx->content->layout = root.children; - ctx->content->layout->parent = NULL; - - ctx->cb(ctx->content, true); - } - - assert(ctx->n == NULL); - - free(ctx); - return; - } - } while (++num_processed < max_processed_before_yield); - - /* More work to do: schedule a continuation */ - guit->misc->schedule(0, (void *)convert_xml_to_box, ctx); -} - -/** - * Construct a list marker box - * - * \param box Box to attach marker to - * \param title Current title attribute - * \param ctx Box construction context - * \param parent Current block-level container - * \return true on success, false on memory exhaustion - */ -static bool box_construct_marker(struct box *box, const char *title, - struct box_construct_ctx *ctx, struct box *parent) -{ - lwc_string *image_uri; - struct box *marker; - - marker = box_create(NULL, box->style, false, NULL, NULL, title, - NULL, ctx->bctx); - if (marker == false) - return false; - - marker->type = BOX_BLOCK; - - /** \todo marker content (list-style-type) */ - switch (css_computed_list_style_type(box->style)) { - case CSS_LIST_STYLE_TYPE_DISC: - /* 2022 BULLET */ - marker->text = (char *) "\342\200\242"; - marker->length = 3; - break; - case CSS_LIST_STYLE_TYPE_CIRCLE: - /* 25CB WHITE CIRCLE */ - marker->text = (char *) "\342\227\213"; - marker->length = 3; - break; - case CSS_LIST_STYLE_TYPE_SQUARE: - /* 25AA BLACK SMALL SQUARE */ - marker->text = (char *) "\342\226\252"; - marker->length = 3; - break; - case CSS_LIST_STYLE_TYPE_DECIMAL: - case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: - case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: - case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: - case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: - default: - if (parent->last) { - struct box *last = parent->last; - - /* Drill down into last child of parent - * to find the list marker (if any) - * - * Floated list boxes end up as: - * - * parent - * BOX_INLINE_CONTAINER - * BOX_FLOAT_{LEFT,RIGHT} - * BOX_BLOCK <-- list box - * ... - */ - while (last != NULL && last->list_marker == NULL) { - struct box *last_inner = last; - - while (last_inner != NULL) { - if (last_inner->list_marker != NULL) - break; - if (last_inner->type == - BOX_INLINE_CONTAINER || - last_inner->type == - BOX_FLOAT_LEFT || - last_inner->type == - BOX_FLOAT_RIGHT) { - last_inner = last_inner->last; - } else { - last_inner = NULL; - } - } - if (last_inner != NULL) { - last = last_inner; - } else { - last = last->prev; - } - } - - if (last && last->list_marker) { - marker->rows = last->list_marker->rows + 1; - } - } - - marker->text = talloc_array(ctx->bctx, char, 20); - if (marker->text == NULL) - return false; - - snprintf(marker->text, 20, "%u.", marker->rows); - marker->length = strlen(marker->text); - break; - case CSS_LIST_STYLE_TYPE_NONE: - marker->text = 0; - marker->length = 0; - break; - } - - if (css_computed_list_style_image(box->style, &image_uri) == CSS_LIST_STYLE_IMAGE_URI && - (image_uri != NULL) && - (nsoption_bool(foreground_images) == true)) { - nsurl *url; - nserror error; - - /* TODO: we get a url out of libcss as a lwc string, but - * earlier we already had it as a nsurl after we - * nsurl_joined it. Can this be improved? - * For now, just making another nsurl. */ - error = nsurl_create(lwc_string_data(image_uri), &url); - if (error != NSERROR_OK) - return false; - - if (html_fetch_object(ctx->content, url, marker, image_types, - ctx->content->base.available_width, 1000, false) == - false) { - nsurl_unref(url); - return false; - } - nsurl_unref(url); - } - - box->list_marker = marker; - marker->parent = box; - - return true; -} - -/** - * Construct the box required for a generated element. - * - * \param n XML node of type XML_ELEMENT_NODE - * \param content Content of type CONTENT_HTML that is being processed - * \param box Box which may have generated content - * \param style Complete computed style for pseudo element, or NULL - * - * TODO: - * This is currently incomplete. It just does enough to support the clearfix - * hack. ( http://www.positioniseverything.net/easyclearing.html ) - */ -static void box_construct_generate(dom_node *n, html_content *content, - struct box *box, const css_computed_style *style) -{ - struct box *gen = NULL; - enum css_display_e computed_display; - const css_computed_content_item *c_item; - - /* Nothing to generate if the parent box is not a block */ - if (box->type != BOX_BLOCK) - return; - - /* To determine if an element has a pseudo element, we select - * for it and test to see if the returned style's content - * property is set to normal. */ - if (style == NULL || - css_computed_content(style, &c_item) == - CSS_CONTENT_NORMAL) { - /* No pseudo element */ - return; - } - - /* create box for this element */ - computed_display = ns_computed_display(style, box_is_root(n)); - if (computed_display == CSS_DISPLAY_BLOCK || - computed_display == CSS_DISPLAY_TABLE) { - /* currently only support block level boxes */ - - /** \todo Not wise to drop const from the computed style */ - gen = box_create(NULL, (css_computed_style *) style, - false, NULL, NULL, NULL, NULL, content->bctx); - if (gen == NULL) { - return; - } - - /* set box type from computed display */ - gen->type = box_map[ns_computed_display( - style, box_is_root(n))]; - - box_add_child(box, gen); - } -} - -/** - * Extract transient construction properties - * - * \param n Current DOM node to convert - * \param props Property object to populate - */ -static void box_extract_properties(dom_node *n, - struct box_construct_props *props) -{ - memset(props, 0, sizeof(*props)); - - props->node_is_root = box_is_root(n); - - /* Extract properties from containing DOM node */ - if (props->node_is_root == false) { - dom_node *current_node = n; - dom_node *parent_node = NULL; - struct box *parent_box; - dom_exception err; - - /* Find ancestor node containing parent box */ - while (true) { - err = dom_node_get_parent_node(current_node, - &parent_node); - if (err != DOM_NO_ERR || parent_node == NULL) - break; - - parent_box = box_for_node(parent_node); - - if (parent_box != NULL) { - props->parent_style = parent_box->style; - props->href = parent_box->href; - props->target = parent_box->target; - props->title = parent_box->title; - - dom_node_unref(parent_node); - break; - } else { - if (current_node != n) - dom_node_unref(current_node); - current_node = parent_node; - parent_node = NULL; - } - } - - /* Find containing block (may be parent) */ - while (true) { - struct box *b; - - err = dom_node_get_parent_node(current_node, - &parent_node); - if (err != DOM_NO_ERR || parent_node == NULL) { - if (current_node != n) - dom_node_unref(current_node); - break; - } - - if (current_node != n) - dom_node_unref(current_node); - - b = box_for_node(parent_node); - - /* Children of nodes that created an inline box - * will generate boxes which are attached as - * _siblings_ of the box generated for their - * parent node. Note, however, that we'll still - * use the parent node's styling as the parent - * style, above. */ - if (b != NULL && b->type != BOX_INLINE && - b->type != BOX_BR) { - props->containing_block = b; - - dom_node_unref(parent_node); - break; - } else { - current_node = parent_node; - parent_node = NULL; - } - } - } - - /* Compute current inline container, if any */ - if (props->containing_block != NULL && - props->containing_block->last != NULL && - props->containing_block->last->type == - BOX_INLINE_CONTAINER) - props->inline_container = props->containing_block->last; -} - -/** - * Construct the box tree for an XML element. - * - * \param ctx Tree construction context - * \param convert_children Whether to convert children - * \return true on success, false on memory exhaustion - */ - -bool box_construct_element(struct box_construct_ctx *ctx, - bool *convert_children) -{ - dom_string *title0, *s; - lwc_string *id = NULL; - struct box *box = NULL, *old_box; - css_select_results *styles = NULL; - struct element_entry *element; - lwc_string *bgimage_uri; - dom_exception err; - struct box_construct_props props; - const css_computed_style *root_style = NULL; - - assert(ctx->n != NULL); - - box_extract_properties(ctx->n, &props); - - if (props.containing_block != NULL) { - /* In case the containing block is a pre block, we clear - * the PRE_STRIP flag since it is not used if we follow - * the pre with a tag */ - props.containing_block->flags &= ~PRE_STRIP; - } - - if (props.node_is_root == false) { - root_style = ctx->root_box->style; - } - - styles = box_get_style(ctx->content, props.parent_style, root_style, - ctx->n); - if (styles == NULL) - return false; - - /* Extract title attribute, if present */ - err = dom_element_get_attribute(ctx->n, corestring_dom_title, &title0); - if (err != DOM_NO_ERR) - return false; - - if (title0 != NULL) { - char *t = squash_whitespace(dom_string_data(title0)); - - dom_string_unref(title0); - - if (t == NULL) - return false; - - props.title = talloc_strdup(ctx->bctx, t); - - free(t); - - if (props.title == NULL) - return false; - } - - /* Extract id attribute, if present */ - err = dom_element_get_attribute(ctx->n, corestring_dom_id, &s); - if (err != DOM_NO_ERR) - return false; - - if (s != NULL) { - err = dom_string_intern(s, &id); - if (err != DOM_NO_ERR) - id = NULL; - - dom_string_unref(s); - } - - box = box_create(styles, styles->styles[CSS_PSEUDO_ELEMENT_NONE], false, - props.href, props.target, props.title, id, - ctx->bctx); - if (box == NULL) - return false; - - /* If this is the root box, add it to the context */ - if (props.node_is_root) - ctx->root_box = box; - - /* Deal with colspan/rowspan */ - err = dom_element_get_attribute(ctx->n, corestring_dom_colspan, &s); - if (err != DOM_NO_ERR) - return false; - - if (s != NULL) { - const char *val = dom_string_data(s); - - if ('0' <= val[0] && val[0] <= '9') - box->columns = strtol(val, NULL, 10); - - dom_string_unref(s); - } - - err = dom_element_get_attribute(ctx->n, corestring_dom_rowspan, &s); - if (err != DOM_NO_ERR) - return false; - - if (s != NULL) { - const char *val = dom_string_data(s); - - if ('0' <= val[0] && val[0] <= '9') - box->rows = strtol(val, NULL, 10); - - dom_string_unref(s); - } - - /* Set box type from computed display */ - if ((css_computed_position(box->style) == CSS_POSITION_ABSOLUTE || - css_computed_position(box->style) == - CSS_POSITION_FIXED) && - (ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE || - ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE_BLOCK || - ns_computed_display_static(box->style) == - CSS_DISPLAY_INLINE_TABLE)) { - /* Special case for absolute positioning: make absolute inlines - * into inline block so that the boxes are constructed in an - * inline container as if they were not absolutely positioned. - * Layout expects and handles this. */ - box->type = box_map[CSS_DISPLAY_INLINE_BLOCK]; - } else if (props.node_is_root) { - /* Special case for root element: force it to BLOCK, or the - * rest of the layout will break. */ - box->type = BOX_BLOCK; - } else { - /* Normal mapping */ - box->type = box_map[ns_computed_display(box->style, - props.node_is_root)]; - } - - err = dom_node_get_node_name(ctx->n, &s); - if (err != DOM_NO_ERR || s == NULL) - return false; - - /* Special elements */ - element = bsearch(dom_string_data(s), element_table, - ELEMENT_TABLE_COUNT, sizeof(element_table[0]), - (int (*)(const void *, const void *)) strcasecmp); - - dom_string_unref(s); - - if (element != NULL) { - /* A special convert function exists for this element */ - if (element->convert(ctx->n, ctx->content, box, - convert_children) == false) - return false; - } - - /* Handle the :before pseudo element */ - if (!(box->flags & IS_REPLACED)) { - box_construct_generate(ctx->n, ctx->content, box, - box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]); - } - - if (box->type == BOX_NONE || (ns_computed_display(box->style, - props.node_is_root) == CSS_DISPLAY_NONE && - props.node_is_root == false)) { - css_select_results_destroy(styles); - box->styles = NULL; - box->style = NULL; - - /* Invalidate associated gadget, if any */ - if (box->gadget != NULL) { - box->gadget->box = NULL; - box->gadget = NULL; - } - - /* Can't do this, because the lifetimes of boxes and gadgets - * are inextricably linked. Fortunately, talloc will save us - * (for now) */ - /* box_free_box(box); */ - - *convert_children = false; - - return true; - } - - /* Attach DOM node to box */ - err = dom_node_set_user_data(ctx->n, - corestring_dom___ns_key_box_node_data, box, NULL, - (void *) &old_box); - if (err != DOM_NO_ERR) - return false; - - /* Attach box to DOM node */ - box->node = dom_node_ref(ctx->n); - - if (props.inline_container == NULL && - (box->type == BOX_INLINE || - box->type == BOX_BR || - box->type == BOX_INLINE_BLOCK || - css_computed_float(box->style) == CSS_FLOAT_LEFT || - css_computed_float(box->style) == CSS_FLOAT_RIGHT) && - props.node_is_root == false) { - /* Found an inline child of a block without a current container - * (i.e. this box is the first child of its parent, or was - * preceded by block-level siblings) */ - assert(props.containing_block != NULL && - "Box must have containing block."); - - props.inline_container = box_create(NULL, NULL, false, NULL, - NULL, NULL, NULL, ctx->bctx); - if (props.inline_container == NULL) - return false; - - props.inline_container->type = BOX_INLINE_CONTAINER; - - box_add_child(props.containing_block, props.inline_container); - } - - /* Kick off fetch for any background image */ - if (css_computed_background_image(box->style, &bgimage_uri) == - CSS_BACKGROUND_IMAGE_IMAGE && bgimage_uri != NULL && - nsoption_bool(background_images) == true) { - nsurl *url; - nserror error; - - /* TODO: we get a url out of libcss as a lwc string, but - * earlier we already had it as a nsurl after we - * nsurl_joined it. Can this be improved? - * For now, just making another nsurl. */ - error = nsurl_create(lwc_string_data(bgimage_uri), &url); - if (error == NSERROR_OK) { - /* Fetch image if we got a valid URL */ - if (html_fetch_object(ctx->content, url, box, - image_types, - ctx->content->base.available_width, - 1000, true) == false) { - nsurl_unref(url); - return false; - } - nsurl_unref(url); - } - } - - if (*convert_children) - box->flags |= CONVERT_CHILDREN; - - if (box->type == BOX_INLINE || box->type == BOX_BR || - box->type == BOX_INLINE_BLOCK) { - /* Inline container must exist, as we'll have - * created it above if it didn't */ - assert(props.inline_container != NULL); - - box_add_child(props.inline_container, box); - } else { - if (ns_computed_display(box->style, props.node_is_root) == - CSS_DISPLAY_LIST_ITEM) { - /* List item: compute marker */ - if (box_construct_marker(box, props.title, ctx, - props.containing_block) == false) - return false; - } - - if (props.node_is_root == false && - (css_computed_float(box->style) == - CSS_FLOAT_LEFT || - css_computed_float(box->style) == - CSS_FLOAT_RIGHT)) { - /* Float: insert a float between the parent and box. */ - struct box *flt = box_create(NULL, NULL, false, - props.href, props.target, props.title, - NULL, ctx->bctx); - if (flt == NULL) - return false; - - if (css_computed_float(box->style) == CSS_FLOAT_LEFT) - flt->type = BOX_FLOAT_LEFT; - else - flt->type = BOX_FLOAT_RIGHT; - - box_add_child(props.inline_container, flt); - box_add_child(flt, box); - } else { - /* Non-floated block-level box: add to containing block - * if there is one. If we're the root box, then there - * won't be. */ - if (props.containing_block != NULL) - box_add_child(props.containing_block, box); - } - } - - return true; -} - -/** - * Complete construction of the box tree for an element. - * - * \param n DOM node to construct for - * \param content Containing document - * - * This will be called after all children of an element have been processed - */ -void box_construct_element_after(dom_node *n, html_content *content) -{ - struct box_construct_props props; - struct box *box = box_for_node(n); - - assert(box != NULL); - - box_extract_properties(n, &props); - - if (box->type == BOX_INLINE || box->type == BOX_BR) { - /* Insert INLINE_END into containing block */ - struct box *inline_end; - bool has_children; - dom_exception err; - - err = dom_node_has_child_nodes(n, &has_children); - if (err != DOM_NO_ERR) - return; - - if (has_children == false || - (box->flags & CONVERT_CHILDREN) == 0) { - /* No children, or didn't want children converted */ - return; - } - - if (props.inline_container == NULL) { - /* Create inline container if we don't have one */ - props.inline_container = box_create(NULL, NULL, false, - NULL, NULL, NULL, NULL, content->bctx); - if (props.inline_container == NULL) - return; - - props.inline_container->type = BOX_INLINE_CONTAINER; - - box_add_child(props.containing_block, - props.inline_container); - } - - inline_end = box_create(NULL, box->style, false, - box->href, box->target, box->title, - box->id == NULL ? NULL : - lwc_string_ref(box->id), content->bctx); - if (inline_end != NULL) { - inline_end->type = BOX_INLINE_END; - - assert(props.inline_container != NULL); - - box_add_child(props.inline_container, inline_end); - - box->inline_end = inline_end; - inline_end->inline_end = box; - } - } else if (!(box->flags & IS_REPLACED)) { - /* Handle the :after pseudo element */ - box_construct_generate(n, content, box, - box->styles->styles[CSS_PSEUDO_ELEMENT_AFTER]); - } -} - -/** - * Construct the box tree for an XML text node. - * - * \param ctx Tree construction context - * \return true on success, false on memory exhaustion - */ - -bool box_construct_text(struct box_construct_ctx *ctx) -{ - struct box_construct_props props; - struct box *box = NULL; - dom_string *content; - dom_exception err; - - assert(ctx->n != NULL); - - box_extract_properties(ctx->n, &props); - - assert(props.containing_block != NULL); - - err = dom_characterdata_get_data(ctx->n, &content); - if (err != DOM_NO_ERR || content == NULL) - return false; - - if (css_computed_white_space(props.parent_style) == - CSS_WHITE_SPACE_NORMAL || - css_computed_white_space(props.parent_style) == - CSS_WHITE_SPACE_NOWRAP) { - char *text; - - text = squash_whitespace(dom_string_data(content)); - - dom_string_unref(content); - - if (text == NULL) - return false; - - /* if the text is just a space, combine it with the preceding - * text node, if any */ - if (text[0] == ' ' && text[1] == 0) { - if (props.inline_container != NULL) { - assert(props.inline_container->last != NULL); - - props.inline_container->last->space = - UNKNOWN_WIDTH; - } - - free(text); - - return true; - } - - if (props.inline_container == NULL) { - /* Child of a block without a current container - * (i.e. this box is the first child of its parent, or - * was preceded by block-level siblings) */ - props.inline_container = box_create(NULL, NULL, false, - NULL, NULL, NULL, NULL, ctx->bctx); - if (props.inline_container == NULL) { - free(text); - return false; - } - - props.inline_container->type = BOX_INLINE_CONTAINER; - - box_add_child(props.containing_block, - props.inline_container); - } - - /** \todo Dropping const here is not clever */ - box = box_create(NULL, - (css_computed_style *) props.parent_style, - false, props.href, props.target, props.title, - NULL, ctx->bctx); - if (box == NULL) { - free(text); - return false; - } - - box->type = BOX_TEXT; - - box->text = talloc_strdup(ctx->bctx, text); - free(text); - if (box->text == NULL) - return false; - - box->length = strlen(box->text); - - /* strip ending space char off */ - if (box->length > 1 && box->text[box->length - 1] == ' ') { - box->space = UNKNOWN_WIDTH; - box->length--; - } - - if (css_computed_text_transform(props.parent_style) != - CSS_TEXT_TRANSFORM_NONE) - box_text_transform(box->text, box->length, - css_computed_text_transform( - props.parent_style)); - - box_add_child(props.inline_container, box); - - if (box->text[0] == ' ') { - box->length--; - - memmove(box->text, &box->text[1], box->length); - - if (box->prev != NULL) - box->prev->space = UNKNOWN_WIDTH; - } - } else { - /* white-space: pre */ - char *text; - size_t text_len = dom_string_byte_length(content); - size_t i; - char *current; - enum css_white_space_e white_space = - css_computed_white_space(props.parent_style); - - /* note: pre-wrap/pre-line are unimplemented */ - assert(white_space == CSS_WHITE_SPACE_PRE || - white_space == CSS_WHITE_SPACE_PRE_LINE || - white_space == CSS_WHITE_SPACE_PRE_WRAP); - - text = malloc(text_len + 1); - dom_string_unref(content); - - if (text == NULL) - return false; - - memcpy(text, dom_string_data(content), text_len); - text[text_len] = '\0'; - - /* TODO: Handle tabs properly */ - for (i = 0; i < text_len; i++) - if (text[i] == '\t') - text[i] = ' '; - - if (css_computed_text_transform(props.parent_style) != - CSS_TEXT_TRANSFORM_NONE) - box_text_transform(text, strlen(text), - css_computed_text_transform( - props.parent_style)); - - current = text; - - /* swallow a single leading new line */ - if (props.containing_block->flags & PRE_STRIP) { - switch (*current) { - case '\n': - current++; - break; - case '\r': - current++; - if (*current == '\n') - current++; - break; - } - props.containing_block->flags &= ~PRE_STRIP; - } - - do { - size_t len = strcspn(current, "\r\n"); - - char old = current[len]; - - current[len] = 0; - - if (props.inline_container == NULL) { - /* Child of a block without a current container - * (i.e. this box is the first child of its - * parent, or was preceded by block-level - * siblings) */ - props.inline_container = box_create(NULL, NULL, - false, NULL, NULL, NULL, NULL, - ctx->bctx); - if (props.inline_container == NULL) { - free(text); - return false; - } - - props.inline_container->type = - BOX_INLINE_CONTAINER; - - box_add_child(props.containing_block, - props.inline_container); - } - - /** \todo Dropping const isn't clever */ - box = box_create(NULL, - (css_computed_style *) props.parent_style, - false, props.href, props.target, props.title, - NULL, ctx->bctx); - if (box == NULL) { - free(text); - return false; - } - - box->type = BOX_TEXT; - - box->text = talloc_strdup(ctx->bctx, current); - if (box->text == NULL) { - free(text); - return false; - } - - box->length = strlen(box->text); - - box_add_child(props.inline_container, box); - - current[len] = old; - - current += len; - - if (current[0] != '\0') { - /* Linebreak: create new inline container */ - props.inline_container = box_create(NULL, NULL, - false, NULL, NULL, NULL, NULL, - ctx->bctx); - if (props.inline_container == NULL) { - free(text); - return false; - } - - props.inline_container->type = - BOX_INLINE_CONTAINER; - - box_add_child(props.containing_block, - props.inline_container); - - if (current[0] == '\r' && current[1] == '\n') - current += 2; - else - current++; - } - } while (*current); - - free(text); - } - - return true; -} - -/** - * Get the style for an element. - * - * \param c content of type CONTENT_HTML that is being processed - * \param parent_style style at this point in xml tree, or NULL for root - * \param root_style root node's style, or NULL for root - * \param n node in xml tree - * \return the new style, or NULL on memory exhaustion - */ -css_select_results *box_get_style(html_content *c, - const css_computed_style *parent_style, - const css_computed_style *root_style, dom_node *n) -{ - dom_string *s; - dom_exception err; - css_stylesheet *inline_style = NULL; - css_select_results *styles; - nscss_select_ctx ctx; - - /* Firstly, construct inline stylesheet, if any */ - err = dom_element_get_attribute(n, corestring_dom_style, &s); - if (err != DOM_NO_ERR) - return NULL; - - if (s != NULL) { - inline_style = nscss_create_inline_style( - (const uint8_t *) dom_string_data(s), - dom_string_byte_length(s), - c->encoding, - nsurl_access(c->base_url), - c->quirks != DOM_DOCUMENT_QUIRKS_MODE_NONE); - - dom_string_unref(s); - - if (inline_style == NULL) - return NULL; - } - - /* Populate selection context */ - ctx.ctx = c->select_ctx; - ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); - ctx.base_url = c->base_url; - ctx.universal = c->universal; - ctx.root_style = root_style; - ctx.parent_style = parent_style; - - /* Select style for element */ - styles = nscss_get_style(&ctx, n, CSS_MEDIA_SCREEN, inline_style); - - /* No longer need inline style */ - if (inline_style != NULL) - css_stylesheet_destroy(inline_style); - - return styles; -} - - -/** - * Apply the CSS text-transform property to given text for its ASCII chars. - * - * \param s string to transform - * \param len length of s - * \param tt transform type - */ - -void box_text_transform(char *s, unsigned int len, enum css_text_transform_e tt) -{ - unsigned int i; - if (len == 0) - return; - switch (tt) { - case CSS_TEXT_TRANSFORM_UPPERCASE: - for (i = 0; i < len; ++i) - if ((unsigned char) s[i] < 0x80) - s[i] = toupper(s[i]); - break; - case CSS_TEXT_TRANSFORM_LOWERCASE: - for (i = 0; i < len; ++i) - if ((unsigned char) s[i] < 0x80) - s[i] = tolower(s[i]); - break; - case CSS_TEXT_TRANSFORM_CAPITALIZE: - if ((unsigned char) s[0] < 0x80) - s[0] = toupper(s[0]); - for (i = 1; i < len; ++i) - if ((unsigned char) s[i] < 0x80 && - isspace(s[i - 1])) - s[i] = toupper(s[i]); - break; - default: - break; - } -} - - -/** - * \name Special case element handlers - * - * These functions are called by box_construct_element() when an element is - * being converted, according to the entries in element_table. - * - * The parameters are the xmlNode, the content for the document, and a partly - * filled in box structure for the element. - * - * Return true on success, false on memory exhaustion. Set *convert_children - * to false if children of this element in the XML tree should be skipped (for - * example, if they have been processed in some special way already). - * - * Elements ordered as in the HTML 4.01 specification. Section numbers in - * brackets [] refer to the spec. - * - * \{ - */ - -/** - * Document body [7.5.1]. - */ - -bool box_body(BOX_SPECIAL_PARAMS) -{ - css_color color; - - css_computed_background_color(box->style, &color); - if (nscss_color_is_transparent(color)) - content->background_colour = NS_TRANSPARENT; - else - content->background_colour = nscss_color_to_ns(color); - - return true; -} - - -/** - * Forced line break [9.3.2]. - */ - -bool box_br(BOX_SPECIAL_PARAMS) -{ - box->type = BOX_BR; - return true; -} - -/** - * Preformatted text [9.3.4]. - */ - -bool box_pre(BOX_SPECIAL_PARAMS) -{ - box->flags |= PRE_STRIP; - return true; -} - -/** - * Anchor [12.2]. - */ - -bool box_a(BOX_SPECIAL_PARAMS) -{ - bool ok; - nsurl *url; - dom_string *s; - dom_exception err; - - err = dom_element_get_attribute(n, corestring_dom_href, &s); - if (err == DOM_NO_ERR && s != NULL) { - ok = box_extract_link(content, s, content->base_url, &url); - dom_string_unref(s); - if (!ok) - return false; - if (url) { - if (box->href != NULL) - nsurl_unref(box->href); - box->href = url; - } - } - - /* name and id share the same namespace */ - err = dom_element_get_attribute(n, corestring_dom_name, &s); - if (err == DOM_NO_ERR && s != NULL) { - lwc_string *lwc_name; - - err = dom_string_intern(s, &lwc_name); - - dom_string_unref(s); - - if (err == DOM_NO_ERR) { - /* name replaces existing id - * TODO: really? */ - if (box->id != NULL) - lwc_string_unref(box->id); - - box->id = lwc_name; - } - } - - /* target frame [16.3] */ - err = dom_element_get_attribute(n, corestring_dom_target, &s); - if (err == DOM_NO_ERR && s != NULL) { - if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__blank)) - box->target = TARGET_BLANK; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__top)) - box->target = TARGET_TOP; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__parent)) - box->target = TARGET_PARENT; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc__self)) - /* the default may have been overridden by a - * , so this is different to 0 */ - box->target = TARGET_SELF; - else { - /* 6.16 says that frame names must begin with [a-zA-Z] - * This doesn't match reality, so just take anything */ - box->target = talloc_strdup(content->bctx, - dom_string_data(s)); - if (!box->target) { - dom_string_unref(s); - return false; - } - } - dom_string_unref(s); - } - - return true; -} - - -/** - * Embedded image [13.2]. - */ - -bool box_image(BOX_SPECIAL_PARAMS) -{ - bool ok; - dom_string *s; - dom_exception err; - nsurl *url; - enum css_width_e wtype; - enum css_height_e htype; - css_fixed value = 0; - css_unit wunit = CSS_UNIT_PX; - css_unit hunit = CSS_UNIT_PX; - - if (box->style && ns_computed_display(box->style, - box_is_root(n)) == CSS_DISPLAY_NONE) - return true; - - /* handle alt text */ - err = dom_element_get_attribute(n, corestring_dom_alt, &s); - if (err == DOM_NO_ERR && s != NULL) { - char *alt = squash_whitespace(dom_string_data(s)); - dom_string_unref(s); - if (alt == NULL) - return false; - box->text = talloc_strdup(content->bctx, alt); - free(alt); - if (box->text == NULL) - return false; - box->length = strlen(box->text); - } - - if (nsoption_bool(foreground_images) == false) { - return true; - } - - /* imagemap associated with this image */ - if (!box_get_attribute(n, "usemap", content->bctx, &box->usemap)) - return false; - if (box->usemap && box->usemap[0] == '#') - box->usemap++; - - /* get image URL */ - err = dom_element_get_attribute(n, corestring_dom_src, &s); - if (err != DOM_NO_ERR || s == NULL) - return true; - - if (box_extract_link(content, s, content->base_url, &url) == false) { - dom_string_unref(s); - return false; - } - - dom_string_unref(s); - - if (url == NULL) - return true; - - /* start fetch */ - box->flags |= IS_REPLACED; - ok = html_fetch_object(content, url, box, image_types, - content->base.available_width, 1000, false); - nsurl_unref(url); - - wtype = css_computed_width(box->style, &value, &wunit); - htype = css_computed_height(box->style, &value, &hunit); - - if (wtype == CSS_WIDTH_SET && wunit != CSS_UNIT_PCT && - htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT) { - /* We know the dimensions the image will be shown at before it's - * fetched. */ - box->flags |= REPLACE_DIM; - } - - return ok; -} - - -/** - * Noscript element - */ - -bool box_noscript(BOX_SPECIAL_PARAMS) -{ - /* If scripting is enabled, do not display the contents of noscript */ - if (content->enable_scripting) - *convert_children = false; - - return true; -} - - -/** - * Destructor for object_params, for <object> elements - * - * \param o The object params being destroyed. - * \return 0 to allow talloc to continue destroying the tree. - */ -static int box_object_talloc_destructor(struct object_params *o) -{ - if (o->codebase != NULL) - nsurl_unref(o->codebase); - if (o->classid != NULL) - nsurl_unref(o->classid); - if (o->data != NULL) - nsurl_unref(o->data); - - return 0; -} - -/** - * Generic embedded object [13.3]. - */ - -bool box_object(BOX_SPECIAL_PARAMS) -{ - struct object_params *params; - struct object_param *param; - dom_string *codebase, *classid, *data; - dom_node *c; - dom_exception err; - - if (box->style && ns_computed_display(box->style, - box_is_root(n)) == CSS_DISPLAY_NONE) - return true; - - if (box_get_attribute(n, "usemap", content->bctx, &box->usemap) == - false) - return false; - if (box->usemap && box->usemap[0] == '#') - box->usemap++; - - params = talloc(content->bctx, struct object_params); - if (params == NULL) - return false; - - talloc_set_destructor(params, box_object_talloc_destructor); - - params->data = NULL; - params->type = NULL; - params->codetype = NULL; - params->codebase = NULL; - params->classid = NULL; - params->params = NULL; - - /* codebase, classid, and data are URLs - * (codebase is the base for the other two) */ - err = dom_element_get_attribute(n, corestring_dom_codebase, &codebase); - if (err == DOM_NO_ERR && codebase != NULL) { - if (box_extract_link(content, codebase, content->base_url, - ¶ms->codebase) == false) { - dom_string_unref(codebase); - return false; - } - dom_string_unref(codebase); - } - if (params->codebase == NULL) - params->codebase = nsurl_ref(content->base_url); - - err = dom_element_get_attribute(n, corestring_dom_classid, &classid); - if (err == DOM_NO_ERR && classid != NULL) { - if (box_extract_link(content, classid, - params->codebase, ¶ms->classid) == false) { - dom_string_unref(classid); - return false; - } - dom_string_unref(classid); - } - - err = dom_element_get_attribute(n, corestring_dom_data, &data); - if (err == DOM_NO_ERR && data != NULL) { - if (box_extract_link(content, data, - params->codebase, ¶ms->data) == false) { - dom_string_unref(data); - return false; - } - dom_string_unref(data); - } - - if (params->classid == NULL && params->data == NULL) - /* nothing to embed; ignore */ - return true; - - /* Don't include ourself */ - if (params->classid != NULL && nsurl_compare(content->base_url, - params->classid, NSURL_COMPLETE)) - return true; - - if (params->data != NULL && nsurl_compare(content->base_url, - params->data, NSURL_COMPLETE)) - return true; - - /* codetype and type are MIME types */ - if (box_get_attribute(n, "codetype", params, - ¶ms->codetype) == false) - return false; - if (box_get_attribute(n, "type", params, ¶ms->type) == false) - return false; - - /* classid && !data => classid is used (consult codetype) - * (classid || !classid) && data => data is used (consult type) - * !classid && !data => invalid; ignored */ - - if (params->classid != NULL && params->data == NULL && - params->codetype != NULL) { - lwc_string *icodetype; - lwc_error lerror; - - lerror = lwc_intern_string(params->codetype, - strlen(params->codetype), &icodetype); - if (lerror != lwc_error_ok) - return false; - - if (content_factory_type_from_mime_type(icodetype) == - CONTENT_NONE) { - /* can't handle this MIME type */ - lwc_string_unref(icodetype); - return true; - } - - lwc_string_unref(icodetype); - } - - if (params->data != NULL && params->type != NULL) { - lwc_string *itype; - lwc_error lerror; - - lerror = lwc_intern_string(params->type, strlen(params->type), - &itype); - if (lerror != lwc_error_ok) - return false; - - if (content_factory_type_from_mime_type(itype) == - CONTENT_NONE) { - /* can't handle this MIME type */ - lwc_string_unref(itype); - return true; - } - - lwc_string_unref(itype); - } - - /* add parameters to linked list */ - err = dom_node_get_first_child(n, &c); - if (err != DOM_NO_ERR) - return false; - - while (c != NULL) { - dom_node *next; - dom_node_type type; - - err = dom_node_get_node_type(c, &type); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - if (type == DOM_ELEMENT_NODE) { - dom_string *name; - - err = dom_node_get_node_name(c, &name); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - if (!dom_string_caseless_lwc_isequal(name, - corestring_lwc_param)) { - /* The first non-param child is the start of - * the alt html. Therefore, we should break - * out of this loop. */ - dom_node_unref(c); - break; - } - - param = talloc(params, struct object_param); - if (param == NULL) { - dom_node_unref(c); - return false; - } - param->name = NULL; - param->value = NULL; - param->type = NULL; - param->valuetype = NULL; - param->next = NULL; - - if (box_get_attribute(c, "name", param, - ¶m->name) == false) { - dom_node_unref(c); - return false; - } - - if (box_get_attribute(c, "value", param, - ¶m->value) == false) { - dom_node_unref(c); - return false; - } - - if (box_get_attribute(c, "type", param, - ¶m->type) == false) { - dom_node_unref(c); - return false; - } - - if (box_get_attribute(c, "valuetype", param, - ¶m->valuetype) == false) { - dom_node_unref(c); - return false; - } - - if (param->valuetype == NULL) { - param->valuetype = talloc_strdup(param, "data"); - if (param->valuetype == NULL) { - dom_node_unref(c); - return false; - } - } - - param->next = params->params; - params->params = param; - } - - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - dom_node_unref(c); - c = next; - } - - box->object_params = params; - - /* start fetch (MIME type is ok or not specified) */ - box->flags |= IS_REPLACED; - if (!html_fetch_object(content, - params->data ? params->data : params->classid, - box, CONTENT_ANY, content->base.available_width, 1000, - false)) - return false; - - *convert_children = false; - return true; -} - - -/** - * Window subdivision [16.2.1]. - */ - -bool box_frameset(BOX_SPECIAL_PARAMS) -{ - bool ok; - - if (content->frameset) { - NSLOG(netsurf, INFO, "Error: multiple framesets in document."); - /* Don't convert children */ - if (convert_children) - *convert_children = false; - /* And ignore this spurious frameset */ - box->type = BOX_NONE; - return true; - } - - content->frameset = talloc_zero(content->bctx, struct content_html_frames); - if (!content->frameset) - return false; - - ok = box_create_frameset(content->frameset, n, content); - if (ok) - box->type = BOX_NONE; - - if (convert_children) - *convert_children = false; - return ok; -} - - -/** - * Destructor for content_html_frames, for frame elements - * - * \param f The frame params being destroyed. - * \return 0 to allow talloc to continue destroying the tree. - */ -static int box_frames_talloc_destructor(struct content_html_frames *f) -{ - if (f->url != NULL) { - nsurl_unref(f->url); - f->url = NULL; - } - - return 0; -} - - -/** - * Parse a multi-length-list, as defined by HTML 4.01. - * - * \param ds dom string to parse - * \param count updated to number of entries - * \return array of struct box_multi_length, or 0 on memory exhaustion - */ -static struct frame_dimension * -box_parse_multi_lengths(const dom_string *ds, unsigned int *count) -{ - char *end; - unsigned int i, n; - struct frame_dimension *length; - const char *s; - - s = dom_string_data(ds); - - for (i = 0, n = 1; s[i]; i++) - if (s[i] == ',') - n++; - - length = calloc(n, sizeof(struct frame_dimension)); - if (!length) - return NULL; - - for (i = 0; i != n; i++) { - while (ascii_is_space(*s)) { - s++; - } - length[i].value = strtof(s, &end); - if (length[i].value <= 0) { - length[i].value = 1; - } - s = end; - switch (*s) { - case '%': - length[i].unit = FRAME_DIMENSION_PERCENT; - break; - case '*': - length[i].unit = FRAME_DIMENSION_RELATIVE; - break; - default: - length[i].unit = FRAME_DIMENSION_PIXELS; - break; - } - while (*s && *s != ',') { - s++; - } - if (*s == ',') { - s++; - } - } - - *count = n; - return length; -} - - -bool box_create_frameset(struct content_html_frames *f, dom_node *n, - html_content *content) { - unsigned int row, col, index, i; - unsigned int rows = 1, cols = 1; - dom_string *s; - dom_exception err; - nsurl *url; - struct frame_dimension *row_height = 0, *col_width = 0; - dom_node *c, *next; - struct content_html_frames *frame; - bool default_border = true; - colour default_border_colour = 0x000000; - - /* parse rows and columns */ - err = dom_element_get_attribute(n, corestring_dom_rows, &s); - if (err == DOM_NO_ERR && s != NULL) { - row_height = box_parse_multi_lengths(s, &rows); - dom_string_unref(s); - if (row_height == NULL) - return false; - } else { - row_height = calloc(1, sizeof(struct frame_dimension)); - if (row_height == NULL) - return false; - row_height->value = 100; - row_height->unit = FRAME_DIMENSION_PERCENT; - } - - err = dom_element_get_attribute(n, corestring_dom_cols, &s); - if (err == DOM_NO_ERR && s != NULL) { - col_width = box_parse_multi_lengths(s, &cols); - dom_string_unref(s); - if (col_width == NULL) { - free(row_height); - return false; - } - } else { - col_width = calloc(1, sizeof(struct frame_dimension)); - if (col_width == NULL) { - free(row_height); - return false; - } - col_width->value = 100; - col_width->unit = FRAME_DIMENSION_PERCENT; - } - - /* common extension: border="0|1" to control all children */ - err = dom_element_get_attribute(n, corestring_dom_border, &s); - if (err == DOM_NO_ERR && s != NULL) { - if ((dom_string_data(s)[0] == '0') && - (dom_string_data(s)[1] == '\0')) - default_border = false; - dom_string_unref(s); - } - - /* common extension: frameborder="yes|no" to control all children */ - err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); - if (err == DOM_NO_ERR && s != NULL) { - if (dom_string_caseless_lwc_isequal(s, - corestring_lwc_no) == 0) - default_border = false; - dom_string_unref(s); - } - - /* common extension: bordercolor="#RRGGBB|" to control - *all children */ - err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); - if (err == DOM_NO_ERR && s != NULL) { - css_color color; - - if (nscss_parse_colour(dom_string_data(s), &color)) - default_border_colour = nscss_color_to_ns(color); - - dom_string_unref(s); - } - - /* update frameset and create default children */ - f->cols = cols; - f->rows = rows; - f->scrolling = BW_SCROLLING_NO; - f->children = talloc_array(content->bctx, struct content_html_frames, - (rows * cols)); - - talloc_set_destructor(f->children, box_frames_talloc_destructor); - - for (row = 0; row < rows; row++) { - for (col = 0; col < cols; col++) { - index = (row * cols) + col; - frame = &f->children[index]; - frame->cols = 0; - frame->rows = 0; - frame->width = col_width[col]; - frame->height = row_height[row]; - frame->margin_width = 0; - frame->margin_height = 0; - frame->name = NULL; - frame->url = NULL; - frame->no_resize = false; - frame->scrolling = BW_SCROLLING_AUTO; - frame->border = default_border; - frame->border_colour = default_border_colour; - frame->children = NULL; - } - } - free(col_width); - free(row_height); - - /* create the frameset windows */ - err = dom_node_get_first_child(n, &c); - if (err != DOM_NO_ERR) - return false; - - for (row = 0; c != NULL && row < rows; row++) { - for (col = 0; c != NULL && col < cols; col++) { - while (c != NULL) { - dom_node_type type; - dom_string *name; - - err = dom_node_get_node_type(c, &type); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - err = dom_node_get_node_name(c, &name); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - if (type != DOM_ELEMENT_NODE || - (!dom_string_caseless_lwc_isequal( - name, - corestring_lwc_frame) && - !dom_string_caseless_lwc_isequal( - name, - corestring_lwc_frameset - ))) { - err = dom_node_get_next_sibling(c, - &next); - if (err != DOM_NO_ERR) { - dom_string_unref(name); - dom_node_unref(c); - return false; - } - - dom_string_unref(name); - dom_node_unref(c); - c = next; - } else { - /* Got a FRAME or FRAMESET element */ - dom_string_unref(name); - break; - } - } - - if (c == NULL) - break; - - /* get current frame */ - index = (row * cols) + col; - frame = &f->children[index]; - - /* nest framesets */ - err = dom_node_get_node_name(c, &s); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - if (dom_string_caseless_lwc_isequal(s, - corestring_lwc_frameset)) { - dom_string_unref(s); - frame->border = 0; - if (box_create_frameset(frame, c, - content) == false) { - dom_node_unref(c); - return false; - } - - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - dom_node_unref(c); - c = next; - continue; - } - - dom_string_unref(s); - - /* get frame URL (not required) */ - url = NULL; - err = dom_element_get_attribute(c, corestring_dom_src, &s); - if (err == DOM_NO_ERR && s != NULL) { - box_extract_link(content, s, content->base_url, - &url); - dom_string_unref(s); - } - - /* copy url */ - if (url != NULL) { - /* no self-references */ - if (nsurl_compare(content->base_url, url, - NSURL_COMPLETE) == false) - frame->url = url; - url = NULL; - } - - /* fill in specified values */ - err = dom_element_get_attribute(c, corestring_dom_name, &s); - if (err == DOM_NO_ERR && s != NULL) { - frame->name = talloc_strdup(content->bctx, - dom_string_data(s)); - dom_string_unref(s); - } - - dom_element_has_attribute(c, corestring_dom_noresize, - &frame->no_resize); - - err = dom_element_get_attribute(c, corestring_dom_frameborder, - &s); - if (err == DOM_NO_ERR && s != NULL) { - i = atoi(dom_string_data(s)); - frame->border = (i != 0); - dom_string_unref(s); - } - - err = dom_element_get_attribute(c, corestring_dom_scrolling, &s); - if (err == DOM_NO_ERR && s != NULL) { - if (dom_string_caseless_lwc_isequal(s, - corestring_lwc_yes)) - frame->scrolling = BW_SCROLLING_YES; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc_no)) - frame->scrolling = BW_SCROLLING_NO; - dom_string_unref(s); - } - - err = dom_element_get_attribute(c, corestring_dom_marginwidth, - &s); - if (err == DOM_NO_ERR && s != NULL) { - frame->margin_width = atoi(dom_string_data(s)); - dom_string_unref(s); - } - - err = dom_element_get_attribute(c, corestring_dom_marginheight, - &s); - if (err == DOM_NO_ERR && s != NULL) { - frame->margin_height = atoi(dom_string_data(s)); - dom_string_unref(s); - } - - err = dom_element_get_attribute(c, corestring_dom_bordercolor, - &s); - if (err == DOM_NO_ERR && s != NULL) { - css_color color; - - if (nscss_parse_colour(dom_string_data(s), - &color)) - frame->border_colour = - nscss_color_to_ns(color); - - dom_string_unref(s); - } - - /* advance */ - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - return false; - } - - dom_node_unref(c); - c = next; - } - } - - /* If the last child wasn't a frame, we still need to unref it */ - if (c != NULL) { - dom_node_unref(c); - } - - return true; -} - - -/** - * Destructor for content_html_iframe, for <iframe> elements - * - * \param f The iframe params being destroyed. - * \return 0 to allow talloc to continue destroying the tree. - */ -static int box_iframes_talloc_destructor(struct content_html_iframe *f) -{ - if (f->url != NULL) { - nsurl_unref(f->url); - f->url = NULL; - } - - return 0; -} - - -/** - * Inline subwindow [16.5]. - */ - -bool box_iframe(BOX_SPECIAL_PARAMS) -{ - nsurl *url; - dom_string *s; - dom_exception err; - struct content_html_iframe *iframe; - int i; - - if (box->style && ns_computed_display(box->style, - box_is_root(n)) == CSS_DISPLAY_NONE) - return true; - - if (box->style && css_computed_visibility(box->style) == - CSS_VISIBILITY_HIDDEN) - /* Don't create iframe discriptors for invisible iframes - * TODO: handle hidden iframes at browser_window generation - * time instead? */ - return true; - - /* get frame URL */ - err = dom_element_get_attribute(n, corestring_dom_src, &s); - if (err != DOM_NO_ERR || s == NULL) - return true; - if (box_extract_link(content, s, content->base_url, &url) == false) { - dom_string_unref(s); - return false; - } - dom_string_unref(s); - if (url == NULL) - return true; - - /* don't include ourself */ - if (nsurl_compare(content->base_url, url, NSURL_COMPLETE)) { - nsurl_unref(url); - return true; - } - - /* create a new iframe */ - iframe = talloc(content->bctx, struct content_html_iframe); - if (iframe == NULL) { - nsurl_unref(url); - return false; - } - - talloc_set_destructor(iframe, box_iframes_talloc_destructor); - - iframe->box = box; - iframe->margin_width = 0; - iframe->margin_height = 0; - iframe->name = NULL; - iframe->url = url; - iframe->scrolling = BW_SCROLLING_AUTO; - iframe->border = true; - - /* Add this iframe to the linked list of iframes */ - iframe->next = content->iframe; - content->iframe = iframe; - - /* fill in specified values */ - err = dom_element_get_attribute(n, corestring_dom_name, &s); - if (err == DOM_NO_ERR && s != NULL) { - iframe->name = talloc_strdup(content->bctx, dom_string_data(s)); - dom_string_unref(s); - } - - err = dom_element_get_attribute(n, corestring_dom_frameborder, &s); - if (err == DOM_NO_ERR && s != NULL) { - i = atoi(dom_string_data(s)); - iframe->border = (i != 0); - dom_string_unref(s); - } - - err = dom_element_get_attribute(n, corestring_dom_bordercolor, &s); - if (err == DOM_NO_ERR && s != NULL) { - css_color color; - - if (nscss_parse_colour(dom_string_data(s), &color)) - iframe->border_colour = nscss_color_to_ns(color); - - dom_string_unref(s); - } - - err = dom_element_get_attribute(n, corestring_dom_scrolling, &s); - if (err == DOM_NO_ERR && s != NULL) { - if (dom_string_caseless_lwc_isequal(s, - corestring_lwc_yes)) - iframe->scrolling = BW_SCROLLING_YES; - else if (dom_string_caseless_lwc_isequal(s, - corestring_lwc_no)) - iframe->scrolling = BW_SCROLLING_NO; - dom_string_unref(s); - } - - err = dom_element_get_attribute(n, corestring_dom_marginwidth, &s); - if (err == DOM_NO_ERR && s != NULL) { - iframe->margin_width = atoi(dom_string_data(s)); - dom_string_unref(s); - } - - err = dom_element_get_attribute(n, corestring_dom_marginheight, &s); - if (err == DOM_NO_ERR && s != NULL) { - iframe->margin_height = atoi(dom_string_data(s)); - dom_string_unref(s); - } - - /* box */ - assert(box->style); - box->flags |= IFRAME; - box->flags |= IS_REPLACED; - - /* Showing iframe, so don't show alternate content */ - if (convert_children) - *convert_children = false; - return true; -} - - -/** - * Helper function for adding textarea widget to box. - * - * This is a load of hacks to ensure boxes replaced with textareas - * can be handled by the layout code. - */ - -static bool box_input_text(html_content *html, struct box *box, - struct dom_node *node) -{ - struct box *inline_container, *inline_box; - - box->type = BOX_INLINE_BLOCK; - - inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, html->bctx); - if (!inline_container) - return false; - inline_container->type = BOX_INLINE_CONTAINER; - inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, - html->bctx); - if (!inline_box) - return false; - inline_box->type = BOX_TEXT; - inline_box->text = talloc_strdup(html->bctx, ""); - - box_add_child(inline_container, inline_box); - box_add_child(box, inline_container); - - return box_textarea_create_textarea(html, box, node); -} - - -/** - * Form control [17.4]. - */ - -bool box_input(BOX_SPECIAL_PARAMS) -{ - struct form_control *gadget = NULL; - dom_string *type = NULL; - dom_exception err; - nsurl *url; - nserror error; - - dom_element_get_attribute(n, corestring_dom_type, &type); - - gadget = html_forms_get_control_for_node(content->forms, n); - if (gadget == NULL) - goto no_memory; - box->gadget = gadget; - box->flags |= IS_REPLACED; - gadget->box = box; - gadget->html = content; - - if (type && dom_string_caseless_lwc_isequal(type, - corestring_lwc_password)) { - if (box_input_text(content, box, n) == false) - goto no_memory; - - } else if (type && dom_string_caseless_lwc_isequal(type, - corestring_lwc_file)) { - box->type = BOX_INLINE_BLOCK; - - } else if (type && dom_string_caseless_lwc_isequal(type, - corestring_lwc_hidden)) { - /* no box for hidden inputs */ - box->type = BOX_NONE; - - } else if (type && - (dom_string_caseless_lwc_isequal(type, - corestring_lwc_checkbox) || - dom_string_caseless_lwc_isequal(type, - corestring_lwc_radio))) { - - } else if (type && - (dom_string_caseless_lwc_isequal(type, - corestring_lwc_submit) || - dom_string_caseless_lwc_isequal(type, - corestring_lwc_reset) || - dom_string_caseless_lwc_isequal(type, - corestring_lwc_button))) { - struct box *inline_container, *inline_box; - - if (box_button(n, content, box, 0) == false) - goto no_memory; - - inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, - content->bctx); - if (inline_container == NULL) - goto no_memory; - - inline_container->type = BOX_INLINE_CONTAINER; - - inline_box = box_create(NULL, box->style, false, 0, 0, - box->title, 0, content->bctx); - if (inline_box == NULL) - goto no_memory; - - inline_box->type = BOX_TEXT; - - if (box->gadget->value != NULL) - inline_box->text = talloc_strdup(content->bctx, - box->gadget->value); - else if (box->gadget->type == GADGET_SUBMIT) - inline_box->text = talloc_strdup(content->bctx, - messages_get("Form_Submit")); - else if (box->gadget->type == GADGET_RESET) - inline_box->text = talloc_strdup(content->bctx, - messages_get("Form_Reset")); - else - inline_box->text = talloc_strdup(content->bctx, - "Button"); - - if (inline_box->text == NULL) - goto no_memory; - - inline_box->length = strlen(inline_box->text); - - box_add_child(inline_container, inline_box); - - box_add_child(box, inline_container); - - } else if (type && dom_string_caseless_lwc_isequal(type, - corestring_lwc_image)) { - gadget->type = GADGET_IMAGE; - - if (box->style && ns_computed_display(box->style, - box_is_root(n)) != CSS_DISPLAY_NONE && - nsoption_bool(foreground_images) == true) { - dom_string *s; - - err = dom_element_get_attribute(n, corestring_dom_src, &s); - if (err == DOM_NO_ERR && s != NULL) { - error = nsurl_join(content->base_url, - dom_string_data(s), &url); - dom_string_unref(s); - if (error != NSERROR_OK) - goto no_memory; - - /* if url is equivalent to the parent's url, - * we've got infinite inclusion. stop it here - */ - if (nsurl_compare(url, content->base_url, - NSURL_COMPLETE) == false) { - if (!html_fetch_object(content, url, - box, image_types, - content->base. - available_width, - 1000, false)) { - nsurl_unref(url); - goto no_memory; - } - } - nsurl_unref(url); - } - } - } else { - /* the default type is "text" */ - if (box_input_text(content, box, n) == false) - goto no_memory; - } - - if (type) - dom_string_unref(type); - - *convert_children = false; - return true; - -no_memory: - if (type) - dom_string_unref(type); - - return false; -} - - -/** - * Push button [17.5]. - */ - -bool box_button(BOX_SPECIAL_PARAMS) -{ - struct form_control *gadget; - - gadget = html_forms_get_control_for_node(content->forms, n); - if (!gadget) - return false; - - gadget->html = content; - box->gadget = gadget; - box->flags |= IS_REPLACED; - gadget->box = box; - - box->type = BOX_INLINE_BLOCK; - - /* Just render the contents */ - - return true; -} - - -/** - * Option selector [17.6]. - */ - -bool box_select(BOX_SPECIAL_PARAMS) -{ - struct box *inline_container; - struct box *inline_box; - struct form_control *gadget; - dom_node *c, *c2; - dom_node *next, *next2; - dom_exception err; - - gadget = html_forms_get_control_for_node(content->forms, n); - if (gadget == NULL) - return false; - - gadget->html = content; - err = dom_node_get_first_child(n, &c); - if (err != DOM_NO_ERR) { - form_free_control(gadget); - return false; - } - - while (c != NULL) { - dom_string *name; - - err = dom_node_get_node_name(c, &name); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - form_free_control(gadget); - return false; - } - - if (dom_string_caseless_lwc_isequal(name, - corestring_lwc_option)) { - dom_string_unref(name); - - if (box_select_add_option(gadget, c) == false) { - dom_node_unref(c); - form_free_control(gadget); - return false; - } - } else if (dom_string_caseless_lwc_isequal(name, - corestring_lwc_optgroup)) { - dom_string_unref(name); - - err = dom_node_get_first_child(c, &c2); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - form_free_control(gadget); - return false; - } - - while (c2 != NULL) { - dom_string *c2_name; - - err = dom_node_get_node_name(c2, &c2_name); - if (err != DOM_NO_ERR) { - dom_node_unref(c2); - dom_node_unref(c); - form_free_control(gadget); - return false; - } - - if (dom_string_caseless_lwc_isequal(c2_name, - corestring_lwc_option)) { - dom_string_unref(c2_name); - - if (box_select_add_option(gadget, - c2) == false) { - dom_node_unref(c2); - dom_node_unref(c); - return false; - } - } else { - dom_string_unref(c2_name); - } - - err = dom_node_get_next_sibling(c2, &next2); - if (err != DOM_NO_ERR) { - dom_node_unref(c2); - dom_node_unref(c); - return false; - } - - dom_node_unref(c2); - c2 = next2; - } - } else { - dom_string_unref(name); - } - - err = dom_node_get_next_sibling(c, &next); - if (err != DOM_NO_ERR) { - dom_node_unref(c); - form_free_control(gadget); - return false; - } - - dom_node_unref(c); - c = next; - } - - if (gadget->data.select.num_items == 0) { - /* no options: ignore entire select */ - form_free_control(gadget); - return true; - } - - box->type = BOX_INLINE_BLOCK; - box->gadget = gadget; - box->flags |= IS_REPLACED; - gadget->box = box; - - inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx); - if (inline_container == NULL) - goto no_memory; - inline_container->type = BOX_INLINE_CONTAINER; - inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, - content->bctx); - if (inline_box == NULL) - goto no_memory; - inline_box->type = BOX_TEXT; - box_add_child(inline_container, inline_box); - box_add_child(box, inline_container); - - if (gadget->data.select.multiple == false && - gadget->data.select.num_selected == 0) { - gadget->data.select.current = gadget->data.select.items; - gadget->data.select.current->initial_selected = - gadget->data.select.current->selected = true; - gadget->data.select.num_selected = 1; - dom_html_option_element_set_selected( - gadget->data.select.current->node, true); - } - - if (gadget->data.select.num_selected == 0) - inline_box->text = talloc_strdup(content->bctx, - messages_get("Form_None")); - else if (gadget->data.select.num_selected == 1) - inline_box->text = talloc_strdup(content->bctx, - gadget->data.select.current->text); - else - inline_box->text = talloc_strdup(content->bctx, - messages_get("Form_Many")); - if (inline_box->text == NULL) - goto no_memory; - - inline_box->length = strlen(inline_box->text); - - *convert_children = false; - return true; - -no_memory: - return false; -} - - -/** - * Add an option to a form select control (helper function for box_select()). - * - * \param control select containing the <option> - * \param n xml element node for <option> - * \return true on success, false on memory exhaustion - */ - -bool box_select_add_option(struct form_control *control, dom_node *n) -{ - char *value = NULL; - char *text = NULL; - char *text_nowrap = NULL; - bool selected; - dom_string *content, *s; - dom_exception err; - - err = dom_node_get_text_content(n, &content); - if (err != DOM_NO_ERR) - return false; - - if (content != NULL) { - text = squash_whitespace(dom_string_data(content)); - dom_string_unref(content); - } else { - text = strdup(""); - } - - if (text == NULL) - goto no_memory; - - err = dom_element_get_attribute(n, corestring_dom_value, &s); - if (err == DOM_NO_ERR && s != NULL) { - value = strdup(dom_string_data(s)); - dom_string_unref(s); - } else { - value = strdup(text); - } - - if (value == NULL) - goto no_memory; - - dom_element_has_attribute(n, corestring_dom_selected, &selected); - - /* replace spaces/TABs with hard spaces to prevent line wrapping */ - text_nowrap = cnv_space2nbsp(text); - if (text_nowrap == NULL) - goto no_memory; - - if (form_add_option(control, value, text_nowrap, selected, n) == false) - goto no_memory; - - free(text); - - return true; - -no_memory: - free(value); - free(text); - free(text_nowrap); - return false; -} - - -/** - * Multi-line text field [17.7]. - */ - -bool box_textarea(BOX_SPECIAL_PARAMS) -{ - /* Get the form_control for the DOM node */ - box->gadget = html_forms_get_control_for_node(content->forms, n); - if (box->gadget == NULL) - return false; - - box->flags |= IS_REPLACED; - box->gadget->html = content; - box->gadget->box = box; - - if (!box_input_text(content, box, n)) - return false; - - *convert_children = false; - return true; -} - - -/** - * Embedded object (not in any HTML specification: - * see http://wp.netscape.com/assist/net_sites/new_html3_prop.html ) - */ - -bool box_embed(BOX_SPECIAL_PARAMS) -{ - struct object_params *params; - struct object_param *param; - dom_namednodemap *attrs; - unsigned long idx; - uint32_t num_attrs; - dom_string *src; - dom_exception err; - - if (box->style && ns_computed_display(box->style, - box_is_root(n)) == CSS_DISPLAY_NONE) - return true; - - params = talloc(content->bctx, struct object_params); - if (params == NULL) - return false; - - talloc_set_destructor(params, box_object_talloc_destructor); - - params->data = NULL; - params->type = NULL; - params->codetype = NULL; - params->codebase = NULL; - params->classid = NULL; - params->params = NULL; - - /* src is a URL */ - err = dom_element_get_attribute(n, corestring_dom_src, &src); - if (err != DOM_NO_ERR || src == NULL) - return true; - if (box_extract_link(content, src, content->base_url, - ¶ms->data) == false) { - dom_string_unref(src); - return false; - } - - dom_string_unref(src); - - if (params->data == NULL) - return true; - - /* Don't include ourself */ - if (nsurl_compare(content->base_url, params->data, NSURL_COMPLETE)) - return true; - - /* add attributes as parameters to linked list */ - err = dom_node_get_attributes(n, &attrs); - if (err != DOM_NO_ERR) - return false; - - err = dom_namednodemap_get_length(attrs, &num_attrs); - if (err != DOM_NO_ERR) { - dom_namednodemap_unref(attrs); - return false; - } - - for (idx = 0; idx < num_attrs; idx++) { - dom_attr *attr; - dom_string *name, *value; - - err = dom_namednodemap_item(attrs, idx, (void *) &attr); - if (err != DOM_NO_ERR) { - dom_namednodemap_unref(attrs); - return false; - } - - err = dom_attr_get_name(attr, &name); - if (err != DOM_NO_ERR) { - dom_namednodemap_unref(attrs); - return false; - } - - if (dom_string_caseless_lwc_isequal(name, corestring_lwc_src)) { - dom_string_unref(name); - continue; - } - - err = dom_attr_get_value(attr, &value); - if (err != DOM_NO_ERR) { - dom_string_unref(name); - dom_namednodemap_unref(attrs); - return false; - } - - param = talloc(content->bctx, struct object_param); - if (param == NULL) { - dom_string_unref(value); - dom_string_unref(name); - dom_namednodemap_unref(attrs); - return false; - } - - param->name = talloc_strdup(content->bctx, dom_string_data(name)); - param->value = talloc_strdup(content->bctx, dom_string_data(value)); - param->type = NULL; - param->valuetype = talloc_strdup(content->bctx, "data"); - param->next = NULL; - - dom_string_unref(value); - dom_string_unref(name); - - if (param->name == NULL || param->value == NULL || - param->valuetype == NULL) { - dom_namednodemap_unref(attrs); - return false; - } - - param->next = params->params; - params->params = param; - } - - dom_namednodemap_unref(attrs); - - box->object_params = params; - - /* start fetch */ - box->flags |= IS_REPLACED; - return html_fetch_object(content, params->data, box, CONTENT_ANY, - content->base.available_width, 1000, false); -} - -/** - * \} - */ - - -/** - * Get the value of an XML element's attribute. - * - * \param n xmlNode, of type XML_ELEMENT_NODE - * \param attribute name of attribute - * \param context talloc context for result buffer - * \param value updated to value, if the attribute is present - * \return true on success, false if attribute present but memory exhausted - * - * Note that returning true does not imply that the attribute was found. If the - * attribute was not found, *value will be unchanged. - */ - -bool box_get_attribute(dom_node *n, const char *attribute, - void *context, char **value) -{ - char *result; - dom_string *attr, *attr_name; - dom_exception err; - - err = dom_string_create_interned((const uint8_t *) attribute, - strlen(attribute), &attr_name); - if (err != DOM_NO_ERR) - return false; - - err = dom_element_get_attribute(n, attr_name, &attr); - if (err != DOM_NO_ERR) { - dom_string_unref(attr_name); - return false; - } - - dom_string_unref(attr_name); - - if (attr != NULL) { - result = talloc_strdup(context, dom_string_data(attr)); - - dom_string_unref(attr); - - if (result == NULL) - return false; - - *value = result; - } - - return true; -} - - -/* exported function documented in render/box.h */ -bool -box_extract_link(const html_content *content, - const dom_string *dsrel, - nsurl *base, - nsurl **result) -{ - char *s, *s1, *apos0 = 0, *apos1 = 0, *quot0 = 0, *quot1 = 0; - unsigned int i, j, end; - nserror error; - const char *rel; - - rel = dom_string_data(dsrel); - - s1 = s = malloc(3 * strlen(rel) + 1); - if (!s) - return false; - - /* copy to s, removing white space and control characters */ - for (i = 0; rel[i] && ascii_is_space(rel[i]); i++) - ; - for (end = strlen(rel); - (end != i) && ascii_is_space(rel[end - 1]); - end--) - ; - for (j = 0; i != end; i++) { - if ((unsigned char) rel[i] < 0x20) { - ; /* skip control characters */ - } else if (rel[i] == ' ') { - s[j++] = '%'; - s[j++] = '2'; - s[j++] = '0'; - } else { - s[j++] = rel[i]; - } - } - s[j] = 0; - - if (content->enable_scripting == false) { - /* extract first quoted string out of "javascript:" link */ - if (strncmp(s, "javascript:", 11) == 0) { - apos0 = strchr(s, '\''); - if (apos0) - apos1 = strchr(apos0 + 1, '\''); - quot0 = strchr(s, '"'); - if (quot0) - quot1 = strchr(quot0 + 1, '"'); - if (apos0 && apos1 && - (!quot0 || !quot1 || apos0 < quot0)) { - *apos1 = 0; - s1 = apos0 + 1; - } else if (quot0 && quot1) { - *quot1 = 0; - s1 = quot0 + 1; - } - } - } - - /* construct absolute URL */ - error = nsurl_join(base, s1, result); - free(s); - if (error != NSERROR_OK) { - *result = NULL; - return false; - } - - return true; -} - - - diff --git a/render/box_normalise.c b/render/box_normalise.c deleted file mode 100644 index 8da245754..000000000 --- a/render/box_normalise.c +++ /dev/null @@ -1,1046 +0,0 @@ -/* - * Copyright 2005 James Bursa - * Copyright 2003 Phil Mellor - * Copyright 2005 John M Bell - * Copyright 2004 Kevin Bagust - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Box tree normalisation (implementation). - */ - -#include -#include -#include - -#include "utils/log.h" -#include "utils/errors.h" -#include "css/select.h" - -#include "render/box.h" -#include "render/html_internal.h" -#include "render/table.h" - -/* Define to enable box normalise debug */ -#undef BOX_NORMALISE_DEBUG - -/** - * Row spanning information for a cell - */ -struct span_info { - /** Number of rows this cell spans */ - unsigned int row_span; - /** Row group of cell */ - struct box *rg; - /** The cell in this column spans all rows until the end of the table */ - bool auto_row; -}; - -/** - * Column record for a table - */ -struct columns { - /** Current column index */ - unsigned int current_column; - /** Number of columns in main part of table 1..max columns */ - unsigned int num_columns; - /** Information about columns in main table, array [0, num_columns) */ - struct span_info *spans; - /** Number of rows in table */ - unsigned int num_rows; -}; - - -static bool box_normalise_table( - struct box *table, - const struct box *root, - html_content *c); -static bool box_normalise_table_spans( - struct box *table, - const struct box *root, - struct span_info *spans, - html_content *c); -static bool box_normalise_table_row_group( - struct box *row_group, - const struct box *root, - struct columns *col_info, - html_content *c); -static bool box_normalise_table_row( - struct box *row, - const struct box *root, - struct columns *col_info, - html_content *c); -static bool calculate_table_row(struct columns *col_info, - unsigned int col_span, unsigned int row_span, - unsigned int *start_column, struct box *cell); -static bool box_normalise_inline_container( - struct box *cont, - const struct box *root, - html_content *c); - -/** - * Ensure the box tree is correctly nested by adding and removing nodes. - * - * \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL - * \param root root box of document - * \param c content of boxes - * \return true on success, false on memory exhaustion - * - * The tree is modified to satisfy the following: - * \code - * parent permitted child nodes - * BLOCK, INLINE_BLOCK BLOCK, INLINE_CONTAINER, TABLE - * INLINE_CONTAINER INLINE, INLINE_BLOCK, FLOAT_LEFT, FLOAT_RIGHT, BR, TEXT - * INLINE, TEXT none - * TABLE at least 1 TABLE_ROW_GROUP - * TABLE_ROW_GROUP at least 1 TABLE_ROW - * TABLE_ROW at least 1 TABLE_CELL - * TABLE_CELL BLOCK, INLINE_CONTAINER, TABLE (same as BLOCK) - * FLOAT_(LEFT|RIGHT) exactly 1 BLOCK or TABLE - * \endcode - */ - -bool box_normalise_block( - struct box *block, - const struct box *root, - html_content *c) -{ - struct box *child; - struct box *next_child; - struct box *table; - css_computed_style *style; - nscss_select_ctx ctx; - - assert(block != NULL); - assert(root != NULL); - - ctx.root_style = root->style; - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "block %p, block->type %u", block, block->type); -#endif - - assert(block->type == BOX_BLOCK || block->type == BOX_INLINE_BLOCK || - block->type == BOX_TABLE_CELL); - - for (child = block->children; child != NULL; child = next_child) { -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "child %p, child->type = %d", child, - child->type); -#endif - - next_child = child->next; /* child may be destroyed */ - - switch (child->type) { - case BOX_BLOCK: - /* ok */ - if (box_normalise_block(child, root, c) == false) - return false; - break; - case BOX_INLINE_CONTAINER: - if (box_normalise_inline_container(child, root, c) == false) - return false; - break; - case BOX_TABLE: - if (box_normalise_table(child, root, c) == false) - return false; - break; - case BOX_INLINE: - case BOX_INLINE_END: - case BOX_INLINE_BLOCK: - case BOX_FLOAT_LEFT: - case BOX_FLOAT_RIGHT: - case BOX_BR: - case BOX_TEXT: - /* should have been wrapped in inline - container by convert_xml_to_box() */ - assert(0); - break; - case BOX_TABLE_ROW_GROUP: - case BOX_TABLE_ROW: - case BOX_TABLE_CELL: - /* insert implied table */ - assert(block->style != NULL); - - ctx.ctx = c->select_ctx; - ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); - ctx.base_url = c->base_url; - ctx.universal = c->universal; - - style = nscss_get_blank_style(&ctx, block->style); - if (style == NULL) - return false; - - table = box_create(NULL, style, true, block->href, - block->target, NULL, NULL, c->bctx); - if (table == NULL) { - css_computed_style_destroy(style); - return false; - } - table->type = BOX_TABLE; - - if (child->prev == NULL) - block->children = table; - else - child->prev->next = table; - - table->prev = child->prev; - - while (child != NULL && ( - child->type == BOX_TABLE_ROW_GROUP || - child->type == BOX_TABLE_ROW || - child->type == BOX_TABLE_CELL)) { - box_add_child(table, child); - - next_child = child->next; - child->next = NULL; - child = next_child; - } - - table->last->next = NULL; - table->next = next_child = child; - if (table->next != NULL) - table->next->prev = table; - else - block->last = table; - table->parent = block; - - if (box_normalise_table(table, root, c) == false) - return false; - break; - default: - assert(0); - } - } - - return true; -} - - -bool box_normalise_table( - struct box *table, - const struct box *root, - html_content * c) -{ - struct box *child; - struct box *next_child; - struct box *row_group; - css_computed_style *style; - struct columns col_info; - nscss_select_ctx ctx; - - assert(table != NULL); - assert(table->type == BOX_TABLE); - - ctx.root_style = root->style; - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "table %p", table); -#endif - - col_info.num_columns = 1; - col_info.current_column = 0; - col_info.spans = malloc(2 * sizeof *col_info.spans); - if (col_info.spans == NULL) - return false; - - col_info.spans[0].row_span = col_info.spans[1].row_span = 0; - col_info.spans[0].auto_row = false; - col_info.spans[1].auto_row = false; - col_info.num_rows = 0; - - for (child = table->children; child != NULL; child = next_child) { - next_child = child->next; - switch (child->type) { - case BOX_TABLE_ROW_GROUP: - /* ok */ - if (box_normalise_table_row_group(child, root, - &col_info, c) == false) { - free(col_info.spans); - return false; - } - break; - case BOX_BLOCK: - case BOX_INLINE_CONTAINER: - case BOX_TABLE: - case BOX_TABLE_ROW: - case BOX_TABLE_CELL: - /* insert implied table row group */ - assert(table->style != NULL); - - ctx.ctx = c->select_ctx; - ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); - ctx.base_url = c->base_url; - ctx.universal = c->universal; - - style = nscss_get_blank_style(&ctx, table->style); - if (style == NULL) { - free(col_info.spans); - return false; - } - - row_group = box_create(NULL, style, true, table->href, - table->target, NULL, NULL, c->bctx); - if (row_group == NULL) { - css_computed_style_destroy(style); - free(col_info.spans); - return false; - } - - row_group->type = BOX_TABLE_ROW_GROUP; - - if (child->prev == NULL) - table->children = row_group; - else - child->prev->next = row_group; - - row_group->prev = child->prev; - - while (child != NULL && ( - child->type == BOX_BLOCK || - child->type == BOX_INLINE_CONTAINER || - child->type == BOX_TABLE || - child->type == BOX_TABLE_ROW || - child->type == BOX_TABLE_CELL)) { - box_add_child(row_group, child); - - next_child = child->next; - child->next = NULL; - child = next_child; - } - - assert(row_group->last != NULL); - - row_group->last->next = NULL; - row_group->next = next_child = child; - if (row_group->next != NULL) - row_group->next->prev = row_group; - else - table->last = row_group; - row_group->parent = table; - - if (box_normalise_table_row_group(row_group, root, - &col_info, c) == false) { - free(col_info.spans); - return false; - } - break; - case BOX_INLINE: - case BOX_INLINE_END: - case BOX_INLINE_BLOCK: - case BOX_FLOAT_LEFT: - case BOX_FLOAT_RIGHT: - case BOX_BR: - case BOX_TEXT: - /* should have been wrapped in inline - container by convert_xml_to_box() */ - assert(0); - break; - default: - fprintf(stderr, "%i\n", child->type); - assert(0); - } - } - - table->columns = col_info.num_columns; - table->rows = col_info.num_rows; - - if (table->children == NULL) { - struct box *row; - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, - "table->children == 0, creating implied row"); -#endif - - assert(table->style != NULL); - - ctx.ctx = c->select_ctx; - ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); - ctx.base_url = c->base_url; - ctx.universal = c->universal; - - style = nscss_get_blank_style(&ctx, table->style); - if (style == NULL) { - free(col_info.spans); - return false; - } - - row_group = box_create(NULL, style, true, table->href, - table->target, NULL, NULL, c->bctx); - if (row_group == NULL) { - css_computed_style_destroy(style); - free(col_info.spans); - return false; - } - row_group->type = BOX_TABLE_ROW_GROUP; - - style = nscss_get_blank_style(&ctx, row_group->style); - if (style == NULL) { - box_free(row_group); - free(col_info.spans); - return false; - } - - row = box_create(NULL, style, true, row_group->href, - row_group->target, NULL, NULL, c->bctx); - if (row == NULL) { - css_computed_style_destroy(style); - box_free(row_group); - free(col_info.spans); - return false; - } - row->type = BOX_TABLE_ROW; - - row->parent = row_group; - row_group->children = row_group->last = row; - - row_group->parent = table; - table->children = table->last = row_group; - - table->rows = 1; - } - - if (box_normalise_table_spans(table, root, col_info.spans, c) == false) { - free(col_info.spans); - return false; - } - - free(col_info.spans); - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "table %p done", table); -#endif - - return true; -} - - -/** - * Normalise table cell column/row counts for colspan/rowspan = 0. - * Additionally, generate empty cells. - * - * \param table Table to process - * \param root root box of document - * \param spans Array of length table->columns for use in empty cell detection - * \param c Content containing table - * \return True on success, false on memory exhaustion. - */ - -bool box_normalise_table_spans( - struct box *table, - const struct box *root, - struct span_info *spans, - html_content *c) -{ - struct box *table_row_group; - struct box *table_row; - struct box *table_cell; - unsigned int rows_left = table->rows; - unsigned int group_rows_left; - unsigned int col; - nscss_select_ctx ctx; - - ctx.root_style = root->style; - - /* Clear span data */ - memset(spans, 0, table->columns * sizeof(struct span_info)); - - /* Scan table, filling in width and height of table cells with - * colspan = 0 and rowspan = 0. Also generate empty cells */ - for (table_row_group = table->children; - table_row_group != NULL; - table_row_group = table_row_group->next) { - - group_rows_left = table_row_group->rows; - - for (table_row = table_row_group->children; - table_row != NULL; - table_row = table_row->next) { - - for (table_cell = table_row->children; - table_cell != NULL; - table_cell = table_cell->next) { - - /* colspan = 0 -> colspan = 1 */ - if (table_cell->columns == 0) { - table_cell->columns = 1; - } - - /* if rowspan is 0 it is expanded to - * the number of rows left in the row - * group - */ - if (table_cell->rows == 0) { - table_cell->rows = group_rows_left; - } - - /* limit rowspans within group */ - if (table_cell->rows > group_rows_left) { - table_cell->rows = group_rows_left; - } - - /* Record span information */ - for (col = table_cell->start_column; - col < table_cell->start_column + - table_cell->columns; col++) { - spans[col].row_span = table_cell->rows; - } - } - - /* Reduce span count of each column */ - for (col = 0; col < table->columns; col++) { - if (spans[col].row_span == 0) { - unsigned int start = col; - css_computed_style *style; - struct box *cell, *prev; - - /* If it's already zero, then we need - * to generate an empty cell for the - * gap in the row that spans as many - * columns as remain blank. - */ - assert(table_row->style != NULL); - - /* Find width of gap */ - while (col < table->columns && - spans[col].row_span == - 0) { - col++; - } - - ctx.ctx = c->select_ctx; - ctx.quirks = (c->quirks == - DOM_DOCUMENT_QUIRKS_MODE_FULL); - ctx.base_url = c->base_url; - ctx.universal = c->universal; - - style = nscss_get_blank_style(&ctx, - table_row->style); - if (style == NULL) - return false; - - cell = box_create(NULL, style, true, - table_row->href, - table_row->target, - NULL, NULL, c->bctx); - if (cell == NULL) { - css_computed_style_destroy( - style); - return false; - } - cell->type = BOX_TABLE_CELL; - - cell->rows = 1; - cell->columns = col - start; - cell->start_column = start; - - /* Find place to insert cell */ - for (prev = table_row->children; - prev != NULL; - prev = prev->next) { - if (prev->start_column + - prev->columns == - start) - break; - if (prev->next == NULL) - break; - } - - /* Insert it */ - if (prev == NULL) { - if (table_row->children != NULL) - table_row->children-> - prev = cell; - else - table_row->last = cell; - - cell->next = - table_row->children; - table_row->children = cell; - } else { - if (prev->next != NULL) - prev->next->prev = cell; - else - table_row->last = cell; - - cell->next = prev->next; - prev->next = cell; - cell->prev = prev; - } - cell->parent = table_row; - } else { - spans[col].row_span--; - } - } - - assert(rows_left > 0); - - rows_left--; - } - - group_rows_left--; - } - - return true; -} - - -bool box_normalise_table_row_group( - struct box *row_group, - const struct box *root, - struct columns *col_info, - html_content * c) -{ - struct box *child; - struct box *next_child; - struct box *row; - css_computed_style *style; - nscss_select_ctx ctx; - unsigned int group_row_count = 0; - - assert(row_group != 0); - assert(row_group->type == BOX_TABLE_ROW_GROUP); - - ctx.root_style = root->style; - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "row_group %p", row_group); -#endif - - for (child = row_group->children; child != NULL; child = next_child) { - next_child = child->next; - - switch (child->type) { - case BOX_TABLE_ROW: - /* ok */ - group_row_count++; - if (box_normalise_table_row(child, root, col_info, - c) == false) - return false; - break; - case BOX_BLOCK: - case BOX_INLINE_CONTAINER: - case BOX_TABLE: - case BOX_TABLE_ROW_GROUP: - case BOX_TABLE_CELL: - /* insert implied table row */ - assert(row_group->style != NULL); - - ctx.ctx = c->select_ctx; - ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); - ctx.base_url = c->base_url; - ctx.universal = c->universal; - - style = nscss_get_blank_style(&ctx, row_group->style); - if (style == NULL) - return false; - - row = box_create(NULL, style, true, row_group->href, - row_group->target, NULL, NULL, c->bctx); - if (row == NULL) { - css_computed_style_destroy(style); - return false; - } - row->type = BOX_TABLE_ROW; - - if (child->prev == NULL) - row_group->children = row; - else - child->prev->next = row; - - row->prev = child->prev; - - while (child != NULL && ( - child->type == BOX_BLOCK || - child->type == BOX_INLINE_CONTAINER || - child->type == BOX_TABLE || - child->type == BOX_TABLE_ROW_GROUP || - child->type == BOX_TABLE_CELL)) { - box_add_child(row, child); - - next_child = child->next; - child->next = NULL; - child = next_child; - } - - assert(row->last != NULL); - - row->last->next = NULL; - row->next = next_child = child; - if (row->next != NULL) - row->next->prev = row; - else - row_group->last = row; - row->parent = row_group; - - group_row_count++; - if (box_normalise_table_row(row, root, col_info, - c) == false) - return false; - break; - case BOX_INLINE: - case BOX_INLINE_END: - case BOX_INLINE_BLOCK: - case BOX_FLOAT_LEFT: - case BOX_FLOAT_RIGHT: - case BOX_BR: - case BOX_TEXT: - /* should have been wrapped in inline - container by convert_xml_to_box() */ - assert(0); - break; - default: - assert(0); - } - } - - if (row_group->children == NULL) { -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, - "row_group->children == 0, inserting implied row"); -#endif - - assert(row_group->style != NULL); - - ctx.ctx = c->select_ctx; - ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); - ctx.base_url = c->base_url; - ctx.universal = c->universal; - - style = nscss_get_blank_style(&ctx, row_group->style); - if (style == NULL) { - return false; - } - - row = box_create(NULL, style, true, row_group->href, - row_group->target, NULL, NULL, c->bctx); - if (row == NULL) { - css_computed_style_destroy(style); - return false; - } - row->type = BOX_TABLE_ROW; - - row->parent = row_group; - row_group->children = row_group->last = row; - - group_row_count = 1; - - /* Keep table's row count in sync */ - col_info->num_rows++; - } - - row_group->rows = group_row_count; - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "row_group %p done", row_group); -#endif - - return true; -} - - -bool box_normalise_table_row( - struct box *row, - const struct box *root, - struct columns *col_info, - html_content * c) -{ - struct box *child; - struct box *next_child; - struct box *cell = NULL; - css_computed_style *style; - unsigned int i; - nscss_select_ctx ctx; - - assert(row != NULL); - assert(row->type == BOX_TABLE_ROW); - - ctx.root_style = root->style; - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "row %p", row); -#endif - - for (child = row->children; child != NULL; child = next_child) { - next_child = child->next; - - switch (child->type) { - case BOX_TABLE_CELL: - /* ok */ - if (box_normalise_block(child, root, c) == false) - return false; - cell = child; - break; - case BOX_BLOCK: - case BOX_INLINE_CONTAINER: - case BOX_TABLE: - case BOX_TABLE_ROW_GROUP: - case BOX_TABLE_ROW: - /* insert implied table cell */ - assert(row->style != NULL); - - ctx.ctx = c->select_ctx; - ctx.quirks = (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL); - ctx.base_url = c->base_url; - ctx.universal = c->universal; - - style = nscss_get_blank_style(&ctx, row->style); - if (style == NULL) - return false; - - cell = box_create(NULL, style, true, row->href, - row->target, NULL, NULL, c->bctx); - if (cell == NULL) { - css_computed_style_destroy(style); - return false; - } - cell->type = BOX_TABLE_CELL; - - if (child->prev == NULL) - row->children = cell; - else - child->prev->next = cell; - - cell->prev = child->prev; - - while (child != NULL && ( - child->type == BOX_BLOCK || - child->type == BOX_INLINE_CONTAINER || - child->type == BOX_TABLE || - child->type == BOX_TABLE_ROW_GROUP || - child->type == BOX_TABLE_ROW)) { - box_add_child(cell, child); - - next_child = child->next; - child->next = NULL; - child = next_child; - } - - assert(cell->last != NULL); - - cell->last->next = NULL; - cell->next = next_child = child; - if (cell->next != NULL) - cell->next->prev = cell; - else - row->last = cell; - cell->parent = row; - - if (box_normalise_block(cell, root, c) == false) - return false; - break; - case BOX_INLINE: - case BOX_INLINE_END: - case BOX_INLINE_BLOCK: - case BOX_FLOAT_LEFT: - case BOX_FLOAT_RIGHT: - case BOX_BR: - case BOX_TEXT: - /* should have been wrapped in inline - container by convert_xml_to_box() */ - assert(0); - break; - default: - assert(0); - } - - if (calculate_table_row(col_info, cell->columns, cell->rows, - &cell->start_column, cell) == false) - return false; - } - - - /* Update row spanning details for all columns */ - for (i = 0; i < col_info->num_columns; i++) { - if (col_info->spans[i].row_span != 0 && - col_info->spans[i].auto_row == false) { - /* This cell spans rows, and is not an auto row. - * Reduce number of rows left to span */ - col_info->spans[i].row_span--; - } - } - - /* Reset current column for next row */ - col_info->current_column = 0; - - /* Increment row counter */ - col_info->num_rows++; - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "row %p done", row); -#endif - - return true; -} - - -/** - * Compute the column index at which the current cell begins. - * Additionally, update the column record to reflect row spanning. - * - * \param col_info Column record - * \param col_span Number of columns that current cell spans - * \param row_span Number of rows that current cell spans - * \param start_column Pointer to location to receive column index - * \param cell Box for current table cell - * \return true on success, false on memory exhaustion - */ - -bool calculate_table_row(struct columns *col_info, - unsigned int col_span, unsigned int row_span, - unsigned int *start_column, struct box *cell) -{ - unsigned int cell_start_col = col_info->current_column; - unsigned int cell_end_col; - unsigned int i; - struct span_info *spans; - struct box *rg = cell->parent->parent; /* Cell's row group */ - - /* Skip columns with cells spanning from above */ - /* TODO: Need to ignore cells spanning from above that belong to - * different row group. We don't have that info here. */ - while (col_info->spans[cell_start_col].row_span != 0 && - col_info->spans[cell_start_col].rg == rg) { - cell_start_col++; - } - - /* Update current column with calculated start */ - col_info->current_column = cell_start_col; - - /* If this cell has a colspan of 0, then assume 1. - * No other browser supports colspan=0, anyway. */ - if (col_span == 0) - col_span = 1; - - cell_end_col = cell_start_col + col_span; - - if (col_info->num_columns < cell_end_col) { - /* It appears that this row has more columns than - * the maximum recorded for the table so far. - * Allocate more span records. */ - spans = realloc(col_info->spans, - sizeof *spans * (cell_end_col + 1)); - if (spans == NULL) - return false; - - col_info->spans = spans; - col_info->num_columns = cell_end_col; - - /* Mark new final column as sentinel */ - col_info->spans[cell_end_col].row_span = 0; - col_info->spans[cell_end_col].auto_row = false; - } - - /* This cell may span multiple columns. If it also wants to span - * multiple rows, temporarily assume it spans 1 row only. This will - * be fixed up in box_normalise_table_spans() */ - for (i = cell_start_col; i < cell_end_col; i++) { - col_info->spans[i].row_span = (row_span == 0) ? 1 : row_span; - col_info->spans[i].auto_row = (row_span == 0); - col_info->spans[i].rg = rg; - } - - /* Update current column with calculated end. */ - col_info->current_column = cell_end_col; - - *start_column = cell_start_col; - - return true; -} - - -bool box_normalise_inline_container( - struct box *cont, - const struct box *root, - html_content * c) -{ - struct box *child; - struct box *next_child; - - assert(cont != NULL); - assert(cont->type == BOX_INLINE_CONTAINER); - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "cont %p", cont); -#endif - - for (child = cont->children; child != NULL; child = next_child) { - next_child = child->next; - switch (child->type) { - case BOX_INLINE: - case BOX_INLINE_END: - case BOX_BR: - case BOX_TEXT: - /* ok */ - break; - case BOX_INLINE_BLOCK: - /* ok */ - if (box_normalise_block(child, root, c) == false) - return false; - break; - case BOX_FLOAT_LEFT: - case BOX_FLOAT_RIGHT: - /* ok */ - assert(child->children != NULL); - - switch (child->children->type) { - case BOX_BLOCK: - if (box_normalise_block(child->children, root, - c) == false) - return false; - break; - case BOX_TABLE: - if (box_normalise_table(child->children, root, - c) == false) - return false; - break; - default: - assert(0); - } - - if (child->children == NULL) { - /* the child has destroyed itself: remove float */ - if (child->prev == NULL) - child->parent->children = child->next; - else - child->prev->next = child->next; - if (child->next != NULL) - child->next->prev = child->prev; - else - child->parent->last = child->prev; - - box_free(child); - } - break; - case BOX_BLOCK: - case BOX_INLINE_CONTAINER: - case BOX_TABLE: - case BOX_TABLE_ROW_GROUP: - case BOX_TABLE_ROW: - case BOX_TABLE_CELL: - default: - assert(0); - } - } - -#ifdef BOX_NORMALISE_DEBUG - NSLOG(netsurf, INFO, "cont %p done", cont); -#endif - - return true; -} diff --git a/render/box_textarea.c b/render/box_textarea.c deleted file mode 100644 index a60984235..000000000 --- a/render/box_textarea.c +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright 2013 Michael Drake - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Box tree treeview box replacement (implementation). - */ - -#include - -#include "utils/config.h" -#include "utils/log.h" -#include "netsurf/keypress.h" -#include "desktop/textarea.h" - -#include "render/box_textarea.h" -#include "render/font.h" -#include "render/form_internal.h" - - -bool box_textarea_keypress(html_content *html, struct box *box, uint32_t key) -{ - struct form_control *gadget = box->gadget; - struct textarea *ta = gadget->data.text.ta; - struct form* form = box->gadget->form; - struct content *c = (struct content *) html; - - assert(ta != NULL); - - if (gadget->type != GADGET_TEXTAREA) { - switch (key) { - case NS_KEY_NL: - case NS_KEY_CR: - if (form) - form_submit(content_get_url(c), html->bw, - form, 0); - return true; - - case NS_KEY_TAB: - { - struct form_control *next_input; - /* Find next text entry field that is actually - * displayed (i.e. has an associated box) */ - for (next_input = gadget->next; - next_input && - ((next_input->type != GADGET_TEXTBOX && - next_input->type != GADGET_TEXTAREA && - next_input->type != GADGET_PASSWORD) || - !next_input->box); - next_input = next_input->next) - ; - if (!next_input) - return true; - - textarea_set_caret(ta, -1); - textarea_set_caret(next_input->data.text.ta, 0); - } - return true; - - case NS_KEY_SHIFT_TAB: - { - struct form_control *prev_input; - /* Find previous text entry field that is actually - * displayed (i.e. has an associated box) */ - for (prev_input = gadget->prev; - prev_input && - ((prev_input->type != GADGET_TEXTBOX && - prev_input->type != GADGET_TEXTAREA && - prev_input->type != GADGET_PASSWORD) || - !prev_input->box); - prev_input = prev_input->prev) - ; - if (!prev_input) - return true; - - textarea_set_caret(ta, -1); - textarea_set_caret(prev_input->data.text.ta, 0); - } - return true; - - default: - /* Pass to textarea widget */ - break; - } - } - - return textarea_keypress(ta, key); -} - - -/** - * Callback for html form textareas. - */ -static void box_textarea_callback(void *data, struct textarea_msg *msg) -{ - struct form_textarea_data *d = data; - struct form_control *gadget = d->gadget; - struct html_content *html = d->gadget->html; - struct box *box = gadget->box; - - switch (msg->type) { - case TEXTAREA_MSG_DRAG_REPORT: - if (msg->data.drag == TEXTAREA_DRAG_NONE) { - /* Textarea drag finished */ - html_drag_type drag_type = HTML_DRAG_NONE; - union html_drag_owner drag_owner; - drag_owner.no_owner = true; - - html_set_drag_type(html, drag_type, drag_owner, - NULL); - } else { - /* Textarea drag started */ - struct rect rect = { - .x0 = INT_MIN, - .y0 = INT_MIN, - .x1 = INT_MAX, - .y1 = INT_MAX - }; - union html_drag_owner drag_owner; - drag_owner.textarea = box; - - switch (msg->data.drag) { - case TEXTAREA_DRAG_SCROLLBAR: - html_set_drag_type(html, - HTML_DRAG_TEXTAREA_SCROLLBAR, - drag_owner, - &rect); - break; - - case TEXTAREA_DRAG_SELECTION: - html_set_drag_type(html, - HTML_DRAG_TEXTAREA_SELECTION, - drag_owner, - &rect); - break; - - default: - NSLOG(netsurf, INFO, - "Drag type %d not handled.", - msg->data.drag); - /* This is a logic faliure in the - * front end code so abort. - */ - assert(0); - break; - } - } - break; - - case TEXTAREA_MSG_REDRAW_REQUEST: - { - /* Request redraw of the required textarea rectangle */ - int x, y; - - if (html->reflowing == true) { - /* Can't redraw during layout, and it will - * be redrawn after layout anyway. */ - break; - } - - box_coords(box, &x, &y); - - content__request_redraw((struct content *)html, - x + msg->data.redraw.x0, - y + msg->data.redraw.y0, - msg->data.redraw.x1 - msg->data.redraw.x0, - msg->data.redraw.y1 - msg->data.redraw.y0); - } - break; - - case TEXTAREA_MSG_SELECTION_REPORT: - if (msg->data.selection.have_selection) { - /* Textarea now has a selection */ - union html_selection_owner sel_owner; - sel_owner.textarea = box; - - html_set_selection(html, HTML_SELECTION_TEXTAREA, - sel_owner, - msg->data.selection.read_only); - } else { - /* The textarea now has no selection */ - union html_selection_owner sel_owner; - sel_owner.none = true; - - html_set_selection(html, HTML_SELECTION_NONE, - sel_owner, true); - } - break; - - case TEXTAREA_MSG_CARET_UPDATE: - if (html->bw == NULL) - break; - - if (msg->data.caret.type == TEXTAREA_CARET_HIDE) { - union html_focus_owner focus_owner; - focus_owner.textarea = box; - html_set_focus(html, HTML_FOCUS_TEXTAREA, - focus_owner, true, 0, 0, 0, NULL); - } else { - union html_focus_owner focus_owner; - focus_owner.textarea = box; - html_set_focus(html, HTML_FOCUS_TEXTAREA, - focus_owner, false, - msg->data.caret.pos.x, - msg->data.caret.pos.y, - msg->data.caret.pos.height, - msg->data.caret.pos.clip); - } - break; - - case TEXTAREA_MSG_TEXT_MODIFIED: - form_gadget_update_value(gadget, - strndup(msg->data.modified.text, - msg->data.modified.len)); - break; - } -} - - -/* Exported interface, documented in box_textarea.h */ -bool box_textarea_create_textarea(html_content *html, - struct box *box, struct dom_node *node) -{ - dom_string *dom_text = NULL; - dom_exception err; - textarea_setup ta_setup; - textarea_flags ta_flags; - plot_font_style_t fstyle = { - .family = PLOT_FONT_FAMILY_SANS_SERIF, - .size = 10 * FONT_SIZE_SCALE, - .weight = 400, - .flags = FONTF_NONE, - .background = 0, - .foreground = 0, - }; - bool read_only = false; - bool disabled = false; - struct form_control *gadget = box->gadget; - const char *text; - - assert(gadget != NULL); - assert(gadget->type == GADGET_TEXTAREA || - gadget->type == GADGET_TEXTBOX || - gadget->type == GADGET_PASSWORD); - - if (gadget->type == GADGET_TEXTAREA) { - dom_html_text_area_element *textarea = - (dom_html_text_area_element *) node; - ta_flags = TEXTAREA_MULTILINE; - - err = dom_html_text_area_element_get_read_only( - textarea, &read_only); - if (err != DOM_NO_ERR) - return false; - - err = dom_html_text_area_element_get_disabled( - textarea, &disabled); - if (err != DOM_NO_ERR) - return false; - - /* Get the textarea's initial content */ - err = dom_html_text_area_element_get_value(textarea, &dom_text); - if (err != DOM_NO_ERR) - return false; - - } else { - dom_html_input_element *input = (dom_html_input_element *) node; - - err = dom_html_input_element_get_read_only( - input, &read_only); - if (err != DOM_NO_ERR) - return false; - - err = dom_html_input_element_get_disabled( - input, &disabled); - if (err != DOM_NO_ERR) - return false; - - if (gadget->type == GADGET_PASSWORD) - ta_flags = TEXTAREA_PASSWORD; - else - ta_flags = TEXTAREA_DEFAULT; - - /* Get initial text */ - err = dom_html_input_element_get_value(input, &dom_text); - if (err != DOM_NO_ERR) - return false; - } - - if (dom_text != NULL) { - text = dom_string_data(dom_text); - } else { - /* No initial text, or failed reading it; - * use a blank string */ - text = ""; - } - - if (read_only || disabled) - ta_flags |= TEXTAREA_READONLY; - - gadget->data.text.data.gadget = gadget; - - /* Reset to correct values by layout */ - ta_setup.width = 200; - ta_setup.height = 20; - ta_setup.pad_top = 4; - ta_setup.pad_right = 4; - ta_setup.pad_bottom = 4; - ta_setup.pad_left = 4; - - /* Set remaining data */ - ta_setup.border_width = 0; - ta_setup.border_col = 0x000000; - ta_setup.text = fstyle; - ta_setup.text.background = NS_TRANSPARENT; - /* Make selected text either black or white, as gives greatest contrast - * with background colour. */ - ta_setup.selected_bg = fstyle.foreground; - ta_setup.selected_text = colour_to_bw_furthest(ta_setup.selected_bg); - - /* Hand reference to dom text over to gadget */ - gadget->data.text.initial = dom_text; - - gadget->data.text.ta = textarea_create(ta_flags, &ta_setup, - box_textarea_callback, &gadget->data.text.data); - - if (gadget->data.text.ta == NULL) { - return false; - } - - if (!textarea_set_text(gadget->data.text.ta, text)) - return false; - - return true; -} - diff --git a/render/box_textarea.h b/render/box_textarea.h deleted file mode 100644 index a7a377076..000000000 --- a/render/box_textarea.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013 Michael Drake - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Box tree treeview box replacement (interface). - */ - - - -#ifndef _NETSURF_RENDER_BOX_TEXTAREA_H_ -#define _NETSURF_RENDER_BOX_TEXTAREA_H_ - - -#include "render/box.h" -#include "render/html_internal.h" - -struct dom_node; - -/** - * Create textarea widget for a form element - * - * \param html html content object - * \param box box with gadget to be given textarea widget - * \param node DOM node for form element - */ -bool box_textarea_create_textarea(html_content *html, - struct box *box, struct dom_node *node); - - -/** - * Handle form textarea keypress input - * - * \param html html content object - * \param box box with textarea widget - * \param key keypress - * \return true iff keypress handled - */ -bool box_textarea_keypress(html_content *html, struct box *box, uint32_t key); - -#endif diff --git a/render/font.c b/render/font.c deleted file mode 100644 index a769b476f..000000000 --- a/render/font.c +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2009 John-Mark Bell - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * - * Renderer internal font handling implementation. - */ - -#include "utils/nsoption.h" -#include "netsurf/plot_style.h" -#include "css/utils.h" - -#include "render/font.h" - -/** - * Map a generic CSS font family to a generic plot font family - * - * \param css Generic CSS font family - * \return Plot font family - */ -static plot_font_generic_family_t plot_font_generic_family( - enum css_font_family_e css) -{ - plot_font_generic_family_t plot; - - switch (css) { - case CSS_FONT_FAMILY_SERIF: - plot = PLOT_FONT_FAMILY_SERIF; - break; - case CSS_FONT_FAMILY_MONOSPACE: - plot = PLOT_FONT_FAMILY_MONOSPACE; - break; - case CSS_FONT_FAMILY_CURSIVE: - plot = PLOT_FONT_FAMILY_CURSIVE; - break; - case CSS_FONT_FAMILY_FANTASY: - plot = PLOT_FONT_FAMILY_FANTASY; - break; - case CSS_FONT_FAMILY_SANS_SERIF: - default: - plot = PLOT_FONT_FAMILY_SANS_SERIF; - break; - } - - return plot; -} - -/** - * Map a CSS font weight to a plot weight value - * - * \param css CSS font weight - * \return Plot weight - */ -static int plot_font_weight(enum css_font_weight_e css) -{ - int weight; - - switch (css) { - case CSS_FONT_WEIGHT_100: - weight = 100; - break; - case CSS_FONT_WEIGHT_200: - weight = 200; - break; - case CSS_FONT_WEIGHT_300: - weight = 300; - break; - case CSS_FONT_WEIGHT_400: - case CSS_FONT_WEIGHT_NORMAL: - default: - weight = 400; - break; - case CSS_FONT_WEIGHT_500: - weight = 500; - break; - case CSS_FONT_WEIGHT_600: - weight = 600; - break; - case CSS_FONT_WEIGHT_700: - case CSS_FONT_WEIGHT_BOLD: - weight = 700; - break; - case CSS_FONT_WEIGHT_800: - weight = 800; - break; - case CSS_FONT_WEIGHT_900: - weight = 900; - break; - } - - return weight; -} - -/** - * Map a CSS font style and font variant to plot font flags - * - * \param style CSS font style - * \param variant CSS font variant - * \return Computed plot flags - */ -static plot_font_flags_t plot_font_flags(enum css_font_style_e style, - enum css_font_variant_e variant) -{ - plot_font_flags_t flags = FONTF_NONE; - - if (style == CSS_FONT_STYLE_ITALIC) - flags |= FONTF_ITALIC; - else if (style == CSS_FONT_STYLE_OBLIQUE) - flags |= FONTF_OBLIQUE; - - if (variant == CSS_FONT_VARIANT_SMALL_CAPS) - flags |= FONTF_SMALLCAPS; - - return flags; -} - - -/* exported function documented in render/font.h */ -void font_plot_style_from_css( - const nscss_len_ctx *len_ctx, - const css_computed_style *css, - plot_font_style_t *fstyle) -{ - lwc_string **families; - css_fixed length = 0; - css_unit unit = CSS_UNIT_PX; - css_color col; - - fstyle->family = plot_font_generic_family( - css_computed_font_family(css, &families)); - - css_computed_font_size(css, &length, &unit); - fstyle->size = FIXTOINT(FMUL(nscss_len2pt(len_ctx, length, unit), - INTTOFIX(FONT_SIZE_SCALE))); - - /* Clamp font size to configured minimum */ - if (fstyle->size < (nsoption_int(font_min_size) * FONT_SIZE_SCALE) / 10) - fstyle->size = (nsoption_int(font_min_size) * FONT_SIZE_SCALE) / 10; - - fstyle->weight = plot_font_weight(css_computed_font_weight(css)); - fstyle->flags = plot_font_flags(css_computed_font_style(css), - css_computed_font_variant(css)); - - css_computed_color(css, &col); - fstyle->foreground = nscss_color_to_ns(col); - fstyle->background = 0; -} diff --git a/render/font.h b/render/font.h deleted file mode 100644 index 52f5a62c2..000000000 --- a/render/font.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2014 Vincent Sanders - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * - * Internal font handling interfaces. - * - * These functions provide font related services. They all work on - * UTF-8 strings with lengths given. - */ - -#ifndef _NETSURF_RENDER_FONT_H_ -#define _NETSURF_RENDER_FONT_H_ - -struct plot_font_style; - -/** - * Populate a font style using data from a computed CSS style - * - * \param len_ctx Length conversion context - * \param css Computed style to consider - * \param fstyle Font style to populate - */ -void font_plot_style_from_css( - const nscss_len_ctx *len_ctx, - const css_computed_style *css, - struct plot_font_style *fstyle); - -#endif diff --git a/render/form.c b/render/form.c deleted file mode 100644 index 432002564..000000000 --- a/render/form.c +++ /dev/null @@ -1,1895 +0,0 @@ -/* - * Copyright 2004 James Bursa - * Copyright 2003 Phil Mellor - * Copyright 2004 John Tytgat - * Copyright 2005-9 John-Mark Bell - * Copyright 2009 Paul Blokus - * Copyright 2010 Michael Drake - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * Form handling functions (implementation). - */ - -#include -#include -#include -#include -#include -#include - -#include "utils/corestrings.h" -#include "utils/log.h" -#include "utils/messages.h" -#include "utils/talloc.h" -#include "utils/url.h" -#include "utils/utf8.h" -#include "utils/ascii.h" -#include "netsurf/browser_window.h" -#include "netsurf/mouse.h" -#include "netsurf/plotters.h" -#include "netsurf/misc.h" -#include "content/fetch.h" -#include "content/hlcache.h" -#include "css/utils.h" -#include "desktop/knockout.h" -#include "desktop/scrollbar.h" -#include "desktop/textarea.h" -#include "desktop/gui_internal.h" - -#include "render/box.h" -#include "render/font.h" -#include "render/form_internal.h" -#include "render/html.h" -#include "render/html_internal.h" -#include "render/layout.h" - -#define MAX_SELECT_HEIGHT 210 -#define SELECT_LINE_SPACING 0.2 -#define SELECT_BORDER_WIDTH 1 -#define SELECT_SELECTED_COLOUR 0xDB9370 - -struct form_select_menu { - int line_height; - int width, height; - struct scrollbar *scrollbar; - int f_size; - bool scroll_capture; - select_menu_redraw_callback callback; - void *client_data; - struct content *c; -}; - -static plot_style_t plot_style_fill_selected = { - .fill_type = PLOT_OP_TYPE_SOLID, - .fill_colour = SELECT_SELECTED_COLOUR, -}; - -static plot_font_style_t plot_fstyle_entry = { - .family = PLOT_FONT_FAMILY_SANS_SERIF, - .weight = 400, - .flags = FONTF_NONE, - .background = 0xffffff, - .foreground = 0x000000, -}; - -static char *form_acceptable_charset(struct form *form); -static char *form_encode_item(const char *item, uint32_t len, const char *charset, - const char *fallback); -static void form_select_menu_clicked(struct form_control *control, - int x, int y); -static void form_select_menu_scroll_callback(void *client_data, - struct scrollbar_msg_data *scrollbar_data); - -/* exported interface documented in render/form_internal.h */ -struct form *form_new(void *node, const char *action, const char *target, - form_method method, const char *charset, - const char *doc_charset) -{ - struct form *form; - - form = calloc(1, sizeof *form); - if (!form) - return NULL; - - form->action = strdup(action != NULL ? action : ""); - if (form->action == NULL) { - free(form); - return NULL; - } - - form->target = target != NULL ? strdup(target) : NULL; - if (target != NULL && form->target == NULL) { - free(form->action); - free(form); - return NULL; - } - - form->method = method; - - form->accept_charsets = charset != NULL ? strdup(charset) : NULL; - if (charset != NULL && form->accept_charsets == NULL) { - free(form->target); - free(form->action); - free(form); - return NULL; - } - - form->document_charset = doc_charset != NULL ? strdup(doc_charset) - : NULL; - if (doc_charset && form->document_charset == NULL) { - free(form->accept_charsets); - free(form->target); - free(form->action); - free(form); - return NULL; - } - - form->node = node; - - return form; -} - - -/* exported interface documented in render/form_internal.h */ -void form_free(struct form *form) -{ - struct form_control *c, *d; - - for (c = form->controls; c != NULL; c = d) { - d = c->next; - - form_free_control(c); - } - - free(form->action); - free(form->target); - free(form->accept_charsets); - free(form->document_charset); - - free(form); -} - -/* exported interface documented in render/form_internal.h */ -struct form_control *form_new_control(void *node, form_control_type type) -{ - struct form_control *control; - - control = calloc(1, sizeof *control); - if (control == NULL) - return NULL; - - control->node = node; - control->type = type; - - return control; -} - - -/** - * Add a control to the list of controls in a form. - * - * \param form The form to add the control to - * \param control The control to add - */ -void form_add_control(struct form *form, struct form_control *control) -{ - if (form == NULL) { - return; - } - - control->form = form; - - if (form->controls != NULL) { - assert(form->last_control); - - form->last_control->next = control; - control->prev = form->last_control; - control->next = NULL; - form->last_control = control; - } else { - form->controls = form->last_control = control; - } -} - - -/** - * Free a struct form_control. - * - * \param control structure to free - */ -void form_free_control(struct form_control *control) -{ - struct form_control *c; - assert(control != NULL); - - NSLOG(netsurf, INFO, "Control:%p name:%p value:%p initial:%p", - control, control->name, control->value, control->initial_value); - free(control->name); - free(control->value); - free(control->initial_value); - - if (control->type == GADGET_SELECT) { - struct form_option *option, *next; - - for (option = control->data.select.items; option; - option = next) { - next = option->next; - NSLOG(netsurf, INFO, - "select option:%p text:%p value:%p", option, - option->text, option->value); - free(option->text); - free(option->value); - free(option); - } - if (control->data.select.menu != NULL) { - form_free_select_menu(control); - } - } - - if (control->type == GADGET_TEXTAREA || - control->type == GADGET_TEXTBOX || - control->type == GADGET_PASSWORD) { - - if (control->data.text.initial != NULL) { - dom_string_unref(control->data.text.initial); - } - - if (control->data.text.ta != NULL) { - textarea_destroy(control->data.text.ta); - } - } - - /* unlink the control from the form */ - if (control->form != NULL) { - for (c = control->form->controls; c != NULL; c = c->next) { - if (c->next == control) { - c->next = control->next; - if (control->form->last_control == control) - control->form->last_control = c; - break; - } - if (c == control) { - /* can only happen if control was first control */ - control->form->controls = control->next; - if (control->form->last_control == control) - control->form->controls = - control->form->last_control = NULL; - break; - } - } - } - - free(control); -} - - -/** - * Add an option to a form select control. - * - * \param control form control of type GADGET_SELECT - * \param value value of option, used directly (not copied) - * \param text text for option, used directly (not copied) - * \param selected this option is selected - * \param node the DOM node this option is associated with - * \return true on success, false on memory exhaustion - */ -bool form_add_option(struct form_control *control, char *value, char *text, - bool selected, void *node) -{ - struct form_option *option; - - assert(control); - assert(control->type == GADGET_SELECT); - - option = calloc(1, sizeof *option); - if (!option) - return false; - - option->value = value; - option->text = text; - - /* add to linked list */ - if (control->data.select.items == 0) - control->data.select.items = option; - else - control->data.select.last_item->next = option; - control->data.select.last_item = option; - - /* set selected */ - if (selected && (control->data.select.num_selected == 0 || - control->data.select.multiple)) { - option->selected = option->initial_selected = true; - control->data.select.num_selected++; - control->data.select.current = option; - } - - control->data.select.num_items++; - - option->node = node; - - return true; -} - - -/* exported interface documented in render/form_internal.h */ -bool form_successful_controls_dom(struct form *_form, - struct form_control *_submit_button, - struct fetch_multipart_data **successful_controls) -{ - dom_html_form_element *form = _form->node; - dom_html_element *submit_button = (_submit_button != NULL) ? _submit_button->node : NULL; - dom_html_collection *form_elements = NULL; - dom_html_options_collection *options = NULL; - dom_node *form_element = NULL, *option_element = NULL; - dom_exception err; - dom_string *nodename = NULL, *inputname = NULL, *inputvalue = NULL, *inputtype = NULL; - struct fetch_multipart_data sentinel, *last_success, *success_new; - bool had_submit = false, element_disabled, checked; - char *charset, *rawfile_temp = NULL, *basename; - uint32_t index, element_count; - struct image_input_coords *coords; - - last_success = &sentinel; - sentinel.next = NULL; - - /** \todo Replace this call with something DOMish */ - charset = form_acceptable_charset(_form); - if (charset == NULL) { - NSLOG(netsurf, INFO, "failed to find charset"); - return false; - } - -#define ENCODE_ITEM(i) (((i) == NULL) ? ( \ - form_encode_item("", 0, charset, _form->document_charset) \ - ):( \ - form_encode_item(dom_string_data(i), dom_string_byte_length(i), \ - charset, _form->document_charset) \ - )) - - err = dom_html_form_element_get_elements(form, &form_elements); - - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, "Could not get form elements"); - goto dom_no_memory; - } - - - err = dom_html_collection_get_length(form_elements, &element_count); - - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, "Could not get form element count"); - goto dom_no_memory; - } - - for (index = 0; index < element_count; index++) { - if (form_element != NULL) { - dom_node_unref(form_element); - form_element = NULL; - } - if (nodename != NULL) { - dom_string_unref(nodename); - nodename = NULL; - } - if (inputname != NULL) { - dom_string_unref(inputname); - inputname = NULL; - } - if (inputvalue != NULL) { - dom_string_unref(inputvalue); - inputvalue = NULL; - } - if (inputtype != NULL) { - dom_string_unref(inputtype); - inputtype = NULL; - } - if (options != NULL) { - dom_html_options_collection_unref(options); - options = NULL; - } - err = dom_html_collection_item(form_elements, - index, &form_element); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not retrieve form element %d", index); - goto dom_no_memory; - } - - /* Form elements are one of: - * HTMLButtonElement - * HTMLInputElement - * HTMLTextAreaElement - * HTMLSelectElement - */ - err = dom_node_get_node_name(form_element, &nodename); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, "Could not get node name"); - goto dom_no_memory; - } - - if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) { - err = dom_html_text_area_element_get_disabled( - (dom_html_text_area_element *)form_element, - &element_disabled); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get text area disabled property"); - goto dom_no_memory; - } - err = dom_html_text_area_element_get_name( - (dom_html_text_area_element *)form_element, - &inputname); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get text area name property"); - goto dom_no_memory; - } - } else if (dom_string_isequal(nodename, corestring_dom_SELECT)) { - err = dom_html_select_element_get_disabled( - (dom_html_select_element *)form_element, - &element_disabled); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get select disabled property"); - goto dom_no_memory; - } - err = dom_html_select_element_get_name( - (dom_html_select_element *)form_element, - &inputname); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get select name property"); - goto dom_no_memory; - } - } else if (dom_string_isequal(nodename, corestring_dom_INPUT)) { - err = dom_html_input_element_get_disabled( - (dom_html_input_element *)form_element, - &element_disabled); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get input disabled property"); - goto dom_no_memory; - } - err = dom_html_input_element_get_name( - (dom_html_input_element *)form_element, - &inputname); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get input name property"); - goto dom_no_memory; - } - } else if (dom_string_isequal(nodename, corestring_dom_BUTTON)) { - err = dom_html_button_element_get_disabled( - (dom_html_button_element *)form_element, - &element_disabled); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get button disabled property"); - goto dom_no_memory; - } - err = dom_html_button_element_get_name( - (dom_html_button_element *)form_element, - &inputname); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get button name property"); - goto dom_no_memory; - } - } else { - /* Unknown element type came through! */ - NSLOG(netsurf, INFO, "Unknown element type: %*s", - (int)dom_string_byte_length(nodename), - dom_string_data(nodename)); - goto dom_no_memory; - } - if (element_disabled) - continue; - if (inputname == NULL) - continue; - - if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) { - err = dom_html_text_area_element_get_value( - (dom_html_text_area_element *)form_element, - &inputvalue); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get text area content"); - goto dom_no_memory; - } - } else if (dom_string_isequal(nodename, corestring_dom_SELECT)) { - uint32_t options_count, option_index; - err = dom_html_select_element_get_options( - (dom_html_select_element *)form_element, - &options); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get select options collection"); - goto dom_no_memory; - } - err = dom_html_options_collection_get_length( - options, &options_count); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get select options collection length"); - goto dom_no_memory; - } - for(option_index = 0; option_index < options_count; - ++option_index) { - bool selected; - if (option_element != NULL) { - dom_node_unref(option_element); - option_element = NULL; - } - if (inputvalue != NULL) { - dom_string_unref(inputvalue); - inputvalue = NULL; - } - err = dom_html_options_collection_item( - options, option_index, &option_element); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get options item %d", - option_index); - goto dom_no_memory; - } - err = dom_html_option_element_get_selected( - (dom_html_option_element *)option_element, - &selected); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get option selected property"); - goto dom_no_memory; - } - if (!selected) - continue; - err = dom_html_option_element_get_value( - (dom_html_option_element *)option_element, - &inputvalue); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get option value"); - goto dom_no_memory; - } - - success_new = calloc(1, sizeof(*success_new)); - if (success_new == NULL) { - NSLOG(netsurf, INFO, - "Could not allocate data for option"); - goto dom_no_memory; - } - - last_success->next = success_new; - last_success = success_new; - - success_new->name = ENCODE_ITEM(inputname); - if (success_new->name == NULL) { - NSLOG(netsurf, INFO, - "Could not encode name for option"); - goto dom_no_memory; - } - success_new->value = ENCODE_ITEM(inputvalue); - if (success_new->value == NULL) { - NSLOG(netsurf, INFO, - "Could not encode value for option"); - goto dom_no_memory; - } - } - continue; - } else if (dom_string_isequal(nodename, corestring_dom_BUTTON)) { - err = dom_html_button_element_get_type( - (dom_html_button_element *) form_element, - &inputtype); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get button element type"); - goto dom_no_memory; - } - if (dom_string_caseless_isequal( - inputtype, corestring_dom_submit)) { - - if (submit_button == NULL && !had_submit) { - /* no button used, and first submit - * node found, so use it - */ - had_submit = true; - } else if ((dom_node *)submit_button != - (dom_node *)form_element) { - continue; - } - - err = dom_html_button_element_get_value( - (dom_html_button_element *)form_element, - &inputvalue); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get submit button value"); - goto dom_no_memory; - } - /* Drop through to report successful button */ - } else { - continue; - } - } else if (dom_string_isequal(nodename, corestring_dom_INPUT)) { - /* Things to consider here */ - /* Buttons -- only if the successful control */ - /* radio and checkbox -- only if selected */ - /* file -- also get the rawfile */ - /* everything else -- just value */ - err = dom_html_input_element_get_type( - (dom_html_input_element *) form_element, - &inputtype); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get input element type"); - goto dom_no_memory; - } - if (dom_string_caseless_isequal( - inputtype, corestring_dom_submit)) { - - if (submit_button == NULL && !had_submit) { - /* no button used, and first submit - * node found, so use it - */ - had_submit = true; - } else if ((dom_node *)submit_button != - (dom_node *)form_element) { - continue; - } - - err = dom_html_input_element_get_value( - (dom_html_input_element *)form_element, - &inputvalue); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get submit button value"); - goto dom_no_memory; - } - /* Drop through to report the successful button */ - } else if (dom_string_caseless_isequal( - inputtype, corestring_dom_image)) { - /* We *ONLY* use an image input if it was the - * thing which activated us - */ - if ((dom_node *)submit_button != - (dom_node *)form_element) - continue; - - err = dom_node_get_user_data( - form_element, - corestring_dom___ns_key_image_coords_node_data, - &coords); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get image XY data"); - goto dom_no_memory; - } - if (coords == NULL) { - NSLOG(netsurf, INFO, - "No XY data on the image input"); - goto dom_no_memory; - } - - basename = ENCODE_ITEM(inputname); - - success_new = calloc(1, sizeof(*success_new)); - if (success_new == NULL) { - free(basename); - NSLOG(netsurf, INFO, - "Could not allocate data for image.x"); - goto dom_no_memory; - } - - last_success->next = success_new; - last_success = success_new; - - success_new->name = malloc(strlen(basename) + 3); - if (success_new->name == NULL) { - free(basename); - NSLOG(netsurf, INFO, - "Could not allocate name for image.x"); - goto dom_no_memory; - } - success_new->value = malloc(20); - if (success_new->value == NULL) { - free(basename); - NSLOG(netsurf, INFO, - "Could not allocate value for image.x"); - goto dom_no_memory; - } - sprintf(success_new->name, "%s.x", basename); - sprintf(success_new->value, "%d", coords->x); - - success_new = calloc(1, sizeof(*success_new)); - if (success_new == NULL) { - free(basename); - NSLOG(netsurf, INFO, - "Could not allocate data for image.y"); - goto dom_no_memory; - } - - last_success->next = success_new; - last_success = success_new; - - success_new->name = malloc(strlen(basename) + 3); - if (success_new->name == NULL) { - free(basename); - NSLOG(netsurf, INFO, - "Could not allocate name for image.y"); - goto dom_no_memory; - } - success_new->value = malloc(20); - if (success_new->value == NULL) { - free(basename); - NSLOG(netsurf, INFO, - "Could not allocate value for image.y"); - goto dom_no_memory; - } - sprintf(success_new->name, "%s.y", basename); - sprintf(success_new->value, "%d", coords->y); - free(basename); - continue; - } else if (dom_string_caseless_isequal( - inputtype, corestring_dom_radio) || - dom_string_caseless_isequal( - inputtype, corestring_dom_checkbox)) { - err = dom_html_input_element_get_checked( - (dom_html_input_element *)form_element, - &checked); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get input element checked"); - goto dom_no_memory; - } - if (!checked) - continue; - err = dom_html_input_element_get_value( - (dom_html_input_element *)form_element, - &inputvalue); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get input element value"); - goto dom_no_memory; - } - if (inputvalue == NULL) { - inputvalue = dom_string_ref( - corestring_dom_on); - } - /* Fall through to simple allocation */ - } else if (dom_string_caseless_isequal( - inputtype, corestring_dom_file)) { - - err = dom_html_input_element_get_value( - (dom_html_input_element *)form_element, - &inputvalue); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get file value"); - goto dom_no_memory; - } - err = dom_node_get_user_data( - form_element, - corestring_dom___ns_key_file_name_node_data, - &rawfile_temp); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get file rawname"); - goto dom_no_memory; - } - rawfile_temp = strdup(rawfile_temp != NULL ? - rawfile_temp : - ""); - if (rawfile_temp == NULL) { - NSLOG(netsurf, INFO, - "Could not copy file rawname"); - goto dom_no_memory; - } - /* Fall out to the allocation */ - } else if (dom_string_caseless_isequal( - inputtype, corestring_dom_reset) || - dom_string_caseless_isequal( - inputtype, corestring_dom_button)) { - /* Skip these */ - NSLOG(netsurf, INFO, - "Skipping RESET and BUTTON"); - continue; - } else { - /* Everything else is treated as text values */ - err = dom_html_input_element_get_value( - (dom_html_input_element *)form_element, - &inputvalue); - if (err != DOM_NO_ERR) { - NSLOG(netsurf, INFO, - "Could not get input value"); - goto dom_no_memory; - } - /* Fall out to the allocation */ - } - } - - success_new = calloc(1, sizeof(*success_new)); - if (success_new == NULL) { - NSLOG(netsurf, INFO, - "Could not allocate data for generic"); - goto dom_no_memory; - } - - last_success->next = success_new; - last_success = success_new; - - success_new->name = ENCODE_ITEM(inputname); - if (success_new->name == NULL) { - NSLOG(netsurf, INFO, - "Could not encode name for generic"); - goto dom_no_memory; - } - success_new->value = ENCODE_ITEM(inputvalue); - if (success_new->value == NULL) { - NSLOG(netsurf, INFO, - "Could not encode value for generic"); - goto dom_no_memory; - } - if (rawfile_temp != NULL) { - success_new->file = true; - success_new->rawfile = rawfile_temp; - rawfile_temp = NULL; - } - } - - free(charset); - - if (form_element != NULL) { - dom_node_unref(form_element); - } - - if (form_elements != NULL) { - dom_html_collection_unref(form_elements); - } - - if (nodename != NULL) { - dom_string_unref(nodename); - } - - if (inputname != NULL) { - dom_string_unref(inputname); - } - - if (inputvalue != NULL) { - dom_string_unref(inputvalue); - } - - if (options != NULL) { - dom_html_options_collection_unref(options); - } - - if (option_element != NULL) { - dom_node_unref(option_element); - } - - if (inputtype != NULL) { - dom_string_unref(inputtype); - } - - if (rawfile_temp != NULL) { - free(rawfile_temp); - } - - *successful_controls = sentinel.next; - - return true; - -dom_no_memory: - free(charset); - fetch_multipart_data_destroy(sentinel.next); - - if (form_elements != NULL) - dom_html_collection_unref(form_elements); - if (form_element != NULL) - dom_node_unref(form_element); - if (nodename != NULL) - dom_string_unref(nodename); - if (inputname != NULL) - dom_string_unref(inputname); - if (inputvalue != NULL) - dom_string_unref(inputvalue); - if (options != NULL) - dom_html_options_collection_unref(options); - if (option_element != NULL) - dom_node_unref(option_element); - if (inputtype != NULL) - dom_string_unref(inputtype); - if (rawfile_temp != NULL) - free(rawfile_temp); - - return false; -} -#undef ENCODE_ITEM - -/** - * Encode controls using application/x-www-form-urlencoded. - * - * \param form form to which successful controls relate - * \param control linked list of fetch_multipart_data - * \param query_string iff true add '?' to the start of returned data - * \return URL-encoded form, or 0 on memory exhaustion - */ - -static char *form_url_encode(struct form *form, - struct fetch_multipart_data *control, - bool query_string) -{ - char *name, *value; - char *s, *s2; - unsigned int len, len1, len_init; - nserror url_err; - - if (query_string) - s = malloc(2); - else - s = malloc(1); - - if (s == NULL) - return NULL; - - if (query_string) { - s[0] = '?'; - s[1] = '\0'; - len_init = len = 1; - } else { - s[0] = '\0'; - len_init = len = 0; - } - - for (; control; control = control->next) { - url_err = url_escape(control->name, true, NULL, &name); - if (url_err == NSERROR_NOMEM) { - free(s); - return NULL; - } - - assert(url_err == NSERROR_OK); - - url_err = url_escape(control->value, true, NULL, &value); - if (url_err == NSERROR_NOMEM) { - free(name); - free(s); - return NULL; - } - - assert(url_err == NSERROR_OK); - - len1 = len + strlen(name) + strlen(value) + 2; - s2 = realloc(s, len1 + 1); - if (!s2) { - free(value); - free(name); - free(s); - return NULL; - } - s = s2; - sprintf(s + len, "%s=%s&", name, value); - len = len1; - free(name); - free(value); - } - - if (len > len_init) { - /* Replace trailing '&' */ - s[len - 1] = '\0'; - } - return s; -} - -/** - * Find an acceptable character set encoding with which to submit the form - * - * \param form The form - * \return Pointer to charset name (on heap, caller should free) or NULL - */ -char *form_acceptable_charset(struct form *form) -{ - char *temp, *c; - - if (!form) - return NULL; - - if (!form->accept_charsets) { - /* no accept-charsets attribute for this form */ - if (form->document_charset) - /* document charset present, so use it */ - return strdup(form->document_charset); - else - /* no document charset, so default to 8859-1 */ - return strdup("ISO-8859-1"); - } - - /* make temporary copy of accept-charsets attribute */ - temp = strdup(form->accept_charsets); - if (!temp) - return NULL; - - /* make it upper case */ - for (c = temp; *c; c++) { - *c = ascii_to_upper(*c); - } - - /* is UTF-8 specified? */ - c = strstr(temp, "UTF-8"); - if (c) { - free(temp); - return strdup("UTF-8"); - } - - /* dispense with temporary copy */ - free(temp); - - /* according to RFC2070, the accept-charsets attribute of the - * form element contains a space and/or comma separated list */ - c = form->accept_charsets; - - /** \todo an improvement would be to choose an encoding - * acceptable to the server which covers as much of the input - * values as possible. Additionally, we need to handle the - * case where none of the acceptable encodings cover all the - * textual input values. For now, we just extract the first - * element of the charset list - */ - while (*c && !ascii_is_space(*c)) { - if (*c == ',') - break; - c++; - } - - return strndup(form->accept_charsets, c - form->accept_charsets); -} - -/** - * Convert a string from UTF-8 to the specified charset - * As a final fallback, this will attempt to convert to ISO-8859-1. - * - * \todo Return charset used? - * - * \param item String to convert - * \param len Length of string to convert - * \param charset Destination charset - * \param fallback Fallback charset (may be NULL), - * used iff converting to charset fails - * \return Pointer to converted string (on heap, caller frees), or NULL - */ -char *form_encode_item(const char *item, uint32_t len, const char *charset, - const char *fallback) -{ - nserror err; - char *ret = NULL; - char cset[256]; - - if (!item || !charset) - return NULL; - - snprintf(cset, sizeof cset, "%s//TRANSLIT", charset); - - err = utf8_to_enc(item, cset, 0, &ret); - if (err == NSERROR_BAD_ENCODING) { - /* charset not understood, try without transliteration */ - snprintf(cset, sizeof cset, "%s", charset); - err = utf8_to_enc(item, cset, len, &ret); - - if (err == NSERROR_BAD_ENCODING) { - /* nope, try fallback charset (if any) */ - if (fallback) { - snprintf(cset, sizeof cset, - "%s//TRANSLIT", fallback); - err = utf8_to_enc(item, cset, 0, &ret); - - if (err == NSERROR_BAD_ENCODING) { - /* and without transliteration */ - snprintf(cset, sizeof cset, - "%s", fallback); - err = utf8_to_enc(item, cset, 0, &ret); - } - } - - if (err == NSERROR_BAD_ENCODING) { - /* that also failed, use 8859-1 */ - err = utf8_to_enc(item, "ISO-8859-1//TRANSLIT", - 0, &ret); - if (err == NSERROR_BAD_ENCODING) { - /* and without transliteration */ - err = utf8_to_enc(item, "ISO-8859-1", - 0, &ret); - } - } - } - } - if (err == NSERROR_NOMEM) { - return NULL; - } - - return ret; -} - -/* exported interface documented in render/form_internal.h */ -bool form_open_select_menu(void *client_data, - struct form_control *control, - select_menu_redraw_callback callback, - struct content *c) -{ - int line_height_with_spacing; - struct box *box; - plot_font_style_t fstyle; - int total_height; - struct form_select_menu *menu; - html_content *html = (html_content *)c; - - - /* if the menu is opened for the first time */ - if (control->data.select.menu == NULL) { - - menu = calloc(1, sizeof (struct form_select_menu)); - if (menu == NULL) { - guit->misc->warning("NoMemory", 0); - return false; - } - - control->data.select.menu = menu; - - box = control->box; - - menu->width = box->width + - box->border[RIGHT].width + - box->border[LEFT].width + - box->padding[RIGHT] + box->padding[LEFT]; - - font_plot_style_from_css(&html->len_ctx, control->box->style, - &fstyle); - menu->f_size = fstyle.size; - - menu->line_height = FIXTOINT(FDIV((FMUL(FLTTOFIX(1.2), - FMUL(nscss_screen_dpi, - INTTOFIX(fstyle.size / FONT_SIZE_SCALE)))), - F_72)); - - line_height_with_spacing = menu->line_height + - menu->line_height * - SELECT_LINE_SPACING; - - total_height = control->data.select.num_items * - line_height_with_spacing; - menu->height = total_height; - - if (menu->height > MAX_SELECT_HEIGHT) { - - menu->height = MAX_SELECT_HEIGHT; - } - menu->client_data = client_data; - menu->callback = callback; - if (scrollbar_create(false, - menu->height, - total_height, - menu->height, - control, - form_select_menu_scroll_callback, - &(menu->scrollbar)) != NSERROR_OK) { - free(menu); - return false; - } - menu->c = c; - } - else menu = control->data.select.menu; - - menu->callback(client_data, 0, 0, menu->width, menu->height); - - return true; -} - - -/* exported interface documented in render/form_internal.h */ -void form_free_select_menu(struct form_control *control) -{ - if (control->data.select.menu->scrollbar != NULL) - scrollbar_destroy(control->data.select.menu->scrollbar); - free(control->data.select.menu); - control->data.select.menu = NULL; -} - - -/* exported interface documented in render/form_internal.h */ -bool form_redraw_select_menu(struct form_control *control, int x, int y, - float scale, const struct rect *clip, - const struct redraw_context *ctx) -{ - struct box *box; - struct form_select_menu *menu = control->data.select.menu; - struct form_option *option; - int line_height, line_height_with_spacing; - int width, height; - int x0, y0, x1, scrollbar_x, y1, y2, y3; - int item_y; - int text_pos_offset, text_x; - int scrollbar_width = SCROLLBAR_WIDTH; - int i; - int scroll; - int x_cp, y_cp; - struct rect r; - struct rect rect; - nserror res; - - box = control->box; - - x_cp = x; - y_cp = y; - width = menu->width; - height = menu->height; - line_height = menu->line_height; - - line_height_with_spacing = line_height + - line_height * SELECT_LINE_SPACING; - scroll = scrollbar_get_offset(menu->scrollbar); - - if (scale != 1.0) { - x *= scale; - y *= scale; - width *= scale; - height *= scale; - scrollbar_width *= scale; - - i = scroll / line_height_with_spacing; - scroll -= i * line_height_with_spacing; - line_height *= scale; - line_height_with_spacing *= scale; - scroll *= scale; - scroll += i * line_height_with_spacing; - } - - - x0 = x; - y0 = y; - x1 = x + width - 1; - y1 = y + height - 1; - scrollbar_x = x1 - scrollbar_width; - - r.x0 = x0; - r.y0 = y0; - r.x1 = x1 + 1; - r.y1 = y1 + 1; - res = ctx->plot->clip(ctx, &r); - if (res != NSERROR_OK) { - return false; - } - - rect.x0 = x0; - rect.y0 = y0; - rect.x1 = x1; - rect.y1 = y1; - res = ctx->plot->rectangle(ctx, plot_style_stroke_darkwbasec, &rect); - if (res != NSERROR_OK) { - return false; - } - - x0 = x0 + SELECT_BORDER_WIDTH; - y0 = y0 + SELECT_BORDER_WIDTH; - x1 = x1 - SELECT_BORDER_WIDTH; - y1 = y1 - SELECT_BORDER_WIDTH; - height = height - 2 * SELECT_BORDER_WIDTH; - - r.x0 = x0; - r.y0 = y0; - r.x1 = x1 + 1; - r.y1 = y1 + 1; - res = ctx->plot->clip(ctx, &r); - if (res != NSERROR_OK) { - return false; - } - - res = ctx->plot->rectangle(ctx, plot_style_fill_lightwbasec, &r); - if (res != NSERROR_OK) { - return false; - } - - option = control->data.select.items; - item_y = line_height_with_spacing; - - while (item_y < scroll) { - option = option->next; - item_y += line_height_with_spacing; - } - item_y -= line_height_with_spacing; - text_pos_offset = y - scroll + - (int) (line_height * (0.75 + SELECT_LINE_SPACING)); - text_x = x + (box->border[LEFT].width + box->padding[LEFT]) * scale; - - plot_fstyle_entry.size = menu->f_size; - - while (option && item_y - scroll < height) { - - if (option->selected) { - y2 = y + item_y - scroll; - y3 = y + item_y + line_height_with_spacing - scroll; - - rect.x0 = x0; - rect.y0 = y0 > y2 ? y0 : y2; - rect.x1 = scrollbar_x + 1; - rect.y1 = y3 < y1 + 1 ? y3 : y1 + 1; - res = ctx->plot->rectangle(ctx, &plot_style_fill_selected, &rect); - if (res != NSERROR_OK) { - return false; - } - } - - y2 = text_pos_offset + item_y; - res = ctx->plot->text(ctx, - &plot_fstyle_entry, - text_x, y2, - option->text, strlen(option->text)); - if (res != NSERROR_OK) { - return false; - } - - item_y += line_height_with_spacing; - option = option->next; - } - - res = scrollbar_redraw(menu->scrollbar, - x_cp + menu->width - SCROLLBAR_WIDTH, - y_cp, - clip, scale, ctx); - if (res != NSERROR_OK) { - return false; - } - - return true; -} - -/** - * Check whether a clipping rectangle is completely contained in the - * select menu. - * - * \param control the select menu to check the clipping rectangle for - * \param scale the current browser window scale - * \param clip the clipping rectangle - * \return true if inside false otherwise - */ -bool form_clip_inside_select_menu(struct form_control *control, float scale, - const struct rect *clip) -{ - struct form_select_menu *menu = control->data.select.menu; - int width, height; - - - width = menu->width; - height = menu->height; - - if (scale != 1.0) { - width *= scale; - height *= scale; - } - - if (clip->x0 >= 0 && clip->x1 <= width && - clip->y0 >= 0 && clip->y1 <= height) - return true; - - return false; -} - - -/** - * Process a selection from a form select menu. - * - * \param html The html content handle for the form - * \param control form control with menu - * \param item index of item selected from the menu - * \return NSERROR_OK or appropriate error code. - */ -static nserror form__select_process_selection(html_content *html, - struct form_control *control, int item) -{ - struct box *inline_box; - struct form_option *o; - int count; - nserror ret = NSERROR_OK; - - assert(control != NULL); - assert(html != NULL); - - /** \todo Even though the form code is effectively part of the html - * content handler, poking around inside contents is not good - */ - - inline_box = control->box->children->children; - - for (count = 0, o = control->data.select.items; - o != NULL; - count++, o = o->next) { - if (!control->data.select.multiple && o->selected) { - o->selected = false; - dom_html_option_element_set_selected(o->node, false); - } - - if (count == item) { - if (control->data.select.multiple) { - if (o->selected) { - o->selected = false; - dom_html_option_element_set_selected( - o->node, false); - control->data.select.num_selected--; - } else { - o->selected = true; - dom_html_option_element_set_selected( - o->node, true); - control->data.select.num_selected++; - } - } else { - dom_html_option_element_set_selected( - o->node, true); - o->selected = true; - } - } - - if (o->selected) { - control->data.select.current = o; - } - } - - talloc_free(inline_box->text); - inline_box->text = 0; - - if (control->data.select.num_selected == 0) { - inline_box->text = talloc_strdup(html->bctx, - messages_get("Form_None")); - } else if (control->data.select.num_selected == 1) { - inline_box->text = talloc_strdup(html->bctx, - control->data.select.current->text); - } else { - inline_box->text = talloc_strdup(html->bctx, - messages_get("Form_Many")); - } - - if (!inline_box->text) { - ret = NSERROR_NOMEM; - inline_box->length = 0; - } else { - inline_box->length = strlen(inline_box->text); - } - inline_box->width = control->box->width; - - html__redraw_a_box(html, control->box); - - return ret; -} - -/* exported interface documented in netsurf/form.h */ -nserror form_select_process_selection(struct form_control *control, int item) -{ - assert(control != NULL); - - return form__select_process_selection(control->html, control, item); -} - -/* exported interface documented in netsurf/form.h */ -struct form_option * -form_select_get_option(struct form_control *control, int item) -{ - struct form_option *opt; - - opt = control->data.select.items; - while ((opt != NULL) && (item > 0)) { - opt = opt->next; - item--; - } - return opt; -} - -/* exported interface documented in netsurf/form.h */ -char *form_control_get_name(struct form_control *control) -{ - return control->name; -} - -/* exported interface documented in netsurf/form.h */ -nserror form_control_bounding_rect(struct form_control *control, struct rect *r) -{ - box_bounds( control->box, r ); - return NSERROR_OK; -} - - -/** - * Handle a click on the area of the currently opened select menu. - * - * \param control the select menu which received the click - * \param x X coordinate of click - * \param y Y coordinate of click - */ -void form_select_menu_clicked(struct form_control *control, int x, int y) -{ - struct form_select_menu *menu = control->data.select.menu; - struct form_option *option; - html_content *html = (html_content *)menu->c; - int line_height, line_height_with_spacing; - int item_bottom_y; - int scroll, i; - - scroll = scrollbar_get_offset(menu->scrollbar); - - line_height = menu->line_height; - line_height_with_spacing = line_height + - line_height * SELECT_LINE_SPACING; - - option = control->data.select.items; - item_bottom_y = line_height_with_spacing; - i = 0; - while (option && item_bottom_y < scroll + y) { - item_bottom_y += line_height_with_spacing; - option = option->next; - i++; - } - - if (option != NULL) { - form__select_process_selection(html, control, i); - } - - menu->callback(menu->client_data, 0, 0, menu->width, menu->height); -} - -/** - * Handle mouse action for the currently opened select menu. - * - * \param control the select menu which received the mouse action - * \param mouse current mouse state - * \param x X coordinate of click - * \param y Y coordinate of click - * \return text for the browser status bar or NULL if the menu has - * to be closed - */ -const char *form_select_mouse_action(struct form_control *control, - browser_mouse_state mouse, int x, int y) -{ - struct form_select_menu *menu = control->data.select.menu; - int x0, y0, x1, y1, scrollbar_x; - const char *status = NULL; - bool multiple = control->data.select.multiple; - - x0 = 0; - y0 = 0; - x1 = menu->width; - y1 = menu->height; - scrollbar_x = x1 - SCROLLBAR_WIDTH; - - if (menu->scroll_capture || - (x > scrollbar_x && x < x1 && y > y0 && y < y1)) { - /* The scroll is currently capturing all events or the mouse - * event is taking place on the scrollbar widget area - */ - x -= scrollbar_x; - return scrollbar_mouse_status_to_message( - scrollbar_mouse_action(menu->scrollbar, - mouse, x, y)); - } - - - if (x > x0 && x < scrollbar_x && y > y0 && y < y1) { - /* over option area */ - - if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) - /* button 1 or 2 click */ - form_select_menu_clicked(control, x, y); - - if (!(mouse & BROWSER_MOUSE_CLICK_1 && !multiple)) - /* anything but a button 1 click over a single select - menu */ - status = messages_get(control->data.select.multiple ? - "SelectMClick" : "SelectClick"); - - } else if (!(mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2))) - /* if not a button 1 or 2 click*/ - status = messages_get("SelectClose"); - - return status; -} - -/** - * Handle mouse drag end for the currently opened select menu. - * - * \param control the select menu which received the mouse drag end - * \param mouse current mouse state - * \param x X coordinate of drag end - * \param y Y coordinate of drag end - */ -void form_select_mouse_drag_end(struct form_control *control, - browser_mouse_state mouse, int x, int y) -{ - int x0, y0, x1, y1; - int box_x, box_y; - struct box *box; - struct form_select_menu *menu = control->data.select.menu; - - box = control->box; - - /* Get global coords of scrollbar */ - box_coords(box, &box_x, &box_y); - box_x -= box->border[LEFT].width; - box_y += box->height + box->border[BOTTOM].width + - box->padding[BOTTOM] + box->padding[TOP]; - - /* Get drag end coords relative to scrollbar */ - x = x - box_x; - y = y - box_y; - - if (menu->scroll_capture) { - x -= menu->width - SCROLLBAR_WIDTH; - scrollbar_mouse_drag_end(menu->scrollbar, mouse, x, y); - return; - } - - x0 = 0; - y0 = 0; - x1 = menu->width; - y1 = menu->height; - - - if (x > x0 && x < x1 - SCROLLBAR_WIDTH && y > y0 && y < y1) - /* handle drag end above the option area like a regular click */ - form_select_menu_clicked(control, x, y); -} - -/** - * Callback for the select menus scroll - */ -void form_select_menu_scroll_callback(void *client_data, - struct scrollbar_msg_data *scrollbar_data) -{ - struct form_control *control = client_data; - struct form_select_menu *menu = control->data.select.menu; - html_content *html = (html_content *)menu->c; - - switch (scrollbar_data->msg) { - case SCROLLBAR_MSG_MOVED: - menu->callback(menu->client_data, - 0, 0, - menu->width, - menu->height); - break; - case SCROLLBAR_MSG_SCROLL_START: - { - struct rect rect = { - .x0 = scrollbar_data->x0, - .y0 = scrollbar_data->y0, - .x1 = scrollbar_data->x1, - .y1 = scrollbar_data->y1 - }; - - browser_window_set_drag_type(html->bw, - DRAGGING_CONTENT_SCROLLBAR, &rect); - - menu->scroll_capture = true; - } - break; - case SCROLLBAR_MSG_SCROLL_FINISHED: - menu->scroll_capture = false; - - browser_window_set_drag_type(html->bw, - DRAGGING_NONE, NULL); - break; - default: - break; - } -} - -/** - * Get the dimensions of a select menu. - * - * \param control the select menu to get the dimensions of - * \param width gets updated to menu width - * \param height gets updated to menu height - */ -void form_select_get_dimensions(struct form_control *control, - int *width, int *height) -{ - *width = control->data.select.menu->width; - *height = control->data.select.menu->height; -} - -/** - * Callback for the core select menu. - */ -void form_select_menu_callback(void *client_data, - int x, int y, int width, int height) -{ - html_content *html = client_data; - int menu_x, menu_y; - struct box *box; - - box = html->visible_select_menu->box; - box_coords(box, &menu_x, &menu_y); - - menu_x -= box->border[LEFT].width; - menu_y += box->height + box->border[BOTTOM].width + - box->padding[BOTTOM] + - box->padding[TOP]; - content__request_redraw((struct content *)html, menu_x + x, menu_y + y, - width, height); -} - - -/** - * Set a radio form control and clear the others in the group. - * - * \param radio form control of type GADGET_RADIO - */ - -void form_radio_set(struct form_control *radio) -{ - struct form_control *control; - - assert(radio); - if (!radio->form) - return; - - if (radio->selected) - return; - - for (control = radio->form->controls; control; - control = control->next) { - if (control->type != GADGET_RADIO) - continue; - if (control == radio) - continue; - if (strcmp(control->name, radio->name) != 0) - continue; - - if (control->selected) { - control->selected = false; - dom_html_input_element_set_checked(control->node, false); - html__redraw_a_box(radio->html, control->box); - } - } - - radio->selected = true; - dom_html_input_element_set_checked(radio->node, true); - html__redraw_a_box(radio->html, radio->box); -} - - -/** - * Collect controls and submit a form. - */ - -void form_submit(nsurl *page_url, struct browser_window *target, - struct form *form, struct form_control *submit_button) -{ - char *data = NULL; - struct fetch_multipart_data *success; - nsurl *action_url; - nsurl *action_query; - nserror error; - - assert(form != NULL); - - if (form_successful_controls_dom(form, submit_button, &success) == false) { - guit->misc->warning("NoMemory", 0); - return; - } - - /* Decompose action */ - if (nsurl_create(form->action, &action_url) != NSERROR_OK) { - free(data); - fetch_multipart_data_destroy(success); - guit->misc->warning("NoMemory", 0); - return; - } - - switch (form->method) { - case method_GET: - data = form_url_encode(form, success, true); - if (data == NULL) { - fetch_multipart_data_destroy(success); - guit->misc->warning("NoMemory", 0); - return; - } - - /* Replace query segment */ - error = nsurl_replace_query(action_url, data, &action_query); - if (error != NSERROR_OK) { - nsurl_unref(action_query); - free(data); - fetch_multipart_data_destroy(success); - guit->misc->warning(messages_get_errorcode(error), 0); - return; - } - - /* Construct submit url */ - browser_window_navigate(target, - action_query, - page_url, - BW_NAVIGATE_HISTORY, - NULL, - NULL, - NULL); - - nsurl_unref(action_query); - break; - - case method_POST_URLENC: - data = form_url_encode(form, success, false); - if (data == NULL) { - fetch_multipart_data_destroy(success); - guit->misc->warning("NoMemory", 0); - nsurl_unref(action_url); - return; - } - - browser_window_navigate(target, - action_url, - page_url, - BW_NAVIGATE_HISTORY, - data, - NULL, - NULL); - break; - - case method_POST_MULTIPART: - browser_window_navigate(target, - action_url, - page_url, - BW_NAVIGATE_HISTORY, - NULL, - success, - NULL); - - break; - } - - nsurl_unref(action_url); - fetch_multipart_data_destroy(success); - free(data); -} - -void form_gadget_update_value(struct form_control *control, char *value) -{ - switch (control->type) { - case GADGET_HIDDEN: - case GADGET_TEXTBOX: - case GADGET_TEXTAREA: - case GADGET_PASSWORD: - case GADGET_FILE: - if (control->value != NULL) { - free(control->value); - } - control->value = value; - if (control->node != NULL) { - dom_exception err; - dom_string *str; - err = dom_string_create((uint8_t *)value, - strlen(value), &str); - if (err == DOM_NO_ERR) { - if (control->type == GADGET_TEXTAREA) - err = dom_html_text_area_element_set_value( - (dom_html_text_area_element *)(control->node), - str); - else - err = dom_html_input_element_set_value( - (dom_html_input_element *)(control->node), - str); - dom_string_unref(str); - } - } - break; - default: - /* Do nothing */ - break; - } -} diff --git a/render/form_internal.h b/render/form_internal.h deleted file mode 100644 index 0ffb6b46c..000000000 --- a/render/form_internal.h +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright 2014 Vincent Sanders - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * Interface to form handling functions internal to render. - */ - -#ifndef _NETSURF_RENDER_FORM_INTERNAL_H_ -#define _NETSURF_RENDER_FORM_INTERNAL_H_ - -#include - -#include "netsurf/form.h" - -struct box; -struct form_control; -struct form_option; -struct form_select_menu; -struct form; -struct html_content; -struct dom_string; -struct content; -struct nsurl; -struct fetch_multipart_data; -struct redraw_context; -struct browser_window; - -enum browser_mouse_state; - -/** Type of a struct form_control. */ -typedef enum { - GADGET_HIDDEN, - GADGET_TEXTBOX, - GADGET_RADIO, - GADGET_CHECKBOX, - GADGET_SELECT, - GADGET_TEXTAREA, - GADGET_IMAGE, - GADGET_PASSWORD, - GADGET_SUBMIT, - GADGET_RESET, - GADGET_FILE, - GADGET_BUTTON -} form_control_type; - -/** Data for textarea */ -struct form_textarea_data { - struct form_control *gadget; -}; - -struct image_input_coords { - int x; - int y; -}; - -/** Form control. */ -struct form_control { - void *node; /**< Corresponding DOM node */ - struct html_content *html; /**< HTML content containing control */ - - form_control_type type; /**< Type of control */ - - struct form *form; /**< Containing form */ - - char *name; /**< Control name */ - char *value; /**< Current value of control */ - char *initial_value; /**< Initial value of control */ - bool disabled; /**< Whether control is disabled */ - - struct box *box; /**< Box for control */ - - unsigned int length; /**< Number of characters in control */ - unsigned int maxlength; /**< Maximum characters permitted */ - - bool selected; /**< Whether control is selected */ - - union { - struct { - int mx, my; - } image; - struct { - int num_items; - struct form_option *items, *last_item; - bool multiple; - int num_selected; - /** Currently selected item, if num_selected == 1. */ - struct form_option *current; - struct form_select_menu *menu; - } select; - struct { - struct textarea *ta; - struct dom_string *initial; - struct form_textarea_data data; - } text; /**< input type=text or textarea */ - } data; - - struct form_control *prev; /**< Previous control in this form */ - struct form_control *next; /**< Next control in this form. */ -}; - -/** Form submit method. */ -typedef enum { - method_GET, /**< GET, always url encoded. */ - method_POST_URLENC, /**< POST, url encoded. */ - method_POST_MULTIPART /**< POST, multipart/form-data. */ -} form_method; - -/** HTML form. */ -struct form { - void *node; /**< Corresponding DOM node */ - - char *action; /**< Absolute URL to submit to. */ - char *target; /**< Target to submit to. */ - form_method method; /**< Method and enctype. */ - char *accept_charsets; /**< Charset to submit form in */ - char *document_charset; /**< Charset of document containing form */ - struct form_control *controls; /**< Linked list of controls. */ - struct form_control *last_control; /**< Last control in list. */ - - struct form *prev; /**< Previous form in doc. */ -}; - -/** - * Called by the select menu when it wants an area to be redrawn. The - * coordinates are menu origin relative. - * - * \param client_data data which was passed to form_open_select_menu - * \param x X coordinate of redraw rectangle - * \param y Y coordinate of redraw rectangle - * \param width width of redraw rectangle - * \param height height of redraw rectangle - */ -typedef void(*select_menu_redraw_callback)(void *client_data, - int x, int y, int width, int height); - -/** - * Create a struct form. - * - * \param node DOM node associated with form - * \param action URL to submit form to, or NULL for default - * \param target Target frame of form, or NULL for default - * \param method method and enctype - * \param charset acceptable encodings for form submission, or NULL - * \param doc_charset encoding of containing document, or NULL - * \return A new form or NULL on memory exhaustion - */ -struct form *form_new(void *node, const char *action, const char *target, - form_method method, const char *charset, - const char *doc_charset); - -/** - * Free a form and any controls it owns. - * - * \note There may exist controls attached to box tree nodes which are not - * associated with any form. These will leak at present. Ideally, they will - * be cleaned up when the box tree is destroyed. As that currently happens - * via talloc, this won't happen. These controls are distinguishable, as their - * form field will be NULL. - * - * \param form The form to free - */ -void form_free(struct form *form); - -/** - * Create a struct form_control. - * - * \param node Associated DOM node - * \param type control type - * \return a new structure, or NULL on memory exhaustion - */ -struct form_control *form_new_control(void *node, form_control_type type); - -void form_add_control(struct form *form, struct form_control *control); -void form_free_control(struct form_control *control); -bool form_add_option(struct form_control *control, char *value, char *text, - bool selected, void *node); -bool form_successful_controls(struct form *form, - struct form_control *submit_button, - struct fetch_multipart_data **successful_controls); - -/** - * Identify 'successful' controls via the DOM. - * - * All text strings in the successful controls list will be in the charset most - * appropriate for submission. Therefore, no utf8_to_* processing should be - * performed upon them. - * - * \todo The chosen charset needs to be made available such that it can be - * included in the submission request (e.g. in the fetch's Content-Type header) - * - * See HTML 4.01 section 17.13.2. - * - * \param[in] form form to search for successful controls - * \param[in] submit_button control used to submit the form, if any - * \param[out] successful_controls updated to point to linked list of - * fetch_multipart_data, 0 if no controls - * \return true on success, false on memory exhaustion - */ -bool form_successful_controls_dom(struct form *form, - struct form_control *submit_button, - struct fetch_multipart_data **successful_controls); - - -/** - * Open a select menu for a select form control, creating it if necessary. - * - * \param client_data data passed to the redraw callback - * \param control The select form control for which the menu is being opened - * \param redraw_callback The callback to redraw the select menu. - * \param c The content the select menu is opening for. - * \return false on memory exhaustion, true otherwise - */ -bool form_open_select_menu(void *client_data, - struct form_control *control, - select_menu_redraw_callback redraw_callback, - struct content *c); - - -void form_select_menu_callback(void *client_data, - int x, int y, int width, int height); - - -/** - * Destroy a select menu and free allocated memory. - * - * \param control the select form control owning the select menu being - * destroyed. - */ -void form_free_select_menu(struct form_control *control); - - -/** - * Redraw an opened select menu. - * - * \param control the select menu being redrawn - * \param x the X coordinate to draw the menu at - * \param y the Y coordinate to draw the menu at - * \param scale current redraw scale - * \param clip clipping rectangle - * \param ctx current redraw context - * \return true on success, false otherwise - */ -bool form_redraw_select_menu(struct form_control *control, int x, int y, - float scale, const struct rect *clip, - const struct redraw_context *ctx); - -bool form_clip_inside_select_menu(struct form_control *control, float scale, - const struct rect *clip); -const char *form_select_mouse_action(struct form_control *control, - enum browser_mouse_state mouse, int x, int y); -void form_select_mouse_drag_end(struct form_control *control, - enum browser_mouse_state mouse, int x, int y); -void form_select_get_dimensions(struct form_control *control, - int *width, int *height); -void form_submit(struct nsurl *page_url, struct browser_window *target, - struct form *form, struct form_control *submit_button); -void form_radio_set(struct form_control *radio); - -void form_gadget_update_value(struct form_control *control, char *value); - -#endif diff --git a/render/html.c b/render/html.c deleted file mode 100644 index b7d7aa313..000000000 --- a/render/html.c +++ /dev/null @@ -1,2467 +0,0 @@ -/* - * Copyright 2007 James Bursa - * Copyright 2010 Michael Drake - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * Content for text/html (implementation). - */ - -#include -#include -#include -#include -#include -#include - -#include "utils/utils.h" -#include "utils/config.h" -#include "utils/corestrings.h" -#include "utils/http.h" -#include "utils/libdom.h" -#include "utils/log.h" -#include "utils/messages.h" -#include "utils/talloc.h" -#include "utils/utf8.h" -#include "utils/nsoption.h" -#include "utils/string.h" -#include "utils/ascii.h" -#include "netsurf/content.h" -#include "netsurf/browser_window.h" -#include "netsurf/utf8.h" -#include "netsurf/layout.h" -#include "netsurf/misc.h" -#include "content/hlcache.h" -#include "desktop/selection.h" -#include "desktop/scrollbar.h" -#include "desktop/textarea.h" -#include "netsurf/bitmap.h" -#include "javascript/js.h" -#include "desktop/gui_internal.h" - -#include "render/box.h" -#include "render/form_internal.h" -#include "render/html_internal.h" -#include "render/imagemap.h" -#include "render/layout.h" -#include "render/search.h" - -#define CHUNK 4096 - -/* Change these to 1 to cause a dump to stderr of the frameset or box - * when the trees have been built. - */ -#define ALWAYS_DUMP_FRAMESET 0 -#define ALWAYS_DUMP_BOX 0 - -static const char *html_types[] = { - "application/xhtml+xml", - "text/html" -}; - -/* Exported interface, see html_internal.h */ -bool fire_dom_event(dom_string *type, dom_node *target, - bool bubbles, bool cancelable) -{ - dom_exception exc; - dom_event *evt; - bool result; - - exc = dom_event_create(&evt); - if (exc != DOM_NO_ERR) return false; - exc = dom_event_init(evt, type, bubbles, cancelable); - if (exc != DOM_NO_ERR) { - dom_event_unref(evt); - return false; - } - NSLOG(netsurf, INFO, "Dispatching '%*s' against %p", - dom_string_length(type), dom_string_data(type), target); - exc = dom_event_target_dispatch_event(target, evt, &result); - if (exc != DOM_NO_ERR) { - result = false; - } - dom_event_unref(evt); - return result; -} - -/** - * Perform post-box-creation conversion of a document - * - * \param c HTML content to complete conversion of - * \param success Whether box tree construction was successful - */ -static void html_box_convert_done(html_content *c, bool success) -{ - nserror err; - dom_exception exc; /* returned by libdom functions */ - dom_node *html; - - NSLOG(netsurf, INFO, "Done XML to box (%p)", c); - - /* Clean up and report error if unsuccessful or aborted */ - if ((success == false) || (c->aborted)) { - html_object_free_objects(c); - - if (success == false) { - content_broadcast_errorcode(&c->base, NSERROR_BOX_CONVERT); - } else { - content_broadcast_errorcode(&c->base, NSERROR_STOPPED); - } - - content_set_error(&c->base); - return; - } - - -#if ALWAYS_DUMP_BOX - box_dump(stderr, c->layout->children, 0, true); -#endif -#if ALWAYS_DUMP_FRAMESET - if (c->frameset) - html_dump_frameset(c->frameset, 0); -#endif - - exc = dom_document_get_document_element(c->document, (void *) &html); - if ((exc != DOM_NO_ERR) || (html == NULL)) { - /** @todo should this call html_object_free_objects(c); - * like the other error paths - */ - NSLOG(netsurf, INFO, "error retrieving html element from dom"); - content_broadcast_errorcode(&c->base, NSERROR_DOM); - content_set_error(&c->base); - return; - } - - /* extract image maps - can't do this sensibly in dom_to_box */ - err = imagemap_extract(c); - if (err != NSERROR_OK) { - NSLOG(netsurf, INFO, "imagemap extraction failed"); - html_object_free_objects(c); - content_broadcast_errorcode(&c->base, err); - content_set_error(&c->base); - dom_node_unref(html); - return; - } - /*imagemap_dump(c);*/ - - /* Destroy the parser binding */ - dom_hubbub_parser_destroy(c->parser); - c->parser = NULL; - - content_set_ready(&c->base); - - if (c->base.active == 0) { - content_set_done(&c->base); - } - - dom_node_unref(html); -} - - -/** process link node */ -static bool html_process_link(html_content *c, dom_node *node) -{ - struct content_rfc5988_link link; /* the link added to the content */ - dom_exception exc; /* returned by libdom functions */ - dom_string *atr_string; - nserror error; - - memset(&link, 0, sizeof(struct content_rfc5988_link)); - - /* check that the relation exists - w3c spec says must be present */ - exc = dom_element_get_attribute(node, corestring_dom_rel, &atr_string); - if ((exc != DOM_NO_ERR) || (atr_string == NULL)) { - return false; - } - /* get a lwc string containing the link relation */ - exc = dom_string_intern(atr_string, &link.rel); - dom_string_unref(atr_string); - if (exc != DOM_NO_ERR) { - return false; - } - - /* check that the href exists - w3c spec says must be present */ - exc = dom_element_get_attribute(node, corestring_dom_href, &atr_string); - if ((exc != DOM_NO_ERR) || (atr_string == NULL)) { - lwc_string_unref(link.rel); - return false; - } - - /* get nsurl */ - error = nsurl_join(c->base_url, dom_string_data(atr_string), - &link.href); - dom_string_unref(atr_string); - if (error != NSERROR_OK) { - lwc_string_unref(link.rel); - return false; - } - - /* look for optional properties -- we don't care if internment fails */ - - exc = dom_element_get_attribute(node, - corestring_dom_hreflang, &atr_string); - if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { - /* get a lwc string containing the href lang */ - exc = dom_string_intern(atr_string, &link.hreflang); - dom_string_unref(atr_string); - } - - exc = dom_element_get_attribute(node, - corestring_dom_type, &atr_string); - if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { - /* get a lwc string containing the type */ - exc = dom_string_intern(atr_string, &link.type); - dom_string_unref(atr_string); - } - - exc = dom_element_get_attribute(node, - corestring_dom_media, &atr_string); - if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { - /* get a lwc string containing the media */ - exc = dom_string_intern(atr_string, &link.media); - dom_string_unref(atr_string); - } - - exc = dom_element_get_attribute(node, - corestring_dom_sizes, &atr_string); - if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { - /* get a lwc string containing the sizes */ - exc = dom_string_intern(atr_string, &link.sizes); - dom_string_unref(atr_string); - } - - /* add to content */ - content__add_rfc5988_link(&c->base, &link); - - if (link.sizes != NULL) - lwc_string_unref(link.sizes); - if (link.media != NULL) - lwc_string_unref(link.media); - if (link.type != NULL) - lwc_string_unref(link.type); - if (link.hreflang != NULL) - lwc_string_unref(link.hreflang); - - nsurl_unref(link.href); - lwc_string_unref(link.rel); - - return true; -} - -/** process title node */ -static bool html_process_title(html_content *c, dom_node *node) -{ - dom_exception exc; /* returned by libdom functions */ - dom_string *title; - char *title_str; - bool success; - - exc = dom_node_get_text_content(node, &title); - if ((exc != DOM_NO_ERR) || (title == NULL)) { - return false; - } - - title_str = squash_whitespace(dom_string_data(title)); - dom_string_unref(title); - - if (title_str == NULL) { - return false; - } - - success = content__set_title(&c->base, title_str); - - free(title_str); - - return success; -} - -static bool html_process_base(html_content *c, dom_node *node) -{ - dom_exception exc; /* returned by libdom functions */ - dom_string *atr_string; - - /* get href attribute if present */ - exc = dom_element_get_attribute(node, - corestring_dom_href, &atr_string); - if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { - nsurl *url; - nserror error; - - /* get url from string */ - error = nsurl_create(dom_string_data(atr_string), &url); - dom_string_unref(atr_string); - if (error == NSERROR_OK) { - if (c->base_url != NULL) - nsurl_unref(c->base_url); - c->base_url = url; - } - } - - - /* get target attribute if present and not already set */ - if (c->base_target != NULL) { - return true; - } - - exc = dom_element_get_attribute(node, - corestring_dom_target, &atr_string); - if ((exc == DOM_NO_ERR) && (atr_string != NULL)) { - /* Validation rules from the HTML5 spec for the base element: - * The target must be one of _blank, _self, _parent, or - * _top or any identifier which does not begin with an - * underscore - */ - if (*dom_string_data(atr_string) != '_' || - dom_string_caseless_lwc_isequal(atr_string, - corestring_lwc__blank) || - dom_string_caseless_lwc_isequal(atr_string, - corestring_lwc__self) || - dom_string_caseless_lwc_isequal(atr_string, - corestring_lwc__parent) || - dom_string_caseless_lwc_isequal(atr_string, - corestring_lwc__top)) { - c->base_target = strdup(dom_string_data(atr_string)); - } - dom_string_unref(atr_string); - } - - return true; -} - -static nserror html_meta_refresh_process_element(html_content *c, dom_node *n) -{ - union content_msg_data msg_data; - const char *url, *end, *refresh = NULL; - char *new_url; - char quote = '\0'; - dom_string *equiv, *content; - dom_exception exc; - nsurl *nsurl; - nserror error = NSERROR_OK; - - exc = dom_element_get_attribute(n, corestring_dom_http_equiv, &equiv); - if (exc != DOM_NO_ERR) { - return NSERROR_DOM; - } - - if (equiv == NULL) { - return NSERROR_OK; - } - - if (!dom_string_caseless_lwc_isequal(equiv, corestring_lwc_refresh)) { - dom_string_unref(equiv); - return NSERROR_OK; - } - - dom_string_unref(equiv); - - exc = dom_element_get_attribute(n, corestring_dom_content, &content); - if (exc != DOM_NO_ERR) { - return NSERROR_DOM; - } - - if (content == NULL) { - return NSERROR_OK; - } - - end = dom_string_data(content) + dom_string_byte_length(content); - - /* content := *LWS intpart fracpart? *LWS [';' *LWS *1url *LWS] - * intpart := 1*DIGIT - * fracpart := 1*('.' | DIGIT) - * url := "url" *LWS '=' *LWS (url-nq | url-sq | url-dq) - * url-nq := *urlchar - * url-sq := "'" *(urlchar | '"') "'" - * url-dq := '"' *(urlchar | "'") '"' - * urlchar := [#x9#x21#x23-#x26#x28-#x7E] | nonascii - * nonascii := [#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF] - */ - - url = dom_string_data(content); - - /* *LWS */ - while (url < end && ascii_is_space(*url)) { - url++; - } - - /* intpart */ - if (url == end || (*url < '0' || '9' < *url)) { - /* Empty content, or invalid timeval */ - dom_string_unref(content); - return NSERROR_OK; - } - - msg_data.delay = (int) strtol(url, &new_url, 10); - /* a very small delay and self-referencing URL can cause a loop - * that grinds machines to a halt. To prevent this we set a - * minimum refresh delay of 1s. */ - if (msg_data.delay < 1) { - msg_data.delay = 1; - } - - url = new_url; - - /* fracpart? (ignored, as delay is integer only) */ - while (url < end && (('0' <= *url && *url <= '9') || - *url == '.')) { - url++; - } - - /* *LWS */ - while (url < end && ascii_is_space(*url)) { - url++; - } - - /* ';' */ - if (url < end && *url == ';') - url++; - - /* *LWS */ - while (url < end && ascii_is_space(*url)) { - url++; - } - - if (url == end) { - /* Just delay specified, so refresh current page */ - dom_string_unref(content); - - c->base.refresh = nsurl_ref( - content_get_url(&c->base)); - - content_broadcast(&c->base, CONTENT_MSG_REFRESH, &msg_data); - - return NSERROR_OK; - } - - /* "url" */ - if (url <= end - 3) { - if (strncasecmp(url, "url", 3) == 0) { - url += 3; - } else { - /* Unexpected input, ignore this header */ - dom_string_unref(content); - return NSERROR_OK; - } - } else { - /* Insufficient input, ignore this header */ - dom_string_unref(content); - return NSERROR_OK; - } - - /* *LWS */ - while (url < end && ascii_is_space(*url)) { - url++; - } - - /* '=' */ - if (url < end) { - if (*url == '=') { - url++; - } else { - /* Unexpected input, ignore this header */ - dom_string_unref(content); - return NSERROR_OK; - } - } else { - /* Insufficient input, ignore this header */ - dom_string_unref(content); - return NSERROR_OK; - } - - /* *LWS */ - while (url < end && ascii_is_space(*url)) { - url++; - } - - /* '"' or "'" */ - if (url < end && (*url == '"' || *url == '\'')) { - quote = *url; - url++; - } - - /* Start of URL */ - refresh = url; - - if (quote != 0) { - /* url-sq | url-dq */ - while (url < end && *url != quote) - url++; - } else { - /* url-nq */ - while (url < end && !ascii_is_space(*url)) - url++; - } - - /* '"' or "'" or *LWS (we don't care) */ - if (url > refresh) { - /* There's a URL */ - new_url = strndup(refresh, url - refresh); - if (new_url == NULL) { - dom_string_unref(content); - return NSERROR_NOMEM; - } - - error = nsurl_join(c->base_url, new_url, &nsurl); - if (error == NSERROR_OK) { - /* broadcast valid refresh url */ - - c->base.refresh = nsurl; - - content_broadcast(&c->base, CONTENT_MSG_REFRESH, - &msg_data); - c->refresh = true; - } - - free(new_url); - - } - - dom_string_unref(content); - - return error; -} - -static bool html_process_img(html_content *c, dom_node *node) -{ - dom_string *src; - nsurl *url; - nserror err; - dom_exception exc; - bool success; - - /* Do nothing if foreground images are disabled */ - if (nsoption_bool(foreground_images) == false) { - return true; - } - - exc = dom_element_get_attribute(node, corestring_dom_src, &src); - if (exc != DOM_NO_ERR || src == NULL) { - return true; - } - - err = nsurl_join(c->base_url, dom_string_data(src), &url); - if (err != NSERROR_OK) { - dom_string_unref(src); - return false; - } - dom_string_unref(src); - - /* Speculatively fetch the image */ - success = html_fetch_object(c, url, NULL, CONTENT_IMAGE, 0, 0, false); - nsurl_unref(url); - - return success; -} - -/* exported function documented in render/html_internal.h */ -void html_finish_conversion(html_content *htmlc) -{ - union content_msg_data msg_data; - dom_exception exc; /* returned by libdom functions */ - dom_node *html; - nserror error; - - /* Bail out if we've been aborted */ - if (htmlc->aborted) { - content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED); - content_set_error(&htmlc->base); - return; - } - - /* create new css selection context */ - error = html_css_new_selection_context(htmlc, &htmlc->select_ctx); - if (error != NSERROR_OK) { - content_broadcast_errorcode(&htmlc->base, error); - content_set_error(&htmlc->base); - return; - } - - - /* fire a simple event named load at the Document's Window - * object, but with its target set to the Document object (and - * the currentTarget set to the Window object) - */ - if (htmlc->jscontext != NULL) { - js_fire_event(htmlc->jscontext, "load", htmlc->document, NULL); - } - - /* convert dom tree to box tree */ - NSLOG(netsurf, INFO, "DOM to box (%p)", htmlc); - content_set_status(&htmlc->base, messages_get("Processing")); - msg_data.explicit_status_text = NULL; - content_broadcast(&htmlc->base, CONTENT_MSG_STATUS, &msg_data); - - exc = dom_document_get_document_element(htmlc->document, (void *) &html); - if ((exc != DOM_NO_ERR) || (html == NULL)) { - NSLOG(netsurf, INFO, "error retrieving html element from dom"); - content_broadcast_errorcode(&htmlc->base, NSERROR_DOM); - content_set_error(&htmlc->base); - return; - } - - error = dom_to_box(html, htmlc, html_box_convert_done); - if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, "box conversion failed"); - dom_node_unref(html); - html_object_free_objects(htmlc); - content_broadcast_errorcode(&htmlc->base, error); - content_set_error(&htmlc->base); - return; - } - - dom_node_unref(html); -} - -/* callback for DOMNodeInserted end type */ -static void -dom_default_action_DOMNodeInserted_cb(struct dom_event *evt, void *pw) -{ - dom_event_target *node; - dom_node_type type; - dom_exception exc; - html_content *htmlc = pw; - - exc = dom_event_get_target(evt, &node); - if ((exc == DOM_NO_ERR) && (node != NULL)) { - exc = dom_node_get_node_type(node, &type); - if ((exc == DOM_NO_ERR) && (type == DOM_ELEMENT_NODE)) { - /* an element node has been inserted */ - dom_html_element_type tag_type; - - exc = dom_html_element_get_tag_type(node, &tag_type); - if (exc != DOM_NO_ERR) { - tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; - } - - switch (tag_type) { - case DOM_HTML_ELEMENT_TYPE_LINK: - /* Handle stylesheet loading */ - html_css_process_link(htmlc, (dom_node *)node); - /* Generic link handling */ - html_process_link(htmlc, (dom_node *)node); - break; - case DOM_HTML_ELEMENT_TYPE_META: - if (htmlc->refresh) - break; - html_meta_refresh_process_element(htmlc, - (dom_node *)node); - break; - case DOM_HTML_ELEMENT_TYPE_TITLE: - if (htmlc->title != NULL) - break; - htmlc->title = dom_node_ref(node); - break; - case DOM_HTML_ELEMENT_TYPE_BASE: - html_process_base(htmlc, (dom_node *)node); - break; - case DOM_HTML_ELEMENT_TYPE_IMG: - html_process_img(htmlc, (dom_node *) node); - break; - case DOM_HTML_ELEMENT_TYPE_STYLE: - html_css_process_style(htmlc, (dom_node *) node); - break; - default: - break; - } - if (htmlc->enable_scripting) { - /* ensure javascript context is available */ - if (htmlc->jscontext == NULL) { - union content_msg_data msg_data; - - msg_data.jscontext = &htmlc->jscontext; - content_broadcast(&htmlc->base, - CONTENT_MSG_GETCTX, - &msg_data); - NSLOG(netsurf, INFO, - "javascript context: %p (htmlc: %p)", - htmlc->jscontext, - htmlc); - } - if (htmlc->jscontext != NULL) { - js_handle_new_element(htmlc->jscontext, - (dom_element *) node); - } - } - } - dom_node_unref(node); - } -} - -/* callback for DOMNodeInserted end type */ -static void -dom_default_action_DOMSubtreeModified_cb(struct dom_event *evt, void *pw) -{ - dom_event_target *node; - dom_node_type type; - dom_exception exc; - html_content *htmlc = pw; - - exc = dom_event_get_target(evt, &node); - if ((exc == DOM_NO_ERR) && (node != NULL)) { - if (htmlc->title == (dom_node *)node) { - /* Node is our title node */ - html_process_title(htmlc, (dom_node *)node); - dom_node_unref(node); - return; - } - - exc = dom_node_get_node_type(node, &type); - if ((exc == DOM_NO_ERR) && (type == DOM_ELEMENT_NODE)) { - /* an element node has been modified */ - dom_html_element_type tag_type; - - exc = dom_html_element_get_tag_type(node, &tag_type); - if (exc != DOM_NO_ERR) { - tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; - } - - switch (tag_type) { - case DOM_HTML_ELEMENT_TYPE_STYLE: - html_css_update_style(htmlc, (dom_node *)node); - break; - default: - break; - } - } - dom_node_unref(node); - } -} - -static void -dom_default_action_finished_cb(struct dom_event *evt, void *pw) -{ - html_content *htmlc = pw; - - if (htmlc->jscontext != NULL) - js_event_cleanup(htmlc->jscontext, evt); -} - -/* callback function selector - * - * selects a callback function for libdom to call based on the type and phase. - * dom_default_action_phase from events/document_event.h - * - * The principle events are: - * DOMSubtreeModified - * DOMAttrModified - * DOMNodeInserted - * DOMNodeInsertedIntoDocument - * - * @return callback function pointer or NULL for none - */ -static dom_default_action_callback -dom_event_fetcher(dom_string *type, - dom_default_action_phase phase, - void **pw) -{ - NSLOG(netsurf, DEEPDEBUG, "type:%s", dom_string_data(type)); - - if (phase == DOM_DEFAULT_ACTION_END) { - if (dom_string_isequal(type, corestring_dom_DOMNodeInserted)) { - return dom_default_action_DOMNodeInserted_cb; - } else if (dom_string_isequal(type, corestring_dom_DOMSubtreeModified)) { - return dom_default_action_DOMSubtreeModified_cb; - } - } else if (phase == DOM_DEFAULT_ACTION_FINISHED) { - return dom_default_action_finished_cb; - } - return NULL; -} - -static void -html_document_user_data_handler(dom_node_operation operation, - dom_string *key, void *data, - struct dom_node *src, - struct dom_node *dst) -{ - if (dom_string_isequal(corestring_dom___ns_key_html_content_data, - key) == false || data == NULL) { - return; - } - - switch (operation) { - case DOM_NODE_CLONED: - NSLOG(netsurf, INFO, "Cloned"); - break; - case DOM_NODE_RENAMED: - NSLOG(netsurf, INFO, "Renamed"); - break; - case DOM_NODE_IMPORTED: - NSLOG(netsurf, INFO, "imported"); - break; - case DOM_NODE_ADOPTED: - NSLOG(netsurf, INFO, "Adopted"); - break; - case DOM_NODE_DELETED: - /* This is the only path I expect */ - break; - default: - NSLOG(netsurf, INFO, "User data operation not handled."); - assert(0); - } -} - - -static nserror -html_create_html_data(html_content *c, const http_parameter *params) -{ - lwc_string *charset; - nserror nerror; - dom_hubbub_parser_params parse_params; - dom_hubbub_error error; - dom_exception err; - void *old_node_data; - - c->parser = NULL; - c->parse_completed = false; - c->document = NULL; - c->quirks = DOM_DOCUMENT_QUIRKS_MODE_NONE; - c->encoding = NULL; - c->base_url = nsurl_ref(content_get_url(&c->base)); - c->base_target = NULL; - c->aborted = false; - c->refresh = false; - c->reflowing = false; - c->title = NULL; - c->bctx = NULL; - c->layout = NULL; - c->background_colour = NS_TRANSPARENT; - c->stylesheet_count = 0; - c->stylesheets = NULL; - c->select_ctx = NULL; - c->universal = NULL; - c->num_objects = 0; - c->object_list = NULL; - c->forms = NULL; - c->imagemaps = NULL; - c->bw = NULL; - c->frameset = NULL; - c->iframe = NULL; - c->page = NULL; - c->font_func = guit->layout; - c->drag_type = HTML_DRAG_NONE; - c->drag_owner.no_owner = true; - c->selection_type = HTML_SELECTION_NONE; - c->selection_owner.none = true; - c->focus_type = HTML_FOCUS_SELF; - c->focus_owner.self = true; - c->search = NULL; - c->search_string = NULL; - c->scripts_count = 0; - c->scripts = NULL; - c->jscontext = NULL; - - c->enable_scripting = nsoption_bool(enable_javascript); - c->base.active = 1; /* The html content itself is active */ - - if (lwc_intern_string("*", SLEN("*"), &c->universal) != lwc_error_ok) { - return NSERROR_NOMEM; - } - - selection_prepare(&c->sel, (struct content *)c, true); - - nerror = http_parameter_list_find_item(params, corestring_lwc_charset, &charset); - if (nerror == NSERROR_OK) { - c->encoding = strdup(lwc_string_data(charset)); - - lwc_string_unref(charset); - - if (c->encoding == NULL) { - lwc_string_unref(c->universal); - c->universal = NULL; - return NSERROR_NOMEM; - - } - c->encoding_source = DOM_HUBBUB_ENCODING_SOURCE_HEADER; - } - - /* Create the parser binding */ - parse_params.enc = c->encoding; - parse_params.fix_enc = true; - parse_params.enable_script = c->enable_scripting; - parse_params.msg = NULL; - parse_params.script = html_process_script; - parse_params.ctx = c; - parse_params.daf = dom_event_fetcher; - - error = dom_hubbub_parser_create(&parse_params, - &c->parser, - &c->document); - if ((error != DOM_HUBBUB_OK) && (c->encoding != NULL)) { - /* Ok, we don't support the declared encoding. Bailing out - * isn't exactly user-friendly, so fall back to autodetect */ - free(c->encoding); - c->encoding = NULL; - - parse_params.enc = c->encoding; - - error = dom_hubbub_parser_create(&parse_params, - &c->parser, - &c->document); - } - if (error != DOM_HUBBUB_OK) { - nsurl_unref(c->base_url); - c->base_url = NULL; - - lwc_string_unref(c->universal); - c->universal = NULL; - - return libdom_hubbub_error_to_nserror(error); - } - - err = dom_node_set_user_data(c->document, - corestring_dom___ns_key_html_content_data, - c, html_document_user_data_handler, - (void *) &old_node_data); - if (err != DOM_NO_ERR) { - dom_hubbub_parser_destroy(c->parser); - nsurl_unref(c->base_url); - c->base_url = NULL; - - lwc_string_unref(c->universal); - c->universal = NULL; - - NSLOG(netsurf, INFO, "Unable to set user data."); - return NSERROR_DOM; - } - - assert(old_node_data == NULL); - - return NSERROR_OK; - -} - -/** - * Create a CONTENT_HTML. - * - * The content_html_data structure is initialized and the HTML parser is - * created. - */ - -static nserror -html_create(const content_handler *handler, - lwc_string *imime_type, - const http_parameter *params, - llcache_handle *llcache, - const char *fallback_charset, - bool quirks, - struct content **c) -{ - html_content *html; - nserror error; - - html = calloc(1, sizeof(html_content)); - if (html == NULL) - return NSERROR_NOMEM; - - error = content__init(&html->base, handler, imime_type, params, - llcache, fallback_charset, quirks); - if (error != NSERROR_OK) { - free(html); - return error; - } - - error = html_create_html_data(html, params); - if (error != NSERROR_OK) { - content_broadcast_errorcode(&html->base, error); - free(html); - return error; - } - - error = html_css_new_stylesheets(html); - if (error != NSERROR_OK) { - content_broadcast_errorcode(&html->base, error); - free(html); - return error; - } - - *c = (struct content *) html; - - return NSERROR_OK; -} - - - -static nserror -html_process_encoding_change(struct content *c, - const char *data, - unsigned int size) -{ - html_content *html = (html_content *) c; - dom_hubbub_parser_params parse_params; - dom_hubbub_error error; - const char *encoding; - const char *source_data; - unsigned long source_size; - - /* Retrieve new encoding */ - encoding = dom_hubbub_parser_get_encoding(html->parser, - &html->encoding_source); - if (encoding == NULL) { - return NSERROR_NOMEM; - } - - if (html->encoding != NULL) { - free(html->encoding); - html->encoding = NULL; - } - - html->encoding = strdup(encoding); - if (html->encoding == NULL) { - return NSERROR_NOMEM; - } - - /* Destroy binding */ - dom_hubbub_parser_destroy(html->parser); - html->parser = NULL; - - if (html->document != NULL) { - dom_node_unref(html->document); - } - - parse_params.enc = html->encoding; - parse_params.fix_enc = true; - parse_params.enable_script = html->enable_scripting; - parse_params.msg = NULL; - parse_params.script = html_process_script; - parse_params.ctx = html; - parse_params.daf = dom_event_fetcher; - - /* Create new binding, using the new encoding */ - error = dom_hubbub_parser_create(&parse_params, - &html->parser, - &html->document); - if (error != DOM_HUBBUB_OK) { - /* Ok, we don't support the declared encoding. Bailing out - * isn't exactly user-friendly, so fall back to Windows-1252 */ - free(html->encoding); - html->encoding = strdup("Windows-1252"); - if (html->encoding == NULL) { - return NSERROR_NOMEM; - } - parse_params.enc = html->encoding; - - error = dom_hubbub_parser_create(&parse_params, - &html->parser, - &html->document); - - if (error != DOM_HUBBUB_OK) { - return libdom_hubbub_error_to_nserror(error); - } - - } - - source_data = content__get_source_data(c, &source_size); - - /* Reprocess all the data. This is safe because - * the encoding is now specified at parser start which means - * it cannot be changed again. - */ - error = dom_hubbub_parser_parse_chunk(html->parser, - (const uint8_t *)source_data, - source_size); - - return libdom_hubbub_error_to_nserror(error); -} - - -/** - * Process data for CONTENT_HTML. - */ - -static bool -html_process_data(struct content *c, const char *data, unsigned int size) -{ - html_content *html = (html_content *) c; - dom_hubbub_error dom_ret; - nserror err = NSERROR_OK; /* assume its all going to be ok */ - - dom_ret = dom_hubbub_parser_parse_chunk(html->parser, - (const uint8_t *) data, - size); - - err = libdom_hubbub_error_to_nserror(dom_ret); - - /* deal with encoding change */ - if (err == NSERROR_ENCODING_CHANGE) { - err = html_process_encoding_change(c, data, size); - } - - /* broadcast the error if necessary */ - if (err != NSERROR_OK) { - content_broadcast_errorcode(c, err); - return false; - } - - return true; -} - - -/** - * Convert a CONTENT_HTML for display. - * - * The following steps are carried out in order: - * - * - parsing to an XML tree is completed - * - stylesheets are fetched - * - the XML tree is converted to a box tree and object fetches are started - * - * On exit, the content status will be either CONTENT_STATUS_DONE if the - * document is completely loaded or CONTENT_STATUS_READY if objects are still - * being fetched. - */ - -static bool html_convert(struct content *c) -{ - html_content *htmlc = (html_content *) c; - dom_exception exc; /* returned by libdom functions */ - - /* The quirk check and associated stylesheet fetch is "safe" - * once the root node has been inserted into the document - * which must have happened by this point in the parse. - * - * faliure to retrive the quirk mode or to start the - * stylesheet fetch is non fatal as this "only" affects the - * render and it would annoy the user to fail the entire - * render for want of a quirks stylesheet. - */ - exc = dom_document_get_quirks_mode(htmlc->document, &htmlc->quirks); - if (exc == DOM_NO_ERR) { - html_css_quirks_stylesheets(htmlc); - NSLOG(netsurf, INFO, "quirks set to %d", htmlc->quirks); - } - - htmlc->base.active--; /* the html fetch is no longer active */ - NSLOG(netsurf, INFO, "%d fetches active (%p)", htmlc->base.active, c); - - /* The parse cannot be completed here because it may be paused - * untill all the resources being fetched have completed. - */ - - /* if there are no active fetches in progress no scripts are - * being fetched or they completed already. - */ - if (html_can_begin_conversion(htmlc)) { - return html_begin_conversion(htmlc); - } - return true; -} - -/* Exported interface documented in html_internal.h */ -bool html_can_begin_conversion(html_content *htmlc) -{ - unsigned int i; - - if (htmlc->base.active != 0) - return false; - - for (i = 0; i != htmlc->stylesheet_count; i++) { - if (htmlc->stylesheets[i].modified) - return false; - } - - return true; -} - -bool -html_begin_conversion(html_content *htmlc) -{ - dom_node *html; - nserror ns_error; - struct form *f; - dom_exception exc; /* returned by libdom functions */ - dom_string *node_name = NULL; - dom_hubbub_error error; - - /* The act of completing the parse can result in additional data - * being flushed through the parser. This may result in new style or - * script nodes, upon which the conversion depends. Thus, once we - * have completed the parse, we must check again to see if we can - * begin the conversion. If we can't, we must stop and wait for the - * new styles/scripts to be processed. Once they have been processed, - * we will be called again to begin the conversion for real. Thus, - * we must also ensure that we don't attempt to complete the parse - * multiple times, so store a flag to indicate that parsing is - * complete to avoid repeating the completion pointlessly. - */ - if (htmlc->parse_completed == false) { - NSLOG(netsurf, INFO, "Completing parse (%p)", htmlc); - /* complete parsing */ - error = dom_hubbub_parser_completed(htmlc->parser); - if (error != DOM_HUBBUB_OK) { - NSLOG(netsurf, INFO, "Parsing failed"); - - content_broadcast_errorcode(&htmlc->base, - libdom_hubbub_error_to_nserror(error)); - - return false; - } - htmlc->parse_completed = true; - } - - if (html_can_begin_conversion(htmlc) == false) { - NSLOG(netsurf, INFO, "Can't begin conversion (%p)", htmlc); - /* We can't proceed (see commentary above) */ - return true; - } - - /* Give up processing if we've been aborted */ - if (htmlc->aborted) { - NSLOG(netsurf, INFO, "Conversion aborted (%p) (active: %u)", - htmlc, htmlc->base.active); - content_set_error(&htmlc->base); - content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED); - return false; - } - - /* complete script execution */ - html_script_exec(htmlc); - - /* fire a simple event that bubbles named DOMContentLoaded at - * the Document. - */ - - /* get encoding */ - if (htmlc->encoding == NULL) { - const char *encoding; - - encoding = dom_hubbub_parser_get_encoding(htmlc->parser, - &htmlc->encoding_source); - if (encoding == NULL) { - content_broadcast_errorcode(&htmlc->base, - NSERROR_NOMEM); - return false; - } - - htmlc->encoding = strdup(encoding); - if (htmlc->encoding == NULL) { - content_broadcast_errorcode(&htmlc->base, - NSERROR_NOMEM); - return false; - } - } - - /* locate root element and ensure it is html */ - exc = dom_document_get_document_element(htmlc->document, (void *) &html); - if ((exc != DOM_NO_ERR) || (html == NULL)) { - NSLOG(netsurf, INFO, "error retrieving html element from dom"); - content_broadcast_errorcode(&htmlc->base, NSERROR_DOM); - return false; - } - - exc = dom_node_get_node_name(html, &node_name); - if ((exc != DOM_NO_ERR) || - (node_name == NULL) || - (!dom_string_caseless_lwc_isequal(node_name, - corestring_lwc_html))) { - NSLOG(netsurf, INFO, "root element not html"); - content_broadcast_errorcode(&htmlc->base, NSERROR_DOM); - dom_node_unref(html); - return false; - } - dom_string_unref(node_name); - - /* Retrieve forms from parser */ - htmlc->forms = html_forms_get_forms(htmlc->encoding, - (dom_html_document *) htmlc->document); - for (f = htmlc->forms; f != NULL; f = f->prev) { - nsurl *action; - - /* Make all actions absolute */ - if (f->action == NULL || f->action[0] == '\0') { - /* HTML5 4.10.22.3 step 9 */ - nsurl *doc_addr = content_get_url(&htmlc->base); - ns_error = nsurl_join(htmlc->base_url, - nsurl_access(doc_addr), - &action); - } else { - ns_error = nsurl_join(htmlc->base_url, - f->action, - &action); - } - - if (ns_error != NSERROR_OK) { - content_broadcast_errorcode(&htmlc->base, ns_error); - - dom_node_unref(html); - return false; - } - - free(f->action); - f->action = strdup(nsurl_access(action)); - nsurl_unref(action); - if (f->action == NULL) { - content_broadcast_errorcode(&htmlc->base, - NSERROR_NOMEM); - - dom_node_unref(html); - return false; - } - - /* Ensure each form has a document encoding */ - if (f->document_charset == NULL) { - f->document_charset = strdup(htmlc->encoding); - if (f->document_charset == NULL) { - content_broadcast_errorcode(&htmlc->base, - NSERROR_NOMEM); - dom_node_unref(html); - return false; - } - } - } - - dom_node_unref(html); - - if (htmlc->base.active == 0) { - html_finish_conversion(htmlc); - } - - return true; -} - - -/** - * Stop loading a CONTENT_HTML. - * - * called when the content is aborted. This must clean up any state - * created during the fetch. - */ - -static void html_stop(struct content *c) -{ - html_content *htmlc = (html_content *) c; - - /* invalidate the html content reference to the javascript context - * as it is about to become invalid and must not be used any - * more. - */ - html_script_invalidate_ctx(htmlc); - - switch (c->status) { - case CONTENT_STATUS_LOADING: - /* Still loading; simply flag that we've been aborted - * html_convert/html_finish_conversion will do the rest */ - htmlc->aborted = true; - break; - - case CONTENT_STATUS_READY: - html_object_abort_objects(htmlc); - - /* If there are no further active fetches and we're still - * in the READY state, transition to the DONE state. */ - if (c->status == CONTENT_STATUS_READY && c->active == 0) { - content_set_done(c); - } - - break; - - case CONTENT_STATUS_DONE: - /* Nothing to do */ - break; - - default: - NSLOG(netsurf, INFO, "Unexpected status %d (%p)", c->status, - c); - assert(0); - } -} - - -/** - * Reformat a CONTENT_HTML to a new width. - */ - -static void html_reformat(struct content *c, int width, int height) -{ - html_content *htmlc = (html_content *) c; - struct box *layout; - uint64_t ms_before; - uint64_t ms_after; - uint64_t ms_interval; - - nsu_getmonotonic_ms(&ms_before); - - htmlc->reflowing = true; - - htmlc->len_ctx.vw = width; - htmlc->len_ctx.vh = height; - htmlc->len_ctx.root_style = htmlc->layout->style; - - layout_document(htmlc, width, height); - layout = htmlc->layout; - - /* width and height are at least margin box of document */ - c->width = layout->x + layout->padding[LEFT] + layout->width + - layout->padding[RIGHT] + layout->border[RIGHT].width + - layout->margin[RIGHT]; - c->height = layout->y + layout->padding[TOP] + layout->height + - layout->padding[BOTTOM] + layout->border[BOTTOM].width + - layout->margin[BOTTOM]; - - /* if boxes overflow right or bottom edge, expand to contain it */ - if (c->width < layout->x + layout->descendant_x1) - c->width = layout->x + layout->descendant_x1; - if (c->height < layout->y + layout->descendant_y1) - c->height = layout->y + layout->descendant_y1; - - selection_reinit(&htmlc->sel, htmlc->layout); - - htmlc->reflowing = false; - - /* calculate next reflow time at three times what it took to reflow */ - nsu_getmonotonic_ms(&ms_after); - - ms_interval = (ms_before - ms_after) * 3; - if (ms_interval < (nsoption_uint(min_reflow_period) * 10)) { - ms_interval = nsoption_uint(min_reflow_period) * 10; - } - c->reformat_time = ms_after + ms_interval; -} - - -/** - * Redraw a box. - * - * \param h content containing the box, of type CONTENT_HTML - * \param box box to redraw - */ - -void html_redraw_a_box(hlcache_handle *h, struct box *box) -{ - int x, y; - - box_coords(box, &x, &y); - - content_request_redraw(h, x, y, - box->padding[LEFT] + box->width + box->padding[RIGHT], - box->padding[TOP] + box->height + box->padding[BOTTOM]); -} - - -/** - * Redraw a box. - * - * \param html content containing the box, of type CONTENT_HTML - * \param box box to redraw. - */ - -void html__redraw_a_box(struct html_content *html, struct box *box) -{ - int x, y; - - box_coords(box, &x, &y); - - content__request_redraw((struct content *)html, x, y, - box->padding[LEFT] + box->width + box->padding[RIGHT], - box->padding[TOP] + box->height + box->padding[BOTTOM]); -} - -static void html_destroy_frameset(struct content_html_frames *frameset) -{ - int i; - - if (frameset->name) { - talloc_free(frameset->name); - frameset->name = NULL; - } - if (frameset->url) { - talloc_free(frameset->url); - frameset->url = NULL; - } - if (frameset->children) { - for (i = 0; i < (frameset->rows * frameset->cols); i++) { - if (frameset->children[i].name) { - talloc_free(frameset->children[i].name); - frameset->children[i].name = NULL; - } - if (frameset->children[i].url) { - nsurl_unref(frameset->children[i].url); - frameset->children[i].url = NULL; - } - if (frameset->children[i].children) - html_destroy_frameset(&frameset->children[i]); - } - talloc_free(frameset->children); - frameset->children = NULL; - } -} - -static void html_destroy_iframe(struct content_html_iframe *iframe) -{ - struct content_html_iframe *next; - next = iframe; - while ((iframe = next) != NULL) { - next = iframe->next; - if (iframe->name) - talloc_free(iframe->name); - if (iframe->url) { - nsurl_unref(iframe->url); - iframe->url = NULL; - } - talloc_free(iframe); - } -} - - -static void html_free_layout(html_content *htmlc) -{ - if (htmlc->bctx != NULL) { - /* freeing talloc context should let the entire box - * set be destroyed - */ - talloc_free(htmlc->bctx); - } -} - -/** - * Destroy a CONTENT_HTML and free all resources it owns. - */ - -static void html_destroy(struct content *c) -{ - html_content *html = (html_content *) c; - struct form *f, *g; - - NSLOG(netsurf, INFO, "content %p", c); - - /* Destroy forms */ - for (f = html->forms; f != NULL; f = g) { - g = f->prev; - - form_free(f); - } - - imagemap_destroy(html); - - if (c->refresh) - nsurl_unref(c->refresh); - - if (html->base_url) - nsurl_unref(html->base_url); - - if (html->parser != NULL) { - dom_hubbub_parser_destroy(html->parser); - html->parser = NULL; - } - - if (html->document != NULL) { - dom_node_unref(html->document); - html->document = NULL; - } - - if (html->title != NULL) { - dom_node_unref(html->title); - html->title = NULL; - } - - /* Free encoding */ - if (html->encoding != NULL) { - free(html->encoding); - html->encoding = NULL; - } - - /* Free base target */ - if (html->base_target != NULL) { - free(html->base_target); - html->base_target = NULL; - } - - /* Free frameset */ - if (html->frameset != NULL) { - html_destroy_frameset(html->frameset); - talloc_free(html->frameset); - html->frameset = NULL; - } - - /* Free iframes */ - if (html->iframe != NULL) { - html_destroy_iframe(html->iframe); - html->iframe = NULL; - } - - /* Destroy selection context */ - if (html->select_ctx != NULL) { - css_select_ctx_destroy(html->select_ctx); - html->select_ctx = NULL; - } - - if (html->universal != NULL) { - lwc_string_unref(html->universal); - html->universal = NULL; - } - - /* Free stylesheets */ - html_css_free_stylesheets(html); - - /* Free scripts */ - html_script_free(html); - - /* Free objects */ - html_object_free_objects(html); - - /* free layout */ - html_free_layout(html); -} - - -static nserror html_clone(const struct content *old, struct content **newc) -{ - /** \todo Clone HTML specifics */ - - /* In the meantime, we should never be called, as HTML contents - * cannot be shared and we're not intending to fix printing's - * cloning of documents. */ - assert(0 && "html_clone should never be called"); - - return true; -} - - -/** - * Handle a window containing a CONTENT_HTML being opened. - */ - -static void -html_open(struct content *c, - struct browser_window *bw, - struct content *page, - struct object_params *params) -{ - html_content *html = (html_content *) c; - - html->bw = bw; - html->page = (html_content *) page; - - html->drag_type = HTML_DRAG_NONE; - html->drag_owner.no_owner = true; - - /* text selection */ - selection_init(&html->sel, html->layout, &html->len_ctx); - html->selection_type = HTML_SELECTION_NONE; - html->selection_owner.none = true; - - html_object_open_objects(html, bw); -} - - -/** - * Handle a window containing a CONTENT_HTML being closed. - */ - -static void html_close(struct content *c) -{ - html_content *htmlc = (html_content *) c; - - selection_clear(&htmlc->sel, false); - - if (htmlc->search != NULL) { - search_destroy_context(htmlc->search); - } - - /* clear the html content reference to the browser window */ - htmlc->bw = NULL; - - /* invalidate the html content reference to the javascript context - * as it is about to become invalid and must not be used any - * more. - */ - html_script_invalidate_ctx(htmlc); - - /* remove all object references from the html content */ - html_object_close_objects(htmlc); -} - - -/** - * Return an HTML content's selection context - */ - -static void html_clear_selection(struct content *c) -{ - html_content *html = (html_content *) c; - - switch (html->selection_type) { - case HTML_SELECTION_NONE: - /* Nothing to do */ - assert(html->selection_owner.none == true); - break; - case HTML_SELECTION_TEXTAREA: - textarea_clear_selection(html->selection_owner.textarea-> - gadget->data.text.ta); - break; - case HTML_SELECTION_SELF: - assert(html->selection_owner.none == false); - selection_clear(&html->sel, true); - break; - case HTML_SELECTION_CONTENT: - content_clear_selection(html->selection_owner.content->object); - break; - default: - break; - } - - /* There is no selection now. */ - html->selection_type = HTML_SELECTION_NONE; - html->selection_owner.none = true; -} - - -/** - * Return an HTML content's selection context - */ - -static char *html_get_selection(struct content *c) -{ - html_content *html = (html_content *) c; - - switch (html->selection_type) { - case HTML_SELECTION_TEXTAREA: - return textarea_get_selection(html->selection_owner.textarea-> - gadget->data.text.ta); - case HTML_SELECTION_SELF: - assert(html->selection_owner.none == false); - return selection_get_copy(&html->sel); - case HTML_SELECTION_CONTENT: - return content_get_selection( - html->selection_owner.content->object); - case HTML_SELECTION_NONE: - /* Nothing to do */ - assert(html->selection_owner.none == true); - break; - default: - break; - } - - return NULL; -} - - -/** - * Get access to any content, link URLs and objects (images) currently - * at the given (x, y) coordinates. - * - * \param[in] c html content to look inside - * \param[in] x x-coordinate of point of interest - * \param[in] y y-coordinate of point of interest - * \param[out] data Positional features struct to be updated with any - * relevent content, or set to NULL if none. - * \return NSERROR_OK on success else appropriate error code. - */ -static nserror -html_get_contextual_content(struct content *c, int x, int y, - struct browser_window_features *data) -{ - html_content *html = (html_content *) c; - - struct box *box = html->layout; - struct box *next; - int box_x = 0, box_y = 0; - - while ((next = box_at_point(&html->len_ctx, box, x, y, - &box_x, &box_y)) != NULL) { - box = next; - - /* hidden boxes are ignored */ - if ((box->style != NULL) && - css_computed_visibility(box->style) == CSS_VISIBILITY_HIDDEN) { - continue; - } - - if (box->iframe) { - browser_window_get_features(box->iframe, - x - box_x, y - box_y, data); - } - - if (box->object) - content_get_contextual_content(box->object, - x - box_x, y - box_y, data); - - if (box->object) - data->object = box->object; - - if (box->href) - data->link = box->href; - - if (box->usemap) { - const char *target = NULL; - nsurl *url = imagemap_get(html, box->usemap, box_x, - box_y, x, y, &target); - /* Box might have imagemap, but no actual link area - * at point */ - if (url != NULL) - data->link = url; - } - if (box->gadget) { - switch (box->gadget->type) { - case GADGET_TEXTBOX: - case GADGET_TEXTAREA: - case GADGET_PASSWORD: - data->form_features = CTX_FORM_TEXT; - break; - - case GADGET_FILE: - data->form_features = CTX_FORM_FILE; - break; - - default: - data->form_features = CTX_FORM_NONE; - break; - } - } - } - return NSERROR_OK; -} - - -/** - * Scroll deepest thing within the content which can be scrolled at given point - * - * \param c html content to look inside - * \param x x-coordinate of point of interest - * \param y y-coordinate of point of interest - * \param scrx number of px try to scroll something in x direction - * \param scry number of px try to scroll something in y direction - * \return true iff scroll was consumed by something in the content - */ -static bool -html_scroll_at_point(struct content *c, int x, int y, int scrx, int scry) -{ - html_content *html = (html_content *) c; - - struct box *box = html->layout; - struct box *next; - int box_x = 0, box_y = 0; - bool handled_scroll = false; - - /* TODO: invert order; visit deepest box first */ - - while ((next = box_at_point(&html->len_ctx, box, x, y, - &box_x, &box_y)) != NULL) { - box = next; - - if (box->style && css_computed_visibility(box->style) == - CSS_VISIBILITY_HIDDEN) - continue; - - /* Pass into iframe */ - if (box->iframe && browser_window_scroll_at_point(box->iframe, - x - box_x, y - box_y, scrx, scry) == true) - return true; - - /* Pass into textarea widget */ - if (box->gadget && (box->gadget->type == GADGET_TEXTAREA || - box->gadget->type == GADGET_PASSWORD || - box->gadget->type == GADGET_TEXTBOX) && - textarea_scroll(box->gadget->data.text.ta, - scrx, scry) == true) - return true; - - /* Pass into object */ - if (box->object != NULL && content_scroll_at_point( - box->object, x - box_x, y - box_y, - scrx, scry) == true) - return true; - - /* Handle box scrollbars */ - if (box->scroll_y && scrollbar_scroll(box->scroll_y, scry)) - handled_scroll = true; - - if (box->scroll_x && scrollbar_scroll(box->scroll_x, scrx)) - handled_scroll = true; - - if (handled_scroll == true) - return true; - } - - return false; -} - -/** Helper for file gadgets to store their filename unencoded on the - * dom node associated with the gadget. - * - * \todo Get rid of this crap eventually - */ -static void html__dom_user_data_handler(dom_node_operation operation, - dom_string *key, void *_data, struct dom_node *src, - struct dom_node *dst) -{ - char *oldfile; - char *data = (char *)_data; - - if (!dom_string_isequal(corestring_dom___ns_key_file_name_node_data, - key) || data == NULL) { - return; - } - - switch (operation) { - case DOM_NODE_CLONED: - if (dom_node_set_user_data(dst, - corestring_dom___ns_key_file_name_node_data, - strdup(data), html__dom_user_data_handler, - &oldfile) == DOM_NO_ERR) { - if (oldfile != NULL) - free(oldfile); - } - break; - - case DOM_NODE_RENAMED: - case DOM_NODE_IMPORTED: - case DOM_NODE_ADOPTED: - break; - - case DOM_NODE_DELETED: - free(data); - break; - default: - NSLOG(netsurf, INFO, "User data operation not handled."); - assert(0); - } -} - -static void html__set_file_gadget_filename(struct content *c, - struct form_control *gadget, const char *fn) -{ - nserror ret; - char *utf8_fn, *oldfile = NULL; - html_content *html = (html_content *)c; - struct box *file_box = gadget->box; - - ret = guit->utf8->local_to_utf8(fn, 0, &utf8_fn); - if (ret != NSERROR_OK) { - assert(ret != NSERROR_BAD_ENCODING); - NSLOG(netsurf, INFO, - "utf8 to local encoding conversion failed"); - /* Load was for us - just no memory */ - return; - } - - form_gadget_update_value(gadget, utf8_fn); - - /* corestring_dom___ns_key_file_name_node_data */ - if (dom_node_set_user_data((dom_node *)file_box->gadget->node, - corestring_dom___ns_key_file_name_node_data, - strdup(fn), html__dom_user_data_handler, - &oldfile) == DOM_NO_ERR) { - if (oldfile != NULL) - free(oldfile); - } - - /* Redraw box. */ - html__redraw_a_box(html, file_box); -} - -void html_set_file_gadget_filename(struct hlcache_handle *hl, - struct form_control *gadget, const char *fn) -{ - return html__set_file_gadget_filename(hlcache_handle_get_content(hl), - gadget, fn); -} - -/** - * Drop a file onto a content at a particular point, or determine if a file - * may be dropped onto the content at given point. - * - * \param c html content to look inside - * \param x x-coordinate of point of interest - * \param y y-coordinate of point of interest - * \param file path to file to be dropped, or NULL to know if drop allowed - * \return true iff file drop has been handled, or if drop possible (NULL file) - */ -static bool html_drop_file_at_point(struct content *c, int x, int y, char *file) -{ - html_content *html = (html_content *) c; - - struct box *box = html->layout; - struct box *next; - struct box *file_box = NULL; - struct box *text_box = NULL; - int box_x = 0, box_y = 0; - - /* Scan box tree for boxes that can handle drop */ - while ((next = box_at_point(&html->len_ctx, box, x, y, - &box_x, &box_y)) != NULL) { - box = next; - - if (box->style && css_computed_visibility(box->style) == - CSS_VISIBILITY_HIDDEN) - continue; - - if (box->iframe) - return browser_window_drop_file_at_point(box->iframe, - x - box_x, y - box_y, file); - - if (box->object && content_drop_file_at_point(box->object, - x - box_x, y - box_y, file) == true) - return true; - - if (box->gadget) { - switch (box->gadget->type) { - case GADGET_FILE: - file_box = box; - break; - - case GADGET_TEXTBOX: - case GADGET_TEXTAREA: - case GADGET_PASSWORD: - text_box = box; - break; - - default: /* appease compiler */ - break; - } - } - } - - if (!file_box && !text_box) - /* No box capable of handling drop */ - return false; - - if (file == NULL) - /* There is a box capable of handling drop here */ - return true; - - /* Handle the drop */ - if (file_box) { - /* File dropped on file input */ - html__set_file_gadget_filename(c, file_box->gadget, file); - - } else { - /* File dropped on text input */ - - size_t file_len; - FILE *fp = NULL; - char *buffer; - char *utf8_buff; - nserror ret; - unsigned int size; - int bx, by; - - /* Open file */ - fp = fopen(file, "rb"); - if (fp == NULL) { - /* Couldn't open file, but drop was for us */ - return true; - } - - /* Get filesize */ - fseek(fp, 0, SEEK_END); - file_len = ftell(fp); - fseek(fp, 0, SEEK_SET); - - if ((long)file_len == -1) { - /* unable to get file length, but drop was for us */ - fclose(fp); - return true; - } - - /* Allocate buffer for file data */ - buffer = malloc(file_len + 1); - if (buffer == NULL) { - /* No memory, but drop was for us */ - fclose(fp); - return true; - } - - /* Stick file into buffer */ - if (file_len != fread(buffer, 1, file_len, fp)) { - /* Failed, but drop was for us */ - free(buffer); - fclose(fp); - return true; - } - - /* Done with file */ - fclose(fp); - - /* Ensure buffer's string termination */ - buffer[file_len] = '\0'; - - /* TODO: Sniff for text? */ - - /* Convert to UTF-8 */ - ret = guit->utf8->local_to_utf8(buffer, file_len, &utf8_buff); - if (ret != NSERROR_OK) { - /* bad encoding shouldn't happen */ - assert(ret != NSERROR_BAD_ENCODING); - NSLOG(netsurf, INFO, "local to utf8 encoding failed"); - free(buffer); - guit->misc->warning("NoMemory", NULL); - return true; - } - - /* Done with buffer */ - free(buffer); - - /* Get new length */ - size = strlen(utf8_buff); - - /* Simulate a click over the input box, to place caret */ - box_coords(text_box, &bx, &by); - textarea_mouse_action(text_box->gadget->data.text.ta, - BROWSER_MOUSE_PRESS_1, x - bx, y - by); - - /* Paste the file as text */ - textarea_drop_text(text_box->gadget->data.text.ta, - utf8_buff, size); - - free(utf8_buff); - } - - return true; -} - - -/** - * set debug status. - * - * \param c The content to debug - * \param op The debug operation type - */ -static nserror -html_debug(struct content *c, enum content_debug op) -{ - html_redraw_debug = !html_redraw_debug; - - return NSERROR_OK; -} - - -/** - * Dump debug info concerning the html_content - * - * \param c The content to debug - * \param f The file to dump to - * \param op The debug dump type - */ -static nserror -html_debug_dump(struct content *c, FILE *f, enum content_debug op) -{ - html_content *htmlc = (html_content *)c; - dom_node *html; - dom_exception exc; /* returned by libdom functions */ - nserror ret; - - assert(htmlc != NULL); - - if (op == CONTENT_DEBUG_RENDER) { - assert(htmlc->layout != NULL); - box_dump(f, htmlc->layout, 0, true); - ret = NSERROR_OK; - } else { - if (htmlc->document == NULL) { - NSLOG(netsurf, INFO, "No document to dump"); - return NSERROR_DOM; - } - - exc = dom_document_get_document_element(htmlc->document, (void *) &html); - if ((exc != DOM_NO_ERR) || (html == NULL)) { - NSLOG(netsurf, INFO, "Unable to obtain root node"); - return NSERROR_DOM; - } - - ret = libdom_dump_structure(html, f, 0); - - NSLOG(netsurf, INFO, "DOM structure dump returning %d", ret); - - dom_node_unref(html); - } - - return ret; -} - - -#if ALWAYS_DUMP_FRAMESET -/** - * Print a frameset tree to stderr. - */ - -static void -html_dump_frameset(struct content_html_frames *frame, unsigned int depth) -{ - unsigned int i; - int row, col, index; - const char *unit[] = {"px", "%", "*"}; - const char *scrolling[] = {"auto", "yes", "no"}; - - assert(frame); - - fprintf(stderr, "%p ", frame); - - fprintf(stderr, "(%i %i) ", frame->rows, frame->cols); - - fprintf(stderr, "w%g%s ", frame->width.value, unit[frame->width.unit]); - fprintf(stderr, "h%g%s ", frame->height.value,unit[frame->height.unit]); - fprintf(stderr, "(margin w%i h%i) ", - frame->margin_width, frame->margin_height); - - if (frame->name) - fprintf(stderr, "'%s' ", frame->name); - if (frame->url) - fprintf(stderr, "<%s> ", frame->url); - - if (frame->no_resize) - fprintf(stderr, "noresize "); - fprintf(stderr, "(scrolling %s) ", scrolling[frame->scrolling]); - if (frame->border) - fprintf(stderr, "border %x ", - (unsigned int) frame->border_colour); - - fprintf(stderr, "\n"); - - if (frame->children) { - for (row = 0; row != frame->rows; row++) { - for (col = 0; col != frame->cols; col++) { - for (i = 0; i != depth; i++) - fprintf(stderr, " "); - fprintf(stderr, "(%i %i): ", row, col); - index = (row * frame->cols) + col; - html_dump_frameset(&frame->children[index], - depth + 1); - } - } - } -} - -#endif - -/** - * Retrieve HTML document tree - * - * \param h HTML content to retrieve document tree from - * \return Pointer to document tree - */ -dom_document *html_get_document(hlcache_handle *h) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - - return c->document; -} - -/** - * Retrieve box tree - * - * \param h HTML content to retrieve tree from - * \return Pointer to box tree - * - * \todo This API must die, as must all use of the box tree outside render/ - */ -struct box *html_get_box_tree(hlcache_handle *h) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - - return c->layout; -} - -/** - * Retrieve the charset of an HTML document - * - * \param c Content to retrieve charset from - * \param op The content encoding operation to perform. - * \return Pointer to charset, or NULL - */ -static const char *html_encoding(const struct content *c, enum content_encoding_type op) -{ - html_content *html = (html_content *) c; - static char enc_token[10] = "Encoding0"; - - assert(html != NULL); - - if (op == CONTENT_ENCODING_SOURCE) { - enc_token[8] = '0' + html->encoding_source; - return messages_get(enc_token); - } - - return html->encoding; -} - - -/** - * Retrieve framesets used in an HTML document - * - * \param h Content to inspect - * \return Pointer to framesets, or NULL if none - */ -struct content_html_frames *html_get_frameset(hlcache_handle *h) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - - return c->frameset; -} - -/** - * Retrieve iframes used in an HTML document - * - * \param h Content to inspect - * \return Pointer to iframes, or NULL if none - */ -struct content_html_iframe *html_get_iframe(hlcache_handle *h) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - - return c->iframe; -} - -/** - * Retrieve an HTML content's base URL - * - * \param h Content to retrieve base target from - * \return Pointer to URL - */ -nsurl *html_get_base_url(hlcache_handle *h) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - - return c->base_url; -} - -/** - * Retrieve an HTML content's base target - * - * \param h Content to retrieve base target from - * \return Pointer to target, or NULL if none - */ -const char *html_get_base_target(hlcache_handle *h) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - - return c->base_target; -} - - -/** - * Retrieve layout coordinates of box with given id - * - * \param h HTML document to search - * \param frag_id String containing an element id - * \param x Updated to global x coord iff id found - * \param y Updated to global y coord iff id found - * \return true iff id found - */ -bool html_get_id_offset(hlcache_handle *h, lwc_string *frag_id, int *x, int *y) -{ - struct box *pos; - struct box *layout; - - if (content_get_type(h) != CONTENT_HTML) - return false; - - layout = html_get_box_tree(h); - - if ((pos = box_find_by_id(layout, frag_id)) != 0) { - box_coords(pos, x, y); - return true; - } - return false; -} - -/** - * Compute the type of a content - * - * \return CONTENT_HTML - */ -static content_type html_content_type(void) -{ - return CONTENT_HTML; -} - - -static void html_fini(void) -{ - html_css_fini(); -} - -static const content_handler html_content_handler = { - .fini = html_fini, - .create = html_create, - .process_data = html_process_data, - .data_complete = html_convert, - .reformat = html_reformat, - .destroy = html_destroy, - .stop = html_stop, - .mouse_track = html_mouse_track, - .mouse_action = html_mouse_action, - .keypress = html_keypress, - .redraw = html_redraw, - .open = html_open, - .close = html_close, - .get_selection = html_get_selection, - .clear_selection = html_clear_selection, - .get_contextual_content = html_get_contextual_content, - .scroll_at_point = html_scroll_at_point, - .drop_file_at_point = html_drop_file_at_point, - .search = html_search, - .search_clear = html_search_clear, - .debug_dump = html_debug_dump, - .debug = html_debug, - .clone = html_clone, - .get_encoding = html_encoding, - .type = html_content_type, - .no_share = true, -}; - -nserror html_init(void) -{ - uint32_t i; - nserror error; - - error = html_css_init(); - if (error != NSERROR_OK) - goto error; - - for (i = 0; i < NOF_ELEMENTS(html_types); i++) { - error = content_factory_register_handler(html_types[i], - &html_content_handler); - if (error != NSERROR_OK) - goto error; - } - - return NSERROR_OK; - -error: - html_fini(); - - return error; -} - -/** - * Get the browser window containing an HTML content - * - * \param c HTML content - * \return the browser window - */ -struct browser_window *html_get_browser_window(struct content *c) -{ - html_content *html = (html_content *) c; - - assert(c != NULL); - assert(c->handler == &html_content_handler); - - return html->bw; -} diff --git a/render/html.h b/render/html.h deleted file mode 100644 index 30219a3bd..000000000 --- a/render/html.h +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2004 James Bursa - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Content for text/html (interface). - * - * These functions should in general be called via the content interface. - */ - -#ifndef _NETSURF_RENDER_HTML_H_ -#define _NETSURF_RENDER_HTML_H_ - -#include - -#include -#include - -#include "netsurf/types.h" -#include "netsurf/content_type.h" -#include "netsurf/browser_window.h" -#include "netsurf/mouse.h" -#include "desktop/frame_types.h" - -struct fetch_multipart_data; -struct box; -struct rect; -struct browser_window; -struct content; -struct hlcache_handle; -struct http_parameter; -struct imagemap; -struct object_params; -struct plotters; -struct textarea; -struct scrollbar; -struct scrollbar_msg_data; -struct search_context; -struct selection; -struct nsurl; -struct plot_font_style; - -/** - * Container for stylesheets used by an HTML document - */ -struct html_stylesheet { - struct dom_node *node; /**< dom node associated with sheet */ - struct hlcache_handle *sheet; - bool modified; - bool unused; -}; - -/** - * Container for scripts used by an HTML document - */ -struct html_script { - /** Type of script */ - enum html_script_type { HTML_SCRIPT_INLINE, - HTML_SCRIPT_SYNC, - HTML_SCRIPT_DEFER, - HTML_SCRIPT_ASYNC } type; - union { - struct hlcache_handle *handle; - struct dom_string *string; - } data; /**< Script data */ - struct dom_string *mimetype; - struct dom_string *encoding; - bool already_started; - bool parser_inserted; - bool force_async; - bool ready_exec; - bool async; - bool defer; -}; - - -/** An object (img, object, etc. tag) in a CONTENT_HTML document. */ -struct content_html_object { - struct content *parent; /**< Parent document */ - struct content_html_object *next; /**< Next in chain */ - - struct hlcache_handle *content; /**< Content, or 0. */ - struct box *box; /**< Node in box tree containing it. */ - /** Bitmap of acceptable content types */ - content_type permitted_types; - bool background; /**< This object is a background image. */ -}; - -struct html_scrollbar_data { - struct content *c; - struct box *box; -}; - -/** Frame tree (frameset or frame tag) */ -struct content_html_frames { - int cols; /** number of columns in frameset */ - int rows; /** number of rows in frameset */ - - struct frame_dimension width; /** frame width */ - struct frame_dimension height; /** frame width */ - int margin_width; /** frame margin width */ - int margin_height; /** frame margin height */ - - char *name; /** frame name (for targetting) */ - struct nsurl *url; /** frame url */ - - bool no_resize; /** frame is not resizable */ - browser_scrolling scrolling; /** scrolling characteristics */ - bool border; /** frame has a border */ - colour border_colour; /** frame border colour */ - - struct content_html_frames *children; /** [cols * rows] children */ -}; - -/** Inline frame list (iframe tag) */ -struct content_html_iframe { - struct box *box; - - int margin_width; /** frame margin width */ - int margin_height; /** frame margin height */ - - char *name; /** frame name (for targetting) */ - struct nsurl *url; /** frame url */ - - browser_scrolling scrolling; /** scrolling characteristics */ - bool border; /** frame has a border */ - colour border_colour; /** frame border colour */ - - struct content_html_iframe *next; -}; - -/* entries in stylesheet_content */ -#define STYLESHEET_BASE 0 /* base style sheet */ -#define STYLESHEET_QUIRKS 1 /* quirks mode stylesheet */ -#define STYLESHEET_ADBLOCK 2 /* adblocking stylesheet */ -#define STYLESHEET_USER 3 /* user stylesheet */ -#define STYLESHEET_START 4 /* start of document stylesheets */ - -nserror html_init(void); - -void html_redraw_a_box(struct hlcache_handle *h, struct box *box); - -void html_overflow_scroll_drag_end(struct scrollbar *scrollbar, - browser_mouse_state mouse, int x, int y); - -bool text_redraw(const char *utf8_text, size_t utf8_len, - size_t offset, int space, - const struct plot_font_style *fstyle, - int x, int y, - const struct rect *clip, - int height, - float scale, - bool excluded, - struct content *c, - const struct selection *sel, - struct search_context *search, - const struct redraw_context *ctx); - -dom_document *html_get_document(struct hlcache_handle *h); -struct box *html_get_box_tree(struct hlcache_handle *h); -struct content_html_frames *html_get_frameset(struct hlcache_handle *h); -struct content_html_iframe *html_get_iframe(struct hlcache_handle *h); -struct nsurl *html_get_base_url(struct hlcache_handle *h); -const char *html_get_base_target(struct hlcache_handle *h); -void html_set_file_gadget_filename(struct hlcache_handle *hl, - struct form_control *gadget, const char *fn); - -/** - * Retrieve stylesheets used by HTML document - * - * \param h Content to retrieve stylesheets from - * \param n Pointer to location to receive number of sheets - * \return Pointer to array of stylesheets - */ -struct html_stylesheet *html_get_stylesheets(struct hlcache_handle *h, - unsigned int *n); - -struct content_html_object *html_get_objects(struct hlcache_handle *h, - unsigned int *n); -bool html_get_id_offset(struct hlcache_handle *h, lwc_string *frag_id, - int *x, int *y); - -#endif diff --git a/render/html_css.c b/render/html_css.c deleted file mode 100644 index 45bc16f56..000000000 --- a/render/html_css.c +++ /dev/null @@ -1,713 +0,0 @@ -/* - * Copyright 2013 Vincent Sanders - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Processing for html content css operations. - */ - -#include -#include -#include -#include -#include -#include - -#include "utils/nsoption.h" -#include "utils/corestrings.h" -#include "utils/config.h" -#include "utils/log.h" -#include "netsurf/misc.h" -#include "netsurf/content.h" -#include "content/hlcache.h" -#include "css/css.h" -#include "desktop/gui_internal.h" - -#include "render/html_internal.h" - -static nsurl *html_default_stylesheet_url; -static nsurl *html_adblock_stylesheet_url; -static nsurl *html_quirks_stylesheet_url; -static nsurl *html_user_stylesheet_url; - -static nserror css_error_to_nserror(css_error error) -{ - switch (error) { - case CSS_OK: - return NSERROR_OK; - - case CSS_NOMEM: - return NSERROR_NOMEM; - - case CSS_BADPARM: - return NSERROR_BAD_PARAMETER; - - case CSS_INVALID: - return NSERROR_INVALID; - - case CSS_FILENOTFOUND: - return NSERROR_NOT_FOUND; - - case CSS_NEEDDATA: - return NSERROR_NEED_DATA; - - case CSS_BADCHARSET: - return NSERROR_BAD_ENCODING; - - case CSS_EOF: - case CSS_IMPORTS_PENDING: - case CSS_PROPERTY_NOT_SET: - default: - break; - } - return NSERROR_CSS; -} - -/** - * Callback for fetchcache() for stylesheets. - */ - -static nserror -html_convert_css_callback(hlcache_handle *css, - const hlcache_event *event, - void *pw) -{ - html_content *parent = pw; - unsigned int i; - struct html_stylesheet *s; - - /* Find sheet */ - for (i = 0, s = parent->stylesheets; - i != parent->stylesheet_count; - i++, s++) { - if (s->sheet == css) - break; - } - - assert(i != parent->stylesheet_count); - - switch (event->type) { - - case CONTENT_MSG_DONE: - NSLOG(netsurf, INFO, "done stylesheet slot %d '%s'", i, - nsurl_access(hlcache_handle_get_url(css))); - parent->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); - break; - - case CONTENT_MSG_ERROR: - NSLOG(netsurf, INFO, "stylesheet %s failed: %s", - nsurl_access(hlcache_handle_get_url(css)), - event->data.error); - /* fall through */ - - case CONTENT_MSG_ERRORCODE: - hlcache_handle_release(css); - s->sheet = NULL; - parent->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); - content_add_error(&parent->base, "?", 0); - break; - - case CONTENT_MSG_POINTER: - /* Really don't want this to continue after the switch */ - return NSERROR_OK; - - default: - break; - } - - if (html_can_begin_conversion(parent)) { - html_begin_conversion(parent); - } - - return NSERROR_OK; -} - -static nserror -html_stylesheet_from_domnode(html_content *c, - dom_node *node, - hlcache_handle **sheet) -{ - hlcache_child_context child; - dom_string *style; - nsurl *url; - dom_exception exc; - nserror error; - uint32_t key; - char urlbuf[64]; - - child.charset = c->encoding; - child.quirks = c->base.quirks; - - exc = dom_node_get_text_content(node, &style); - if ((exc != DOM_NO_ERR) || (style == NULL)) { - NSLOG(netsurf, INFO, "No text content"); - return NSERROR_OK; - } - - error = html_css_fetcher_add_item(style, c->base_url, &key); - if (error != NSERROR_OK) { - dom_string_unref(style); - return error; - } - - dom_string_unref(style); - - snprintf(urlbuf, sizeof(urlbuf), "x-ns-css:%u", key); - - error = nsurl_create(urlbuf, &url); - if (error != NSERROR_OK) { - return error; - } - - error = hlcache_handle_retrieve(url, 0, - content_get_url(&c->base), NULL, - html_convert_css_callback, c, &child, CONTENT_CSS, - sheet); - if (error != NSERROR_OK) { - nsurl_unref(url); - return error; - } - - nsurl_unref(url); - - c->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - - return NSERROR_OK; -} - -/** - * Process an inline stylesheet in the document. - * - * \param c content structure - * \param style xml node of style element - * \return true on success, false if an error occurred - */ - -static struct html_stylesheet * -html_create_style_element(html_content *c, dom_node *style) -{ - dom_string *val; - dom_exception exc; - struct html_stylesheet *stylesheets; - - /* type='text/css', or not present (invalid but common) */ - exc = dom_element_get_attribute(style, corestring_dom_type, &val); - if (exc == DOM_NO_ERR && val != NULL) { - if (!dom_string_caseless_lwc_isequal(val, - corestring_lwc_text_css)) { - dom_string_unref(val); - return NULL; - } - dom_string_unref(val); - } - - /* media contains 'screen' or 'all' or not present */ - exc = dom_element_get_attribute(style, corestring_dom_media, &val); - if (exc == DOM_NO_ERR && val != NULL) { - if (strcasestr(dom_string_data(val), "screen") == NULL && - strcasestr(dom_string_data(val), - "all") == NULL) { - dom_string_unref(val); - return NULL; - } - dom_string_unref(val); - } - - /* Extend array */ - stylesheets = realloc(c->stylesheets, - sizeof(struct html_stylesheet) * - (c->stylesheet_count + 1)); - if (stylesheets == NULL) { - - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); - return false; - - } - c->stylesheets = stylesheets; - - c->stylesheets[c->stylesheet_count].node = dom_node_ref(style); - c->stylesheets[c->stylesheet_count].sheet = NULL; - c->stylesheets[c->stylesheet_count].modified = false; - c->stylesheets[c->stylesheet_count].unused = false; - c->stylesheet_count++; - - return c->stylesheets + (c->stylesheet_count - 1); -} - -static bool html_css_process_modified_style(html_content *c, - struct html_stylesheet *s) -{ - hlcache_handle *sheet = NULL; - nserror error; - - error = html_stylesheet_from_domnode(c, s->node, &sheet); - if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, "Failed to update sheet"); - content_broadcast_errorcode(&c->base, error); - return false; - } - - if (sheet != NULL) { - NSLOG(netsurf, INFO, "Updating sheet %p with %p", s->sheet, - sheet); - - if (s->sheet != NULL) { - switch (content_get_status(s->sheet)) { - case CONTENT_STATUS_DONE: - break; - default: - hlcache_handle_abort(s->sheet); - c->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", - c->base.active); - } - hlcache_handle_release(s->sheet); - } - s->sheet = sheet; - } - - s->modified = false; - - return true; -} - -static void html_css_process_modified_styles(void *pw) -{ - html_content *c = pw; - struct html_stylesheet *s; - unsigned int i; - bool all_done = true; - - for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) { - if (c->stylesheets[i].modified) { - all_done &= html_css_process_modified_style(c, s); - } - } - - /* If we failed to process any sheet, schedule a retry */ - if (all_done == false) { - guit->misc->schedule(1000, html_css_process_modified_styles, c); - } -} - -bool html_css_update_style(html_content *c, dom_node *style) -{ - unsigned int i; - struct html_stylesheet *s; - - /* Find sheet */ - for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) { - if (s->node == style) - break; - } - if (i == c->stylesheet_count) { - s = html_create_style_element(c, style); - } - if (s == NULL) { - NSLOG(netsurf, INFO, - "Could not find or create inline stylesheet for %p", - style); - return false; - } - - s->modified = true; - - guit->misc->schedule(0, html_css_process_modified_styles, c); - - return true; -} - -bool html_css_process_style(html_content *c, dom_node *node) -{ - unsigned int i; - dom_string *val; - dom_exception exc; - struct html_stylesheet *s; - - /* Find sheet */ - for (i = 0, s = c->stylesheets; i != c->stylesheet_count; i++, s++) { - if (s->node == node) - break; - } - - /* Should already exist */ - if (i == c->stylesheet_count) { - return false; - } - - exc = dom_element_get_attribute(node, corestring_dom_media, &val); - if (exc == DOM_NO_ERR && val != NULL) { - if (strcasestr(dom_string_data(val), "screen") == NULL && - strcasestr(dom_string_data(val), - "all") == NULL) { - s->unused = true; - } - dom_string_unref(val); - } - - return true; -} - -bool html_css_process_link(html_content *htmlc, dom_node *node) -{ - dom_string *rel, *type_attr, *media, *href; - struct html_stylesheet *stylesheets; - nsurl *joined; - dom_exception exc; - nserror ns_error; - hlcache_child_context child; - - /* rel= */ - exc = dom_element_get_attribute(node, corestring_dom_rel, &rel); - if (exc != DOM_NO_ERR || rel == NULL) - return true; - - if (strcasestr(dom_string_data(rel), "stylesheet") == 0) { - dom_string_unref(rel); - return true; - } else if (strcasestr(dom_string_data(rel), "alternate") != 0) { - /* Ignore alternate stylesheets */ - dom_string_unref(rel); - return true; - } - dom_string_unref(rel); - - /* type='text/css' or not present */ - exc = dom_element_get_attribute(node, corestring_dom_type, &type_attr); - if (exc == DOM_NO_ERR && type_attr != NULL) { - if (!dom_string_caseless_lwc_isequal(type_attr, - corestring_lwc_text_css)) { - dom_string_unref(type_attr); - return true; - } - dom_string_unref(type_attr); - } - - /* media contains 'screen' or 'all' or not present */ - exc = dom_element_get_attribute(node, corestring_dom_media, &media); - if (exc == DOM_NO_ERR && media != NULL) { - if (strcasestr(dom_string_data(media), "screen") == NULL && - strcasestr(dom_string_data(media), "all") == NULL) { - dom_string_unref(media); - return true; - } - dom_string_unref(media); - } - - /* href='...' */ - exc = dom_element_get_attribute(node, corestring_dom_href, &href); - if (exc != DOM_NO_ERR || href == NULL) - return true; - - /* TODO: only the first preferred stylesheets (ie. - * those with a title attribute) should be loaded - * (see HTML4 14.3) */ - - ns_error = nsurl_join(htmlc->base_url, dom_string_data(href), &joined); - if (ns_error != NSERROR_OK) { - dom_string_unref(href); - goto no_memory; - } - dom_string_unref(href); - - NSLOG(netsurf, INFO, "linked stylesheet %i '%s'", - htmlc->stylesheet_count, nsurl_access(joined)); - - /* extend stylesheets array to allow for new sheet */ - stylesheets = realloc(htmlc->stylesheets, - sizeof(struct html_stylesheet) * - (htmlc->stylesheet_count + 1)); - if (stylesheets == NULL) { - nsurl_unref(joined); - ns_error = NSERROR_NOMEM; - goto no_memory; - } - - htmlc->stylesheets = stylesheets; - htmlc->stylesheets[htmlc->stylesheet_count].node = NULL; - htmlc->stylesheets[htmlc->stylesheet_count].modified = false; - htmlc->stylesheets[htmlc->stylesheet_count].unused = false; - - /* start fetch */ - child.charset = htmlc->encoding; - child.quirks = htmlc->base.quirks; - - ns_error = hlcache_handle_retrieve(joined, 0, - content_get_url(&htmlc->base), - NULL, html_convert_css_callback, - htmlc, &child, CONTENT_CSS, - &htmlc->stylesheets[htmlc->stylesheet_count].sheet); - - nsurl_unref(joined); - - if (ns_error != NSERROR_OK) - goto no_memory; - - htmlc->stylesheet_count++; - - htmlc->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", htmlc->base.active); - - return true; - -no_memory: - content_broadcast_errorcode(&htmlc->base, ns_error); - return false; -} - -/* exported interface documented in render/html.h */ -struct html_stylesheet *html_get_stylesheets(hlcache_handle *h, unsigned int *n) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - assert(n != NULL); - - *n = c->stylesheet_count; - - return c->stylesheets; -} - - -/* exported interface documented in render/html_internal.h */ -nserror html_css_free_stylesheets(html_content *html) -{ - unsigned int i; - - guit->misc->schedule(-1, html_css_process_modified_styles, html); - - for (i = 0; i != html->stylesheet_count; i++) { - if (html->stylesheets[i].sheet != NULL) { - hlcache_handle_release(html->stylesheets[i].sheet); - } - if (html->stylesheets[i].node != NULL) { - dom_node_unref(html->stylesheets[i].node); - } - } - free(html->stylesheets); - - return NSERROR_OK; -} - -/* exported interface documented in render/html_internal.h */ -nserror html_css_quirks_stylesheets(html_content *c) -{ - nserror ns_error = NSERROR_OK; - hlcache_child_context child; - - assert(c->stylesheets != NULL); - - if (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL) { - child.charset = c->encoding; - child.quirks = c->base.quirks; - - ns_error = hlcache_handle_retrieve(html_quirks_stylesheet_url, - 0, content_get_url(&c->base), NULL, - html_convert_css_callback, c, &child, - CONTENT_CSS, - &c->stylesheets[STYLESHEET_QUIRKS].sheet); - if (ns_error != NSERROR_OK) { - return ns_error; - } - - c->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - } - - return ns_error; -} - -/* exported interface documented in render/html_internal.h */ -nserror html_css_new_stylesheets(html_content *c) -{ - nserror ns_error; - hlcache_child_context child; - - if (c->stylesheets != NULL) { - return NSERROR_OK; /* already initialised */ - } - - /* stylesheet 0 is the base style sheet, - * stylesheet 1 is the quirks mode style sheet, - * stylesheet 2 is the adblocking stylesheet, - * stylesheet 3 is the user stylesheet */ - c->stylesheets = calloc(STYLESHEET_START, - sizeof(struct html_stylesheet)); - if (c->stylesheets == NULL) { - return NSERROR_NOMEM; - } - - c->stylesheets[STYLESHEET_BASE].sheet = NULL; - c->stylesheets[STYLESHEET_QUIRKS].sheet = NULL; - c->stylesheets[STYLESHEET_ADBLOCK].sheet = NULL; - c->stylesheets[STYLESHEET_USER].sheet = NULL; - c->stylesheet_count = STYLESHEET_START; - - child.charset = c->encoding; - child.quirks = c->base.quirks; - - ns_error = hlcache_handle_retrieve(html_default_stylesheet_url, 0, - content_get_url(&c->base), NULL, - html_convert_css_callback, c, &child, CONTENT_CSS, - &c->stylesheets[STYLESHEET_BASE].sheet); - if (ns_error != NSERROR_OK) { - return ns_error; - } - - c->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - - - if (nsoption_bool(block_advertisements)) { - ns_error = hlcache_handle_retrieve(html_adblock_stylesheet_url, - 0, content_get_url(&c->base), NULL, - html_convert_css_callback, - c, &child, CONTENT_CSS, - &c->stylesheets[STYLESHEET_ADBLOCK].sheet); - if (ns_error != NSERROR_OK) { - return ns_error; - } - - c->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - - } - - ns_error = hlcache_handle_retrieve(html_user_stylesheet_url, 0, - content_get_url(&c->base), NULL, - html_convert_css_callback, c, &child, CONTENT_CSS, - &c->stylesheets[STYLESHEET_USER].sheet); - if (ns_error != NSERROR_OK) { - return ns_error; - } - - c->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - - return ns_error; -} - -nserror -html_css_new_selection_context(html_content *c, css_select_ctx **ret_select_ctx) -{ - uint32_t i; - css_error css_ret; - css_select_ctx *select_ctx; - - /* check that the base stylesheet loaded; layout fails without it */ - if (c->stylesheets[STYLESHEET_BASE].sheet == NULL) { - return NSERROR_CSS_BASE; - } - - /* Create selection context */ - css_ret = css_select_ctx_create(&select_ctx); - if (css_ret != CSS_OK) { - return css_error_to_nserror(css_ret); - } - - /* Add sheets to it */ - for (i = STYLESHEET_BASE; i != c->stylesheet_count; i++) { - const struct html_stylesheet *hsheet = &c->stylesheets[i]; - css_stylesheet *sheet = NULL; - css_origin origin = CSS_ORIGIN_AUTHOR; - - /* Filter out stylesheets for non-screen media. */ - if (hsheet->unused) { - continue; - } - - if (i < STYLESHEET_USER) { - origin = CSS_ORIGIN_UA; - } else if (i < STYLESHEET_START) { - origin = CSS_ORIGIN_USER; - } - - if (hsheet->sheet != NULL) { - sheet = nscss_get_stylesheet(hsheet->sheet); - } - - if (sheet != NULL) { - css_ret = css_select_ctx_append_sheet(select_ctx, - sheet, - origin, - CSS_MEDIA_SCREEN); - if (css_ret != CSS_OK) { - css_select_ctx_destroy(select_ctx); - return css_error_to_nserror(css_ret); - } - } - } - - /* return new selection context to caller */ - *ret_select_ctx = select_ctx; - return NSERROR_OK; -} - -nserror html_css_init(void) -{ - nserror error; - - error = html_css_fetcher_register(); - if (error != NSERROR_OK) - return error; - - error = nsurl_create("resource:default.css", - &html_default_stylesheet_url); - if (error != NSERROR_OK) - return error; - - error = nsurl_create("resource:adblock.css", - &html_adblock_stylesheet_url); - if (error != NSERROR_OK) - return error; - - error = nsurl_create("resource:quirks.css", - &html_quirks_stylesheet_url); - if (error != NSERROR_OK) - return error; - - error = nsurl_create("resource:user.css", - &html_user_stylesheet_url); - - return error; -} - -void html_css_fini(void) -{ - if (html_user_stylesheet_url != NULL) { - nsurl_unref(html_user_stylesheet_url); - html_user_stylesheet_url = NULL; - } - - if (html_quirks_stylesheet_url != NULL) { - nsurl_unref(html_quirks_stylesheet_url); - html_quirks_stylesheet_url = NULL; - } - - if (html_adblock_stylesheet_url != NULL) { - nsurl_unref(html_adblock_stylesheet_url); - html_adblock_stylesheet_url = NULL; - } - - if (html_default_stylesheet_url != NULL) { - nsurl_unref(html_default_stylesheet_url); - html_default_stylesheet_url = NULL; - } -} diff --git a/render/html_css_fetcher.c b/render/html_css_fetcher.c deleted file mode 100644 index 0f8809a42..000000000 --- a/render/html_css_fetcher.c +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2008 Rob Kendrick - * Copyright 2013 John-Mark Bell - * - * This file is part of NetSurf. - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include - -#include "netsurf/inttypes.h" -#include "utils/config.h" -#include "utils/log.h" -#include "utils/ring.h" -#include "utils/nsurl.h" -#include "utils/utils.h" -#include "content/fetch.h" -#include "content/fetchers.h" - -#include "render/html_internal.h" - -typedef struct html_css_fetcher_item { - uint32_t key; - dom_string *data; - nsurl *base_url; - - struct html_css_fetcher_item *r_next, *r_prev; -} html_css_fetcher_item; - -typedef struct html_css_fetcher_context { - struct fetch *parent_fetch; - - nsurl *url; - html_css_fetcher_item *item; - - bool aborted; - bool locked; - - struct html_css_fetcher_context *r_next, *r_prev; -} html_css_fetcher_context; - -static uint32_t current_key = 0; -static html_css_fetcher_item *items = NULL; -static html_css_fetcher_context *ring = NULL; - -static bool html_css_fetcher_initialise(lwc_string *scheme) -{ - NSLOG(netsurf, INFO, "html_css_fetcher_initialise called for %s", - lwc_string_data(scheme)); - return true; -} - -static void html_css_fetcher_finalise(lwc_string *scheme) -{ - NSLOG(netsurf, INFO, "html_css_fetcher_finalise called for %s", - lwc_string_data(scheme)); -} - -static bool html_css_fetcher_can_fetch(const nsurl *url) -{ - return true; -} - -static void *html_css_fetcher_setup(struct fetch *parent_fetch, nsurl *url, - bool only_2xx, bool downgrade_tls, const char *post_urlenc, - const struct fetch_multipart_data *post_multipart, - const char **headers) -{ - html_css_fetcher_context *ctx; - lwc_string *path; - uint32_t key; - html_css_fetcher_item *item, *found = NULL; - - /* format of a x-ns-css URL is: - * x-ns-url: - * Where key is an unsigned 32bit integer - */ - - path = nsurl_get_component(url, NSURL_PATH); - /* The path must exist */ - if (path == NULL) { - return NULL; - } - - key = strtoul(lwc_string_data(path), NULL, 10); - - lwc_string_unref(path); - - /* There must be at least one item */ - if (items == NULL) { - return NULL; - } - - item = items; - do { - if (item->key == key) { - found = item; - break; - } - - item = item->r_next; - } while (item != items); - - /* We must have found the item */ - if (found == NULL) { - return NULL; - } - - ctx = calloc(1, sizeof(*ctx)); - if (ctx == NULL) - return NULL; - - ctx->parent_fetch = parent_fetch; - ctx->url = nsurl_ref(url); - ctx->item = found; - - RING_INSERT(ring, ctx); - - return ctx; -} - -static bool html_css_fetcher_start(void *ctx) -{ - return true; -} - -static void html_css_fetcher_free(void *ctx) -{ - html_css_fetcher_context *c = ctx; - - nsurl_unref(c->url); - if (c->item != NULL) { - nsurl_unref(c->item->base_url); - dom_string_unref(c->item->data); - RING_REMOVE(items, c->item); - free(c->item); - } - RING_REMOVE(ring, c); - free(ctx); -} - -static void html_css_fetcher_abort(void *ctx) -{ - html_css_fetcher_context *c = ctx; - - /* To avoid the poll loop having to deal with the fetch context - * disappearing from under it, we simply flag the abort here. - * The poll loop itself will perform the appropriate cleanup. - */ - c->aborted = true; -} - -static void html_css_fetcher_send_callback(const fetch_msg *msg, - html_css_fetcher_context *c) -{ - c->locked = true; - fetch_send_callback(msg, c->parent_fetch); - c->locked = false; -} - -static void html_css_fetcher_poll(lwc_string *scheme) -{ - fetch_msg msg; - html_css_fetcher_context *c, *next; - - if (ring == NULL) return; - - /* Iterate over ring, processing each pending fetch */ - c = ring; - do { - /* Ignore fetches that have been flagged as locked. - * This allows safe re-entrant calls to this function. - * Re-entrancy can occur if, as a result of a callback, - * the interested party causes fetch_poll() to be called - * again. - */ - if (c->locked == true) { - next = c->r_next; - continue; - } - - /* Only process non-aborted fetches */ - if (c->aborted) { - /* Nothing to do */ - assert(c->locked == false); - } else if (c->item != NULL) { - char header[4096]; - - fetch_set_http_code(c->parent_fetch, 200); - - /* Any callback can result in the fetch being aborted. - * Therefore, we _must_ check for this after _every_ - * call to html_css_fetcher_send_callback(). - */ - snprintf(header, sizeof header, - "Content-Type: text/css; charset=utf-8"); - msg.type = FETCH_HEADER; - msg.data.header_or_data.buf = (const uint8_t *) header; - msg.data.header_or_data.len = strlen(header); - html_css_fetcher_send_callback(&msg, c); - - if (c->aborted == false) { - snprintf(header, sizeof header, - "Content-Length: %"PRIsizet, - dom_string_byte_length(c->item->data)); - msg.type = FETCH_HEADER; - msg.data.header_or_data.buf = - (const uint8_t *) header; - msg.data.header_or_data.len = strlen(header); - html_css_fetcher_send_callback(&msg, c); - } - - if (c->aborted == false) { - snprintf(header, sizeof header, - "X-NS-Base: %.*s", - (int) nsurl_length(c->item->base_url), - nsurl_access(c->item->base_url)); - msg.type = FETCH_HEADER; - msg.data.header_or_data.buf = - (const uint8_t *) header; - msg.data.header_or_data.len = strlen(header); - html_css_fetcher_send_callback(&msg, c); - } - - if (c->aborted == false) { - msg.type = FETCH_DATA; - msg.data.header_or_data.buf = - (const uint8_t *) - dom_string_data(c->item->data); - msg.data.header_or_data.len = - dom_string_byte_length(c->item->data); - html_css_fetcher_send_callback(&msg, c); - } - - if (c->aborted == false) { - msg.type = FETCH_FINISHED; - html_css_fetcher_send_callback(&msg, c); - } - } else { - NSLOG(netsurf, INFO, "Processing of %s failed!", - nsurl_access(c->url)); - - /* Ensure that we're unlocked here. If we aren't, - * then html_css_fetcher_process() is broken. - */ - assert(c->locked == false); - } - - /* Compute next fetch item at the last possible moment as - * processing this item may have added to the ring. - */ - next = c->r_next; - - fetch_remove_from_queues(c->parent_fetch); - fetch_free(c->parent_fetch); - - /* Advance to next ring entry, exiting if we've reached - * the start of the ring or the ring has become empty - */ - } while ( (c = next) != ring && ring != NULL); -} - -/* exported interface documented in html_internal.h */ -nserror html_css_fetcher_register(void) -{ - lwc_string *scheme; - const struct fetcher_operation_table html_css_fetcher_ops = { - .initialise = html_css_fetcher_initialise, - .acceptable = html_css_fetcher_can_fetch, - .setup = html_css_fetcher_setup, - .start = html_css_fetcher_start, - .abort = html_css_fetcher_abort, - .free = html_css_fetcher_free, - .poll = html_css_fetcher_poll, - .finalise = html_css_fetcher_finalise - }; - - if (lwc_intern_string("x-ns-css", SLEN("x-ns-css"), - &scheme) != lwc_error_ok) { - NSLOG(netsurf, INFO, "could not intern \"x-ns-css\"."); - return NSERROR_INIT_FAILED; - } - - return fetcher_add(scheme, &html_css_fetcher_ops); -} - -/* exported interface documented in html_internal.h */ -nserror -html_css_fetcher_add_item(dom_string *data, nsurl *base_url, uint32_t *key) -{ - html_css_fetcher_item *item = malloc(sizeof(*item)); - - if (item == NULL) { - return NSERROR_NOMEM; - } - - *key = item->key = current_key++; - item->data = dom_string_ref(data); - item->base_url = nsurl_ref(base_url); - - RING_INSERT(items, item); - - return NSERROR_OK; -} - diff --git a/render/html_forms.c b/render/html_forms.c deleted file mode 100644 index 39bc690d9..000000000 --- a/render/html_forms.c +++ /dev/null @@ -1,575 +0,0 @@ -/* - * Copyright 2011 Vincent Sanders - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "utils/config.h" -#include "utils/corestrings.h" -#include "utils/log.h" - -#include "render/form_internal.h" -#include "render/html_internal.h" - -/** - * process form element from dom - */ -static struct form * -parse_form_element(const char *docenc, dom_node *node) -{ - dom_string *ds_action = NULL; - dom_string *ds_charset = NULL; - dom_string *ds_target = NULL; - dom_string *ds_method = NULL; - dom_string *ds_enctype = NULL; - char *action = NULL, *charset = NULL, *target = NULL; - form_method method; - dom_html_form_element *formele = (dom_html_form_element *)(node); - struct form * ret = NULL; - - /* Retrieve the attributes from the node */ - if (dom_html_form_element_get_action(formele, - &ds_action) != DOM_NO_ERR) - goto out; - - if (dom_html_form_element_get_accept_charset(formele, - &ds_charset) != DOM_NO_ERR) - goto out; - - if (dom_html_form_element_get_target(formele, - &ds_target) != DOM_NO_ERR) - goto out; - - if (dom_html_form_element_get_method(formele, - &ds_method) != DOM_NO_ERR) - goto out; - - if (dom_html_form_element_get_enctype(formele, - &ds_enctype) != DOM_NO_ERR) - goto out; - - /* Extract the plain attributes ready for use. We have to do this - * because we cannot guarantee that the dom_strings are NULL terminated - * and thus we copy them. - */ - if (ds_action != NULL) - action = strndup(dom_string_data(ds_action), - dom_string_byte_length(ds_action)); - - if (ds_charset != NULL) - charset = strndup(dom_string_data(ds_charset), - dom_string_byte_length(ds_charset)); - - if (ds_target != NULL) - target = strndup(dom_string_data(ds_target), - dom_string_byte_length(ds_target)); - - /* Determine the method */ - method = method_GET; - if (ds_method != NULL) { - if (dom_string_caseless_lwc_isequal(ds_method, - corestring_lwc_post)) { - method = method_POST_URLENC; - if (ds_enctype != NULL) { - if (dom_string_caseless_lwc_isequal(ds_enctype, - corestring_lwc_multipart_form_data)) { - - method = method_POST_MULTIPART; - } - } - } - } - - /* Construct the form object */ - ret = form_new(node, action, target, method, charset, docenc); - -out: - if (ds_action != NULL) - dom_string_unref(ds_action); - if (ds_charset != NULL) - dom_string_unref(ds_charset); - if (ds_target != NULL) - dom_string_unref(ds_target); - if (ds_method != NULL) - dom_string_unref(ds_method); - if (ds_enctype != NULL) - dom_string_unref(ds_enctype); - if (action != NULL) - free(action); - if (charset != NULL) - free(charset); - if (target != NULL) - free(target); - return ret; -} - -/* documented in html_internal.h */ -struct form *html_forms_get_forms(const char *docenc, dom_html_document *doc) -{ - dom_html_collection *forms; - struct form *ret = NULL, *newf; - dom_node *node; - unsigned long n; - uint32_t nforms; - - if (doc == NULL) - return NULL; - - /* Attempt to build a set of all the forms */ - if (dom_html_document_get_forms(doc, &forms) != DOM_NO_ERR) - return NULL; - - /* Count the number of forms so we can iterate */ - if (dom_html_collection_get_length(forms, &nforms) != DOM_NO_ERR) - goto out; - - /* Iterate the forms collection, making form structs for returning */ - for (n = 0; n < nforms; ++n) { - if (dom_html_collection_item(forms, n, &node) != DOM_NO_ERR) { - goto out; - } - newf = parse_form_element(docenc, node); - dom_node_unref(node); - if (newf == NULL) { - goto err; - } - newf->prev = ret; - ret = newf; - } - - /* All went well */ - goto out; -err: - while (ret != NULL) { - struct form *prev = ret->prev; - /* Destroy ret */ - free(ret); - ret = prev; - } -out: - /* Finished with the collection, return it */ - dom_html_collection_unref(forms); - - return ret; -} - -static struct form * -find_form(struct form *forms, dom_html_form_element *form) -{ - while (forms != NULL) { - if (forms->node == form) - break; - forms = forms->prev; - } - - return forms; -} - -static struct form_control * -parse_button_element(struct form *forms, dom_html_button_element *button) -{ - struct form_control *control = NULL; - dom_exception err; - dom_html_form_element *form = NULL; - dom_string *ds_type = NULL; - dom_string *ds_value = NULL; - dom_string *ds_name = NULL; - - err = dom_html_button_element_get_form(button, &form); - if (err != DOM_NO_ERR) - goto out; - - err = dom_html_button_element_get_type(button, &ds_type); - if (err != DOM_NO_ERR) - goto out; - - if (ds_type == NULL) { - control = form_new_control(button, GADGET_SUBMIT); - } else { - if (dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_submit)) { - control = form_new_control(button, GADGET_SUBMIT); - } else if (dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_reset)) { - control = form_new_control(button, GADGET_RESET); - } else { - control = form_new_control(button, GADGET_BUTTON); - } - } - - if (control == NULL) - goto out; - - err = dom_html_button_element_get_value(button, &ds_value); - if (err != DOM_NO_ERR) - goto out; - err = dom_html_button_element_get_name(button, &ds_name); - if (err != DOM_NO_ERR) - goto out; - - if (ds_value != NULL) { - control->value = strndup( - dom_string_data(ds_value), - dom_string_byte_length(ds_value)); - - if (control->value == NULL) { - form_free_control(control); - control = NULL; - goto out; - } - } - - if (ds_name != NULL) { - control->name = strndup( - dom_string_data(ds_name), - dom_string_byte_length(ds_name)); - - if (control->name == NULL) { - form_free_control(control); - control = NULL; - goto out; - } - } - - if (form != NULL && control != NULL) - form_add_control(find_form(forms, form), control); - -out: - if (form != NULL) - dom_node_unref(form); - if (ds_type != NULL) - dom_string_unref(ds_type); - if (ds_value != NULL) - dom_string_unref(ds_value); - if (ds_name != NULL) - dom_string_unref(ds_name); - - return control; -} - -static struct form_control * -parse_input_element(struct form *forms, dom_html_input_element *input) -{ - struct form_control *control = NULL; - dom_html_form_element *form = NULL; - dom_string *ds_type = NULL; - dom_string *ds_name = NULL; - dom_string *ds_value = NULL; - - char *name = NULL; - - if (dom_html_input_element_get_form(input, &form) != DOM_NO_ERR) - goto out; - - if (dom_html_input_element_get_type(input, &ds_type) != DOM_NO_ERR) - goto out; - - if (dom_html_input_element_get_name(input, &ds_name) != DOM_NO_ERR) - goto out; - - if (ds_name != NULL) - name = strndup(dom_string_data(ds_name), - dom_string_byte_length(ds_name)); - - if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_password)) { - control = form_new_control(input, GADGET_PASSWORD); - } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_file)) { - control = form_new_control(input, GADGET_FILE); - } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_hidden)) { - control = form_new_control(input, GADGET_HIDDEN); - } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_checkbox)) { - control = form_new_control(input, GADGET_CHECKBOX); - } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_radio)) { - control = form_new_control(input, GADGET_RADIO); - } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_submit)) { - control = form_new_control(input, GADGET_SUBMIT); - } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_reset)) { - control = form_new_control(input, GADGET_RESET); - } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_button)) { - control = form_new_control(input, GADGET_BUTTON); - } else if (ds_type != NULL && dom_string_caseless_lwc_isequal(ds_type, - corestring_lwc_image)) { - control = form_new_control(input, GADGET_IMAGE); - } else { - control = form_new_control(input, GADGET_TEXTBOX); - } - - if (control == NULL) - goto out; - - if (name != NULL) { - /* Hand the name string over */ - control->name = name; - name = NULL; - } - - if (control->type == GADGET_CHECKBOX || control->type == GADGET_RADIO) { - bool selected; - if (dom_html_input_element_get_checked( - input, &selected) == DOM_NO_ERR) { - control->selected = selected; - } - } - - if (control->type == GADGET_PASSWORD || - control->type == GADGET_TEXTBOX) { - int32_t maxlength; - if (dom_html_input_element_get_max_length( - input, &maxlength) != DOM_NO_ERR) { - maxlength = -1; - } - - if (maxlength >= 0) { - /* Got valid maxlength */ - control->maxlength = maxlength; - } else { - /* Input has no maxlength attr, or - * dom_html_input_element_get_max_length failed. - * - * Set it to something insane. */ - control->maxlength = UINT_MAX; - } - } - - if (control->type != GADGET_FILE && control->type != GADGET_IMAGE) { - if (dom_html_input_element_get_value( - input, &ds_value) == DOM_NO_ERR) { - if (ds_value != NULL) { - control->value = strndup( - dom_string_data(ds_value), - dom_string_byte_length(ds_value)); - if (control->value == NULL) { - form_free_control(control); - control = NULL; - goto out; - } - control->length = strlen(control->value); - } - } - - if (control->type == GADGET_TEXTBOX || - control->type == GADGET_PASSWORD) { - if (control->value == NULL) { - control->value = strdup(""); - if (control->value == NULL) { - form_free_control(control); - control = NULL; - goto out; - } - - control->length = 0; - } - - control->initial_value = strdup(control->value); - if (control->initial_value == NULL) { - form_free_control(control); - control = NULL; - goto out; - } - } - } - - if (form != NULL && control != NULL) - form_add_control(find_form(forms, form), control); - -out: - if (form != NULL) - dom_node_unref(form); - if (ds_type != NULL) - dom_string_unref(ds_type); - if (ds_name != NULL) - dom_string_unref(ds_name); - if (ds_value != NULL) - dom_string_unref(ds_value); - - if (name != NULL) - free(name); - - return control; -} - -static struct form_control * -parse_textarea_element(struct form *forms, dom_html_text_area_element *ta) -{ - struct form_control *control = NULL; - dom_html_form_element *form = NULL; - dom_string *ds_name = NULL; - - char *name = NULL; - - if (dom_html_text_area_element_get_form(ta, &form) != DOM_NO_ERR) - goto out; - - if (dom_html_text_area_element_get_name(ta, &ds_name) != DOM_NO_ERR) - goto out; - - if (ds_name != NULL) - name = strndup(dom_string_data(ds_name), - dom_string_byte_length(ds_name)); - - control = form_new_control(ta, GADGET_TEXTAREA); - - if (control == NULL) - goto out; - - if (name != NULL) { - /* Hand the name string over */ - control->name = name; - name = NULL; - } - - if (form != NULL && control != NULL) - form_add_control(find_form(forms, form), control); - -out: - if (form != NULL) - dom_node_unref(form); - if (ds_name != NULL) - dom_string_unref(ds_name); - - if (name != NULL) - free(name); - - - return control; -} - -static struct form_control * -parse_select_element(struct form *forms, dom_html_select_element *select) -{ - struct form_control *control = NULL; - dom_html_form_element *form = NULL; - dom_string *ds_name = NULL; - - char *name = NULL; - - if (dom_html_select_element_get_form(select, &form) != DOM_NO_ERR) - goto out; - - if (dom_html_select_element_get_name(select, &ds_name) != DOM_NO_ERR) - goto out; - - if (ds_name != NULL) - name = strndup(dom_string_data(ds_name), - dom_string_byte_length(ds_name)); - - control = form_new_control(select, GADGET_SELECT); - - if (control == NULL) - goto out; - - if (name != NULL) { - /* Hand the name string over */ - control->name = name; - name = NULL; - } - - dom_html_select_element_get_multiple(select, - &(control->data.select.multiple)); - - if (form != NULL && control != NULL) - form_add_control(find_form(forms, form), control); - -out: - if (form != NULL) - dom_node_unref(form); - if (ds_name != NULL) - dom_string_unref(ds_name); - - if (name != NULL) - free(name); - - - return control; -} - - -static struct form_control * -invent_fake_gadget(dom_node *node) -{ - struct form_control *ctl = form_new_control(node, GADGET_HIDDEN); - if (ctl != NULL) { - ctl->value = strdup(""); - ctl->initial_value = strdup(""); - ctl->name = strdup("foo"); - - if (ctl->value == NULL || ctl->initial_value == NULL || - ctl->name == NULL) { - form_free_control(ctl); - ctl = NULL; - } - } - return ctl; -} - -/* documented in html_internal.h */ -struct form_control *html_forms_get_control_for_node(struct form *forms, - dom_node *node) -{ - struct form *f; - struct form_control *ctl = NULL; - dom_exception err; - dom_string *ds_name = NULL; - - /* Step one, see if we already have a control */ - for (f = forms; f != NULL; f = f->prev) { - for (ctl = f->controls; ctl != NULL; ctl = ctl->next) { - if (ctl->node == node) - return ctl; - } - } - - /* Step two, extract the node's name so we can construct a gadget. */ - err = dom_element_get_tag_name(node, &ds_name); - if (err == DOM_NO_ERR && ds_name != NULL) { - - /* Step three, attempt to work out what gadget to make */ - if (dom_string_caseless_lwc_isequal(ds_name, - corestring_lwc_button)) { - ctl = parse_button_element(forms, - (dom_html_button_element *) node); - } else if (dom_string_caseless_lwc_isequal(ds_name, - corestring_lwc_input)) { - ctl = parse_input_element(forms, - (dom_html_input_element *) node); - } else if (dom_string_caseless_lwc_isequal(ds_name, - corestring_lwc_textarea)) { - ctl = parse_textarea_element(forms, - (dom_html_text_area_element *) node); - } else if (dom_string_caseless_lwc_isequal(ds_name, - corestring_lwc_select)) { - ctl = parse_select_element(forms, - (dom_html_select_element *) node); - } - } - - /* If all else fails, fake gadget time */ - if (ctl == NULL) - ctl = invent_fake_gadget(node); - - if (ds_name != NULL) - dom_string_unref(ds_name); - - return ctl; -} - diff --git a/render/html_interaction.c b/render/html_interaction.c deleted file mode 100644 index 2d14ed2ae..000000000 --- a/render/html_interaction.c +++ /dev/null @@ -1,1434 +0,0 @@ -/* - * Copyright 2006 James Bursa - * Copyright 2006 Richard Wilson - * Copyright 2008 Michael Drake - * Copyright 2009 Paul Blokus - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * User interaction with a CONTENT_HTML (implementation). - */ - -#include -#include - -#include - -#include "utils/corestrings.h" -#include "utils/messages.h" -#include "utils/utils.h" -#include "utils/log.h" -#include "utils/nsoption.h" -#include "netsurf/content.h" -#include "netsurf/browser_window.h" -#include "netsurf/mouse.h" -#include "netsurf/misc.h" -#include "netsurf/layout.h" -#include "netsurf/keypress.h" -#include "content/hlcache.h" -#include "desktop/frames.h" -#include "desktop/scrollbar.h" -#include "desktop/selection.h" -#include "desktop/textarea.h" -#include "javascript/js.h" -#include "desktop/gui_internal.h" - -#include "render/box.h" -#include "render/box_textarea.h" -#include "render/font.h" -#include "render/form_internal.h" -#include "render/html_internal.h" -#include "render/imagemap.h" -#include "render/search.h" - -/** - * Get pointer shape for given box - * - * \param box box in question - * \param imagemap whether an imagemap applies to the box - */ - -static browser_pointer_shape get_pointer_shape(struct box *box, bool imagemap) -{ - browser_pointer_shape pointer; - css_computed_style *style; - enum css_cursor_e cursor; - lwc_string **cursor_uris; - - if (box->type == BOX_FLOAT_LEFT || box->type == BOX_FLOAT_RIGHT) - style = box->children->style; - else - style = box->style; - - if (style == NULL) - return BROWSER_POINTER_DEFAULT; - - cursor = css_computed_cursor(style, &cursor_uris); - - switch (cursor) { - case CSS_CURSOR_AUTO: - if (box->href || (box->gadget && - (box->gadget->type == GADGET_IMAGE || - box->gadget->type == GADGET_SUBMIT)) || - imagemap) { - /* link */ - pointer = BROWSER_POINTER_POINT; - } else if (box->gadget && - (box->gadget->type == GADGET_TEXTBOX || - box->gadget->type == GADGET_PASSWORD || - box->gadget->type == GADGET_TEXTAREA)) { - /* text input */ - pointer = BROWSER_POINTER_CARET; - } else { - /* html content doesn't mind */ - pointer = BROWSER_POINTER_AUTO; - } - break; - case CSS_CURSOR_CROSSHAIR: - pointer = BROWSER_POINTER_CROSS; - break; - case CSS_CURSOR_POINTER: - pointer = BROWSER_POINTER_POINT; - break; - case CSS_CURSOR_MOVE: - pointer = BROWSER_POINTER_MOVE; - break; - case CSS_CURSOR_E_RESIZE: - pointer = BROWSER_POINTER_RIGHT; - break; - case CSS_CURSOR_W_RESIZE: - pointer = BROWSER_POINTER_LEFT; - break; - case CSS_CURSOR_N_RESIZE: - pointer = BROWSER_POINTER_UP; - break; - case CSS_CURSOR_S_RESIZE: - pointer = BROWSER_POINTER_DOWN; - break; - case CSS_CURSOR_NE_RESIZE: - pointer = BROWSER_POINTER_RU; - break; - case CSS_CURSOR_SW_RESIZE: - pointer = BROWSER_POINTER_LD; - break; - case CSS_CURSOR_SE_RESIZE: - pointer = BROWSER_POINTER_RD; - break; - case CSS_CURSOR_NW_RESIZE: - pointer = BROWSER_POINTER_LU; - break; - case CSS_CURSOR_TEXT: - pointer = BROWSER_POINTER_CARET; - break; - case CSS_CURSOR_WAIT: - pointer = BROWSER_POINTER_WAIT; - break; - case CSS_CURSOR_PROGRESS: - pointer = BROWSER_POINTER_PROGRESS; - break; - case CSS_CURSOR_HELP: - pointer = BROWSER_POINTER_HELP; - break; - default: - pointer = BROWSER_POINTER_DEFAULT; - break; - } - - return pointer; -} - - -/** - * Start drag scrolling the contents of a box - * - * \param box the box to be scrolled - * \param x x ordinate of initial mouse position - * \param y y ordinate - */ - -static void html_box_drag_start(struct box *box, int x, int y) -{ - int box_x, box_y; - int scroll_mouse_x, scroll_mouse_y; - - box_coords(box, &box_x, &box_y); - - if (box->scroll_x != NULL) { - scroll_mouse_x = x - box_x ; - scroll_mouse_y = y - (box_y + box->padding[TOP] + - box->height + box->padding[BOTTOM] - - SCROLLBAR_WIDTH); - scrollbar_start_content_drag(box->scroll_x, - scroll_mouse_x, scroll_mouse_y); - } else if (box->scroll_y != NULL) { - scroll_mouse_x = x - (box_x + box->padding[LEFT] + - box->width + box->padding[RIGHT] - - SCROLLBAR_WIDTH); - scroll_mouse_y = y - box_y; - - scrollbar_start_content_drag(box->scroll_y, - scroll_mouse_x, scroll_mouse_y); - } -} - - -/** - * End overflow scroll scrollbar drags - * - * \param html html content - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - * \param dir Direction of drag - */ -static size_t html_selection_drag_end(struct html_content *html, - browser_mouse_state mouse, int x, int y, int dir) -{ - int pixel_offset; - struct box *box; - int dx, dy; - size_t idx = 0; - - box = box_pick_text_box(html, x, y, dir, &dx, &dy); - if (box) { - plot_font_style_t fstyle; - - font_plot_style_from_css(&html->len_ctx, box->style, &fstyle); - - guit->layout->position(&fstyle, box->text, box->length, - dx, &idx, &pixel_offset); - - idx += box->byte_offset; - } - - return idx; -} - - -/** - * Handle mouse tracking (including drags) in an HTML content window. - * - * \param c content of type html - * \param bw browser window - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - */ - -void html_mouse_track(struct content *c, struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - html_mouse_action(c, bw, mouse, x, y); -} - -/** - * Helper for file gadgets to store their filename. - * - * Stores the filename unencoded on the dom node associated with the - * gadget. - * - * \todo Get rid of this crap eventually - * - * \param operation DOM operation - * \param key DOM node key being considerd - * \param _data The data assocated with the key - * \param src The source DOM node. - * \param dst The destination DOM node. - */ -static void -html__image_coords_dom_user_data_handler(dom_node_operation operation, - dom_string *key, - void *_data, - struct dom_node *src, - struct dom_node *dst) -{ - struct image_input_coords *oldcoords, *coords = _data, *newcoords; - - if (!dom_string_isequal(corestring_dom___ns_key_image_coords_node_data, - key) || coords == NULL) { - return; - } - - switch (operation) { - case DOM_NODE_CLONED: - newcoords = calloc(1, sizeof(*newcoords)); - if (newcoords != NULL) { - *newcoords = *coords; - if (dom_node_set_user_data(dst, - corestring_dom___ns_key_image_coords_node_data, - newcoords, - html__image_coords_dom_user_data_handler, - &oldcoords) == DOM_NO_ERR) { - free(oldcoords); - } - } - break; - - case DOM_NODE_DELETED: - free(coords); - break; - - case DOM_NODE_RENAMED: - case DOM_NODE_IMPORTED: - case DOM_NODE_ADOPTED: - break; - - default: - NSLOG(netsurf, INFO, "User data operation not handled."); - assert(0); - } -} - -/** - * Handle mouse clicks and movements in an HTML content window. - * - * \param c content of type html - * \param bw browser window - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - * - * This function handles both hovering and clicking. It is important that the - * code path is identical (except that hovering doesn't carry out the action), - * so that the status bar reflects exactly what will happen. Having separate - * code paths opens the possibility that an attacker will make the status bar - * show some harmless action where clicking will be harmful. - */ - -void html_mouse_action(struct content *c, struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - html_content *html = (html_content *) c; - enum { ACTION_NONE, ACTION_SUBMIT, ACTION_GO } action = ACTION_NONE; - const char *title = 0; - nsurl *url = 0; - char *url_s = NULL; - size_t url_l = 0; - const char *target = 0; - char status_buffer[200]; - const char *status = 0; - browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; - bool imagemap = false; - int box_x = 0, box_y = 0; - int gadget_box_x = 0, gadget_box_y = 0; - int html_object_pos_x = 0, html_object_pos_y = 0; - int text_box_x = 0; - struct box *url_box = 0; - struct box *gadget_box = 0; - struct box *text_box = 0; - struct box *box; - struct form_control *gadget = 0; - hlcache_handle *object = NULL; - struct box *html_object_box = NULL; - struct browser_window *iframe = NULL; - struct box *drag_candidate = NULL; - struct scrollbar *scrollbar = NULL; - plot_font_style_t fstyle; - int scroll_mouse_x = 0, scroll_mouse_y = 0; - int padding_left, padding_right, padding_top, padding_bottom; - browser_drag_type drag_type = browser_window_get_drag_type(bw); - union content_msg_data msg_data; - struct dom_node *node = NULL; - union html_drag_owner drag_owner; - union html_selection_owner sel_owner; - bool click = mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2 | - BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2 | - BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2); - - if (drag_type != DRAGGING_NONE && !mouse && - html->visible_select_menu != NULL) { - /* drag end: select menu */ - form_select_mouse_drag_end(html->visible_select_menu, - mouse, x, y); - } - - if (html->visible_select_menu != NULL) { - box = html->visible_select_menu->box; - box_coords(box, &box_x, &box_y); - - box_x -= box->border[LEFT].width; - box_y += box->height + box->border[BOTTOM].width + - box->padding[BOTTOM] + box->padding[TOP]; - status = form_select_mouse_action(html->visible_select_menu, - mouse, x - box_x, y - box_y); - if (status != NULL) { - msg_data.explicit_status_text = status; - content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); - } else { - int width, height; - form_select_get_dimensions(html->visible_select_menu, - &width, &height); - html->visible_select_menu = NULL; - browser_window_redraw_rect(bw, box_x, box_y, - width, height); - } - return; - } - - if (html->drag_type == HTML_DRAG_SELECTION) { - /* Selection drag */ - struct box *box; - int dir = -1; - int dx, dy; - - if (!mouse) { - /* End of selection drag */ - int dir = -1; - size_t idx; - - if (selection_dragging_start(&html->sel)) - dir = 1; - - idx = html_selection_drag_end(html, mouse, x, y, dir); - - if (idx != 0) - selection_track(&html->sel, mouse, idx); - - drag_owner.no_owner = true; - html_set_drag_type(html, HTML_DRAG_NONE, - drag_owner, NULL); - return; - } - - if (selection_dragging_start(&html->sel)) - dir = 1; - - box = box_pick_text_box(html, x, y, dir, &dx, &dy); - - if (box != NULL) { - int pixel_offset; - size_t idx; - plot_font_style_t fstyle; - - font_plot_style_from_css(&html->len_ctx, - box->style, &fstyle); - - guit->layout->position(&fstyle, - box->text, box->length, - dx, &idx, &pixel_offset); - - selection_track(&html->sel, mouse, - box->byte_offset + idx); - } - return; - } - - if (html->drag_type == HTML_DRAG_SCROLLBAR) { - struct scrollbar *scr = html->drag_owner.scrollbar; - struct html_scrollbar_data *data = scrollbar_get_data(scr); - - if (!mouse) { - /* drag end: scrollbar */ - html_overflow_scroll_drag_end(scr, mouse, x, y); - } - - box = data->box; - box_coords(box, &box_x, &box_y); - if (scrollbar_is_horizontal(scr)) { - scroll_mouse_x = x - box_x ; - scroll_mouse_y = y - (box_y + box->padding[TOP] + - box->height + box->padding[BOTTOM] - - SCROLLBAR_WIDTH); - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action(scr, mouse, - scroll_mouse_x, - scroll_mouse_y)); - } else { - scroll_mouse_x = x - (box_x + box->padding[LEFT] + - box->width + box->padding[RIGHT] - - SCROLLBAR_WIDTH); - scroll_mouse_y = y - box_y; - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action(scr, mouse, - scroll_mouse_x, - scroll_mouse_y)); - } - - msg_data.explicit_status_text = status; - content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); - return; - } - - if (html->drag_type == HTML_DRAG_TEXTAREA_SELECTION || - html->drag_type == HTML_DRAG_TEXTAREA_SCROLLBAR) { - box = html->drag_owner.textarea; - assert(box->gadget != NULL); - assert(box->gadget->type == GADGET_TEXTAREA || - box->gadget->type == GADGET_PASSWORD || - box->gadget->type == GADGET_TEXTBOX); - - box_coords(box, &box_x, &box_y); - textarea_mouse_action(box->gadget->data.text.ta, mouse, - x - box_x, y - box_y); - - /* TODO: Set appropriate statusbar message */ - return; - } - - if (html->drag_type == HTML_DRAG_CONTENT_SELECTION || - html->drag_type == HTML_DRAG_CONTENT_SCROLL) { - box = html->drag_owner.content; - assert(box->object != NULL); - - box_coords(box, &box_x, &box_y); - content_mouse_track(box->object, bw, mouse, - x - box_x, y - box_y); - return; - } - - if (html->drag_type == HTML_DRAG_CONTENT_SELECTION) { - box = html->drag_owner.content; - assert(box->object != NULL); - - box_coords(box, &box_x, &box_y); - content_mouse_track(box->object, bw, mouse, - x - box_x, y - box_y); - return; - } - - /* Content related drags handled by now */ - assert(html->drag_type == HTML_DRAG_NONE); - - /* search the box tree for a link, imagemap, form control, or - * box with scrollbars - */ - - box = html->layout; - - /* Consider the margins of the html page now */ - box_x = box->margin[LEFT]; - box_y = box->margin[TOP]; - - /* descend through visible boxes setting more specific values for: - * box - deepest box at point - * html_object_box - html object - * html_object_pos_x - html object - * html_object_pos_y - html object - * object - non html object - * iframe - iframe - * url - href or imagemap - * target - href or imagemap or gadget - * url_box - href or imagemap - * imagemap - imagemap - * gadget - gadget - * gadget_box - gadget - * gadget_box_x - gadget - * gadget_box_y - gadget - * title - title - * pointer - * - * drag_candidate - first box with scroll - * padding_left - box with scroll - * padding_right - * padding_top - * padding_bottom - * scrollbar - inside padding box stops decent - * scroll_mouse_x - inside padding box stops decent - * scroll_mouse_y - inside padding box stops decent - * - * text_box - text box - * text_box_x - text_box - */ - do { - if ((box->style != NULL) && - (css_computed_visibility(box->style) == - CSS_VISIBILITY_HIDDEN)) { - continue; - } - - if (box->node != NULL) { - node = box->node; - } - - if (box->object) { - if (content_get_type(box->object) == CONTENT_HTML) { - html_object_box = box; - html_object_pos_x = box_x; - html_object_pos_y = box_y; - } else { - object = box->object; - } - } - - if (box->iframe) { - iframe = box->iframe; - } - - if (box->href) { - url = box->href; - target = box->target; - url_box = box; - } - - if (box->usemap) { - url = imagemap_get(html, box->usemap, - box_x, box_y, x, y, &target); - if (url) { - imagemap = true; - url_box = box; - } - } - - if (box->gadget) { - gadget = box->gadget; - gadget_box = box; - gadget_box_x = box_x; - gadget_box_y = box_y; - if (gadget->form) - target = gadget->form->target; - } - - if (box->title) { - title = box->title; - } - - pointer = get_pointer_shape(box, false); - - if ((box->scroll_x != NULL) || - (box->scroll_y != NULL)) { - - if (drag_candidate == NULL) { - drag_candidate = box; - } - - padding_left = box_x + - scrollbar_get_offset(box->scroll_x); - padding_right = padding_left + box->padding[LEFT] + - box->width + box->padding[RIGHT]; - padding_top = box_y + - scrollbar_get_offset(box->scroll_y); - padding_bottom = padding_top + box->padding[TOP] + - box->height + box->padding[BOTTOM]; - - if ((x > padding_left) && - (x < padding_right) && - (y > padding_top) && - (y < padding_bottom)) { - /* mouse inside padding box */ - - if ((box->scroll_y != NULL) && - (x > (padding_right - - SCROLLBAR_WIDTH))) { - /* mouse above vertical box scroll */ - - scrollbar = box->scroll_y; - scroll_mouse_x = x - (padding_right - - SCROLLBAR_WIDTH); - scroll_mouse_y = y - padding_top; - break; - - } else if ((box->scroll_x != NULL) && - (y > (padding_bottom - - SCROLLBAR_WIDTH))) { - /* mouse above horizontal box scroll */ - - scrollbar = box->scroll_x; - scroll_mouse_x = x - padding_left; - scroll_mouse_y = y - (padding_bottom - - SCROLLBAR_WIDTH); - break; - } - } - } - - if (box->text && !box->object) { - text_box = box; - text_box_x = box_x; - } - } while ((box = box_at_point(&html->len_ctx, box, x, y, - &box_x, &box_y)) != NULL); - - /* use of box_x, box_y, or content below this point is probably a - * mistake; they will refer to the last box returned by box_at_point */ - assert(node != NULL); - - if (scrollbar) { - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action(scrollbar, mouse, - scroll_mouse_x, - scroll_mouse_y)); - pointer = BROWSER_POINTER_DEFAULT; - } else if (gadget) { - textarea_mouse_status ta_status; - - switch (gadget->type) { - case GADGET_SELECT: - status = messages_get("FormSelect"); - pointer = BROWSER_POINTER_MENU; - if (mouse & BROWSER_MOUSE_CLICK_1 && - nsoption_bool(core_select_menu)) { - html->visible_select_menu = gadget; - form_open_select_menu(c, gadget, - form_select_menu_callback, - c); - pointer = BROWSER_POINTER_DEFAULT; - } else if (mouse & BROWSER_MOUSE_CLICK_1) { - msg_data.select_menu.gadget = gadget; - content_broadcast(c, CONTENT_MSG_SELECTMENU, - &msg_data); - } - break; - case GADGET_CHECKBOX: - status = messages_get("FormCheckbox"); - if (mouse & BROWSER_MOUSE_CLICK_1) { - gadget->selected = !gadget->selected; - dom_html_input_element_set_checked( - (dom_html_input_element *)(gadget->node), - gadget->selected); - html__redraw_a_box(html, gadget_box); - } - break; - case GADGET_RADIO: - status = messages_get("FormRadio"); - if (mouse & BROWSER_MOUSE_CLICK_1) - form_radio_set(gadget); - break; - case GADGET_IMAGE: - /* This falls through to SUBMIT */ - if (mouse & BROWSER_MOUSE_CLICK_1) { - struct image_input_coords *coords, *oldcoords; - /** \todo Find a way to not ignore errors */ - coords = calloc(1, sizeof(*coords)); - if (coords == NULL) { - return; - } - coords->x = x - gadget_box_x; - coords->y = y - gadget_box_y; - if (dom_node_set_user_data( - gadget->node, - corestring_dom___ns_key_image_coords_node_data, - coords, html__image_coords_dom_user_data_handler, - &oldcoords) != DOM_NO_ERR) - return; - free(oldcoords); - } - /* Fall through */ - case GADGET_SUBMIT: - if (gadget->form) { - snprintf(status_buffer, sizeof status_buffer, - messages_get("FormSubmit"), - gadget->form->action); - status = status_buffer; - pointer = get_pointer_shape(gadget_box, false); - if (mouse & (BROWSER_MOUSE_CLICK_1 | - BROWSER_MOUSE_CLICK_2)) - action = ACTION_SUBMIT; - } else { - status = messages_get("FormBadSubmit"); - } - break; - case GADGET_TEXTBOX: - case GADGET_PASSWORD: - case GADGET_TEXTAREA: - if (gadget->type == GADGET_TEXTAREA) - status = messages_get("FormTextarea"); - else - status = messages_get("FormTextbox"); - - if (click && (html->selection_type != - HTML_SELECTION_TEXTAREA || - html->selection_owner.textarea != - gadget_box)) { - sel_owner.none = true; - html_set_selection(html, HTML_SELECTION_NONE, - sel_owner, true); - } - - ta_status = textarea_mouse_action(gadget->data.text.ta, - mouse, x - gadget_box_x, - y - gadget_box_y); - - if (ta_status & TEXTAREA_MOUSE_EDITOR) { - pointer = get_pointer_shape(gadget_box, false); - } else { - pointer = BROWSER_POINTER_DEFAULT; - status = scrollbar_mouse_status_to_message( - ta_status >> 3); - } - break; - case GADGET_HIDDEN: - /* not possible: no box generated */ - break; - case GADGET_RESET: - status = messages_get("FormReset"); - break; - case GADGET_FILE: - status = messages_get("FormFile"); - if (mouse & BROWSER_MOUSE_CLICK_1) { - msg_data.gadget_click.gadget = gadget; - content_broadcast(c, CONTENT_MSG_GADGETCLICK, - &msg_data); - } - break; - case GADGET_BUTTON: - /* This gadget cannot be activated */ - status = messages_get("FormButton"); - break; - } - - } else if (object && (mouse & BROWSER_MOUSE_MOD_2)) { - - if (mouse & BROWSER_MOUSE_DRAG_2) { - msg_data.dragsave.type = CONTENT_SAVE_NATIVE; - msg_data.dragsave.content = object; - content_broadcast(c, CONTENT_MSG_DRAGSAVE, &msg_data); - - } else if (mouse & BROWSER_MOUSE_DRAG_1) { - msg_data.dragsave.type = CONTENT_SAVE_ORIG; - msg_data.dragsave.content = object; - content_broadcast(c, CONTENT_MSG_DRAGSAVE, &msg_data); - } - - /* \todo should have a drag-saving object msg */ - - } else if (iframe) { - int pos_x, pos_y; - float scale = browser_window_get_scale(bw); - - browser_window_get_position(iframe, false, &pos_x, &pos_y); - - pos_x /= scale; - pos_y /= scale; - - if (mouse & BROWSER_MOUSE_CLICK_1 || - mouse & BROWSER_MOUSE_CLICK_2) { - browser_window_mouse_click(iframe, mouse, - x - pos_x, y - pos_y); - } else { - browser_window_mouse_track(iframe, mouse, - x - pos_x, y - pos_y); - } - } else if (html_object_box) { - - if (click && (html->selection_type != HTML_SELECTION_CONTENT || - html->selection_owner.content != - html_object_box)) { - sel_owner.none = true; - html_set_selection(html, HTML_SELECTION_NONE, - sel_owner, true); - } - if (mouse & BROWSER_MOUSE_CLICK_1 || - mouse & BROWSER_MOUSE_CLICK_2) { - content_mouse_action(html_object_box->object, - bw, mouse, - x - html_object_pos_x, - y - html_object_pos_y); - } else { - content_mouse_track(html_object_box->object, - bw, mouse, - x - html_object_pos_x, - y - html_object_pos_y); - } - } else if (url) { - if (nsoption_bool(display_decoded_idn) == true) { - if (nsurl_get_utf8(url, &url_s, &url_l) != NSERROR_OK) { - /* Unable to obtain a decoded IDN. This is not a fatal error. - * Ensure the string pointer is NULL so we use the encoded version. */ - url_s = NULL; - } - } - - if (title) { - snprintf(status_buffer, sizeof status_buffer, "%s: %s", - url_s ? url_s : nsurl_access(url), title); - } else { - snprintf(status_buffer, sizeof status_buffer, "%s", - url_s ? url_s : nsurl_access(url)); - } - - status = status_buffer; - - if (url_s != NULL) - free(url_s); - - pointer = get_pointer_shape(url_box, imagemap); - - if (mouse & BROWSER_MOUSE_CLICK_1 && - mouse & BROWSER_MOUSE_MOD_1) { - /* force download of link */ - browser_window_navigate(bw, - url, - content_get_url(c), - BW_NAVIGATE_DOWNLOAD, - NULL, - NULL, - NULL); - - } else if (mouse & BROWSER_MOUSE_CLICK_2 && - mouse & BROWSER_MOUSE_MOD_1) { - msg_data.savelink.url = url; - msg_data.savelink.title = title; - content_broadcast(c, CONTENT_MSG_SAVELINK, &msg_data); - - } else if (mouse & (BROWSER_MOUSE_CLICK_1 | - BROWSER_MOUSE_CLICK_2)) - action = ACTION_GO; - } else { - bool done = false; - - /* frame resizing */ - if (browser_window_frame_resize_start(bw, mouse, x, y, - &pointer)) { - if (mouse & (BROWSER_MOUSE_DRAG_1 | - BROWSER_MOUSE_DRAG_2)) { - status = messages_get("FrameDrag"); - } - done = true; - } - - /* if clicking in the main page, remove the selection from any - * text areas */ - if (!done) { - - if (click && html->focus_type != HTML_FOCUS_SELF) { - union html_focus_owner fo; - fo.self = true; - html_set_focus(html, HTML_FOCUS_SELF, fo, - true, 0, 0, 0, NULL); - } - if (click && html->selection_type != - HTML_SELECTION_SELF) { - sel_owner.none = true; - html_set_selection(html, HTML_SELECTION_NONE, - sel_owner, true); - } - - if (text_box) { - int pixel_offset; - size_t idx; - - font_plot_style_from_css(&html->len_ctx, - text_box->style, &fstyle); - - guit->layout->position(&fstyle, - text_box->text, - text_box->length, - x - text_box_x, - &idx, - &pixel_offset); - - if (selection_click(&html->sel, mouse, - text_box->byte_offset + idx)) { - /* key presses must be directed at the - * main browser window, paste text - * operations ignored */ - html_drag_type drag_type; - union html_drag_owner drag_owner; - - if (selection_dragging(&html->sel)) { - drag_type = HTML_DRAG_SELECTION; - drag_owner.no_owner = true; - html_set_drag_type(html, - drag_type, - drag_owner, - NULL); - status = messages_get( - "Selecting"); - } - - done = true; - } - - } else if (mouse & BROWSER_MOUSE_PRESS_1) { - sel_owner.none = true; - selection_clear(&html->sel, true); - } - - if (selection_defined(&html->sel)) { - sel_owner.none = false; - html_set_selection(html, HTML_SELECTION_SELF, - sel_owner, true); - } else if (click && html->selection_type != - HTML_SELECTION_NONE) { - sel_owner.none = true; - html_set_selection(html, HTML_SELECTION_NONE, - sel_owner, true); - } - } - - if (!done) { - if (title) - status = title; - - if (mouse & BROWSER_MOUSE_DRAG_1) { - if (mouse & BROWSER_MOUSE_MOD_2) { - msg_data.dragsave.type = - CONTENT_SAVE_COMPLETE; - msg_data.dragsave.content = NULL; - content_broadcast(c, - CONTENT_MSG_DRAGSAVE, - &msg_data); - } else { - if (drag_candidate == NULL) { - browser_window_page_drag_start( - bw, x, y); - } else { - html_box_drag_start( - drag_candidate, - x, y); - } - pointer = BROWSER_POINTER_MOVE; - } - } - else if (mouse & BROWSER_MOUSE_DRAG_2) { - if (mouse & BROWSER_MOUSE_MOD_2) { - msg_data.dragsave.type = - CONTENT_SAVE_SOURCE; - msg_data.dragsave.content = NULL; - content_broadcast(c, - CONTENT_MSG_DRAGSAVE, - &msg_data); - } else { - if (drag_candidate == NULL) { - browser_window_page_drag_start( - bw, x, y); - } else { - html_box_drag_start( - drag_candidate, - x, y); - } - pointer = BROWSER_POINTER_MOVE; - } - } - } - if (mouse && mouse < BROWSER_MOUSE_MOD_1) { - /* ensure key presses still act on the browser window */ - union html_focus_owner fo; - fo.self = true; - html_set_focus(html, HTML_FOCUS_SELF, fo, - true, 0, 0, 0, NULL); - } - } - - if (!iframe && !html_object_box) { - msg_data.explicit_status_text = status; - content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); - - msg_data.pointer = pointer; - content_broadcast(c, CONTENT_MSG_POINTER, &msg_data); - } - - /* fire dom click event */ - if (mouse & BROWSER_MOUSE_CLICK_1) { - fire_dom_event(corestring_dom_click, node, true, true); - } - - /* deferred actions that can cause this browser_window to be destroyed - * and must therefore be done after set_status/pointer - */ - switch (action) { - case ACTION_SUBMIT: - form_submit(content_get_url(c), - browser_window_find_target(bw, target, mouse), - gadget->form, gadget); - break; - case ACTION_GO: - browser_window_navigate(browser_window_find_target(bw, target, mouse), - url, - content_get_url(c), - BW_NAVIGATE_HISTORY, - NULL, - NULL, - NULL); - break; - case ACTION_NONE: - break; - } -} - - -/** - * Handle keypresses. - * - * \param c content of type HTML - * \param key The UCS4 character codepoint - * \return true if key handled, false otherwise - */ - -bool html_keypress(struct content *c, uint32_t key) -{ - html_content *html = (html_content *) c; - struct selection *sel = &html->sel; - struct box *box; - - switch (html->focus_type) { - case HTML_FOCUS_CONTENT: - box = html->focus_owner.content; - return content_keypress(box->object, key); - - case HTML_FOCUS_TEXTAREA: - box = html->focus_owner.textarea; - return box_textarea_keypress(html, box, key); - - default: - /* Deal with it below */ - break; - } - - switch (key) { - case NS_KEY_COPY_SELECTION: - selection_copy_to_clipboard(sel); - return true; - - case NS_KEY_CLEAR_SELECTION: - selection_clear(sel, true); - return true; - - case NS_KEY_SELECT_ALL: - selection_select_all(sel); - return true; - - case NS_KEY_ESCAPE: - if (selection_defined(sel)) { - selection_clear(sel, true); - return true; - } - - /* if there's no selection, leave Escape for the caller */ - return false; - } - - return false; -} - - -/** - * Handle search. - * - * \param c content of type HTML - * \param context front end private data - * \param flags search flags - * \param string search string - */ -void html_search(struct content *c, void *context, - search_flags_t flags, const char *string) -{ - html_content *html = (html_content *)c; - - assert(c != NULL); - - if (string != NULL && html->search_string != NULL && - strcmp(string, html->search_string) == 0 && - html->search != NULL) { - /* Continue prev. search */ - search_step(html->search, flags, string); - - } else if (string != NULL) { - /* New search */ - free(html->search_string); - html->search_string = strdup(string); - if (html->search_string == NULL) - return; - - if (html->search != NULL) { - search_destroy_context(html->search); - html->search = NULL; - } - - html->search = search_create_context(c, CONTENT_HTML, context); - - if (html->search == NULL) - return; - - search_step(html->search, flags, string); - - } else { - /* Clear search */ - html_search_clear(c); - - free(html->search_string); - html->search_string = NULL; - } -} - - -/** - * Terminate a search. - * - * \param c content of type HTML - */ -void html_search_clear(struct content *c) -{ - html_content *html = (html_content *)c; - - assert(c != NULL); - - free(html->search_string); - html->search_string = NULL; - - if (html->search != NULL) { - search_destroy_context(html->search); - } - html->search = NULL; -} - - -/** - * Callback for in-page scrollbars. - */ -void html_overflow_scroll_callback(void *client_data, - struct scrollbar_msg_data *scrollbar_data) -{ - struct html_scrollbar_data *data = client_data; - html_content *html = (html_content *)data->c; - struct box *box = data->box; - union content_msg_data msg_data; - html_drag_type drag_type; - union html_drag_owner drag_owner; - - switch(scrollbar_data->msg) { - case SCROLLBAR_MSG_MOVED: - - if (html->reflowing == true) { - /* Can't redraw during layout, and it will - * be redrawn after layout anyway. */ - break; - } - - html__redraw_a_box(html, box); - break; - case SCROLLBAR_MSG_SCROLL_START: - { - struct rect rect = { - .x0 = scrollbar_data->x0, - .y0 = scrollbar_data->y0, - .x1 = scrollbar_data->x1, - .y1 = scrollbar_data->y1 - }; - drag_type = HTML_DRAG_SCROLLBAR; - drag_owner.scrollbar = scrollbar_data->scrollbar; - html_set_drag_type(html, drag_type, drag_owner, &rect); - } - break; - case SCROLLBAR_MSG_SCROLL_FINISHED: - drag_type = HTML_DRAG_NONE; - drag_owner.no_owner = true; - html_set_drag_type(html, drag_type, drag_owner, NULL); - - msg_data.pointer = BROWSER_POINTER_AUTO; - content_broadcast(data->c, CONTENT_MSG_POINTER, &msg_data); - break; - } -} - - -/** - * End overflow scroll scrollbar drags - * - * \param scrollbar scrollbar widget - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - */ -void html_overflow_scroll_drag_end(struct scrollbar *scrollbar, - browser_mouse_state mouse, int x, int y) -{ - int scroll_mouse_x, scroll_mouse_y, box_x, box_y; - struct html_scrollbar_data *data = scrollbar_get_data(scrollbar); - struct box *box; - - box = data->box; - box_coords(box, &box_x, &box_y); - - if (scrollbar_is_horizontal(scrollbar)) { - scroll_mouse_x = x - box_x; - scroll_mouse_y = y - (box_y + box->padding[TOP] + - box->height + box->padding[BOTTOM] - - SCROLLBAR_WIDTH); - scrollbar_mouse_drag_end(scrollbar, mouse, - scroll_mouse_x, scroll_mouse_y); - } else { - scroll_mouse_x = x - (box_x + box->padding[LEFT] + - box->width + box->padding[RIGHT] - - SCROLLBAR_WIDTH); - scroll_mouse_y = y - box_y; - scrollbar_mouse_drag_end(scrollbar, mouse, - scroll_mouse_x, scroll_mouse_y); - } -} - -/* Documented in html_internal.h */ -void html_set_drag_type(html_content *html, html_drag_type drag_type, - union html_drag_owner drag_owner, const struct rect *rect) -{ - union content_msg_data msg_data; - - assert(html != NULL); - - html->drag_type = drag_type; - html->drag_owner = drag_owner; - - switch (drag_type) { - case HTML_DRAG_NONE: - assert(drag_owner.no_owner == true); - msg_data.drag.type = CONTENT_DRAG_NONE; - break; - - case HTML_DRAG_SCROLLBAR: - case HTML_DRAG_TEXTAREA_SCROLLBAR: - case HTML_DRAG_CONTENT_SCROLL: - msg_data.drag.type = CONTENT_DRAG_SCROLL; - break; - - case HTML_DRAG_SELECTION: - assert(drag_owner.no_owner == true); - /* Fall through */ - case HTML_DRAG_TEXTAREA_SELECTION: - case HTML_DRAG_CONTENT_SELECTION: - msg_data.drag.type = CONTENT_DRAG_SELECTION; - break; - } - msg_data.drag.rect = rect; - - /* Inform of the content's drag status change */ - content_broadcast((struct content *)html, CONTENT_MSG_DRAG, &msg_data); -} - -/* Documented in html_internal.h */ -void html_set_focus(html_content *html, html_focus_type focus_type, - union html_focus_owner focus_owner, bool hide_caret, - int x, int y, int height, const struct rect *clip) -{ - union content_msg_data msg_data; - int x_off = 0; - int y_off = 0; - struct rect cr; - bool textarea_lost_focus = html->focus_type == HTML_FOCUS_TEXTAREA && - focus_type != HTML_FOCUS_TEXTAREA; - - assert(html != NULL); - - switch (focus_type) { - case HTML_FOCUS_SELF: - assert(focus_owner.self == true); - if (html->focus_type == HTML_FOCUS_SELF) - /* Don't need to tell anyone anything */ - return; - break; - - case HTML_FOCUS_CONTENT: - box_coords(focus_owner.content, &x_off, &y_off); - break; - - case HTML_FOCUS_TEXTAREA: - box_coords(focus_owner.textarea, &x_off, &y_off); - break; - } - - html->focus_type = focus_type; - html->focus_owner = focus_owner; - - if (textarea_lost_focus) { - msg_data.caret.type = CONTENT_CARET_REMOVE; - } else if (focus_type != HTML_FOCUS_SELF && hide_caret) { - msg_data.caret.type = CONTENT_CARET_HIDE; - } else { - if (clip != NULL) { - cr = *clip; - cr.x0 += x_off; - cr.y0 += y_off; - cr.x1 += x_off; - cr.y1 += y_off; - } - - msg_data.caret.type = CONTENT_CARET_SET_POS; - msg_data.caret.pos.x = x + x_off; - msg_data.caret.pos.y = y + y_off; - msg_data.caret.pos.height = height; - msg_data.caret.pos.clip = (clip == NULL) ? NULL : &cr; - } - - /* Inform of the content's drag status change */ - content_broadcast((struct content *)html, CONTENT_MSG_CARET, &msg_data); -} - -/* Documented in html_internal.h */ -void html_set_selection(html_content *html, html_selection_type selection_type, - union html_selection_owner selection_owner, bool read_only) -{ - union content_msg_data msg_data; - struct box *box; - bool changed = false; - bool same_type = html->selection_type == selection_type; - - assert(html != NULL); - - if ((selection_type == HTML_SELECTION_NONE && - html->selection_type != HTML_SELECTION_NONE) || - (selection_type != HTML_SELECTION_NONE && - html->selection_type == HTML_SELECTION_NONE)) - /* Existance of selection has changed, and we'll need to - * inform our owner */ - changed = true; - - /* Clear any existing selection */ - if (html->selection_type != HTML_SELECTION_NONE) { - switch (html->selection_type) { - case HTML_SELECTION_SELF: - if (same_type) - break; - selection_clear(&html->sel, true); - break; - case HTML_SELECTION_TEXTAREA: - if (same_type && html->selection_owner.textarea == - selection_owner.textarea) - break; - box = html->selection_owner.textarea; - textarea_clear_selection(box->gadget->data.text.ta); - break; - case HTML_SELECTION_CONTENT: - if (same_type && html->selection_owner.content == - selection_owner.content) - break; - box = html->selection_owner.content; - content_clear_selection(box->object); - break; - default: - break; - } - } - - html->selection_type = selection_type; - html->selection_owner = selection_owner; - - if (!changed) - /* Don't need to report lack of change to owner */ - return; - - /* Prepare msg */ - switch (selection_type) { - case HTML_SELECTION_NONE: - assert(selection_owner.none == true); - msg_data.selection.selection = false; - break; - case HTML_SELECTION_SELF: - assert(selection_owner.none == false); - /* fall through */ - case HTML_SELECTION_TEXTAREA: - case HTML_SELECTION_CONTENT: - msg_data.selection.selection = true; - break; - default: - break; - } - msg_data.selection.read_only = read_only; - - /* Inform of the content's selection status change */ - content_broadcast((struct content *)html, CONTENT_MSG_SELECTION, - &msg_data); -} diff --git a/render/html_internal.h b/render/html_internal.h deleted file mode 100644 index 66ecb2b36..000000000 --- a/render/html_internal.h +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright 2004 James Bursa - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * Content for text/html (private data). - */ - -#ifndef NETSURF_RENDER_HTML_INTERNAL_H_ -#define NETSURF_RENDER_HTML_INTERNAL_H_ - -#include - -#include "content/handlers/css/utils.h" -#include "content/content_protected.h" -#include "desktop/selection.h" -#include "render/html.h" - -struct gui_layout_table; - -typedef enum { - HTML_DRAG_NONE, /** No drag */ - HTML_DRAG_SELECTION, /** Own; Text selection */ - HTML_DRAG_SCROLLBAR, /** Not own; drag in scrollbar widget */ - HTML_DRAG_TEXTAREA_SELECTION, /** Not own; drag in textarea widget */ - HTML_DRAG_TEXTAREA_SCROLLBAR, /** Not own; drag in textarea widget */ - HTML_DRAG_CONTENT_SELECTION, /** Not own; drag in child content */ - HTML_DRAG_CONTENT_SCROLL /** Not own; drag in child content */ -} html_drag_type; - -union html_drag_owner { - bool no_owner; - struct box *content; - struct scrollbar *scrollbar; - struct box *textarea; -}; /**< For drags we don't own */ - -typedef enum { - HTML_SELECTION_NONE, /** No selection */ - HTML_SELECTION_TEXTAREA, /** Selection in one of our textareas */ - HTML_SELECTION_SELF, /** Selection in this html content */ - HTML_SELECTION_CONTENT /** Selection in child content */ -} html_selection_type; -union html_selection_owner { - bool none; - struct box *textarea; - struct box *content; -}; /**< For getting at selections in this content or things in this content */ - -typedef enum { - HTML_FOCUS_SELF, /** Focus is our own */ - HTML_FOCUS_CONTENT, /** Focus belongs to child content */ - HTML_FOCUS_TEXTAREA /** Focus belongs to textarea */ -} html_focus_type; -union html_focus_owner { - bool self; - struct box *textarea; - struct box *content; -}; /**< For directing input */ - -/** Data specific to CONTENT_HTML. */ -typedef struct html_content { - struct content base; - - dom_hubbub_parser *parser; /**< Parser object handle */ - bool parse_completed; /**< Whether the parse has been completed */ - - /** Document tree */ - dom_document *document; - /** Quirkyness of document */ - dom_document_quirks_mode quirks; - - /** Encoding of source, NULL if unknown. */ - char *encoding; - /** Source of encoding information. */ - dom_hubbub_encoding_source encoding_source; - - /** Base URL (may be a copy of content->url). */ - nsurl *base_url; - /** Base target */ - char *base_target; - - /** CSS length conversion context for document. */ - nscss_len_ctx len_ctx; - - /** Content has been aborted in the LOADING state */ - bool aborted; - - /** Whether a meta refresh has been handled */ - bool refresh; - - /** Whether a layout (reflow) is in progress */ - bool reflowing; - - /** Whether scripts are enabled for this content */ - bool enable_scripting; - - /* Title element node */ - dom_node *title; - - /** A talloc context purely for the render box tree */ - int *bctx; - /** Box tree, or NULL. */ - struct box *layout; - /** Document background colour. */ - colour background_colour; - - /** Font callback table */ - const struct gui_layout_table *font_func; - - /** Number of entries in scripts */ - unsigned int scripts_count; - /** Scripts */ - struct html_script *scripts; - /** javascript context */ - struct jscontext *jscontext; - - /** Number of entries in stylesheet_content. */ - unsigned int stylesheet_count; - /** Stylesheets. Each may be NULL. */ - struct html_stylesheet *stylesheets; - /**< Style selection context */ - css_select_ctx *select_ctx; - /**< Universal selector */ - lwc_string *universal; - - /** Number of entries in object_list. */ - unsigned int num_objects; - /** List of objects. */ - struct content_html_object *object_list; - /** Forms, in reverse order to document. */ - struct form *forms; - /** Hash table of imagemaps. */ - struct imagemap **imagemaps; - - /** Browser window containing this document, or NULL if not open. */ - struct browser_window *bw; - - /** Frameset information */ - struct content_html_frames *frameset; - - /** Inline frame information */ - struct content_html_iframe *iframe; - - /** Content of type CONTENT_HTML containing this, or NULL if not an - * object within a page. */ - struct html_content *page; - - /** Current drag type */ - html_drag_type drag_type; - /** Widget capturing all mouse events */ - union html_drag_owner drag_owner; - - /** Current selection state */ - html_selection_type selection_type; - /** Current selection owner */ - union html_selection_owner selection_owner; - - /** Current input focus target type */ - html_focus_type focus_type; - /** Current input focus target */ - union html_focus_owner focus_owner; - - /** HTML content's own text selection object */ - struct selection sel; - - /** Open core-handled form SELECT menu, - * or NULL if none currently open. */ - struct form_control *visible_select_menu; - - /** Context for free text search, or NULL if none */ - struct search_context *search; - /** Search string or NULL */ - char *search_string; - -} html_content; - -/** Render padding and margin box outlines in html_redraw(). */ -extern bool html_redraw_debug; - -void html__redraw_a_box(html_content *html, struct box *box); - -/** - * Set our drag status, and inform whatever owns the content - * - * \param html HTML content - * \param drag_type Type of drag - * \param drag_owner What owns the drag - * \param rect Pointer movement bounds - */ -void html_set_drag_type(html_content *html, html_drag_type drag_type, - union html_drag_owner drag_owner, const struct rect *rect); - -/** - * Set our selection status, and inform whatever owns the content - * - * \param html HTML content - * \param selection_type Type of selection - * \param selection_owner What owns the selection - * \param read_only True iff selection is read only - */ -void html_set_selection(html_content *html, html_selection_type selection_type, - union html_selection_owner selection_owner, bool read_only); - -/** - * Set our input focus, and inform whatever owns the content - * - * \param html HTML content - * \param focus_type Type of input focus - * \param focus_owner What owns the focus - * \param hide_caret True iff caret to be hidden - * \param x Carret x-coord rel to owner - * \param y Carret y-coord rel to owner - * \param height Carret height - * \param clip Carret clip rect - */ -void html_set_focus(html_content *html, html_focus_type focus_type, - union html_focus_owner focus_owner, bool hide_caret, - int x, int y, int height, const struct rect *clip); - - -struct browser_window *html_get_browser_window(struct content *c); - -/** - * Complete conversion of an HTML document - * - * \param htmlc Content to convert - */ -void html_finish_conversion(html_content *htmlc); - -/** - * Test if an HTML content conversion can begin - * - * \param htmlc html content to test - * \return true iff the html content conversion can begin - */ -bool html_can_begin_conversion(html_content *htmlc); - -/** - * Begin conversion of an HTML document - * - * \param htmlc Content to convert - */ -bool html_begin_conversion(html_content *htmlc); - -/* in render/html_redraw.c */ -bool html_redraw(struct content *c, struct content_redraw_data *data, - const struct rect *clip, const struct redraw_context *ctx); - -/* in render/html_redraw_border.c */ -bool html_redraw_borders(struct box *box, int x_parent, int y_parent, - int p_width, int p_height, const struct rect *clip, float scale, - const struct redraw_context *ctx); - -bool html_redraw_inline_borders(struct box *box, struct rect b, - const struct rect *clip, float scale, bool first, bool last, - const struct redraw_context *ctx); - -/* in render/html_interaction.c */ -void html_mouse_track(struct content *c, struct browser_window *bw, - browser_mouse_state mouse, int x, int y); -void html_mouse_action(struct content *c, struct browser_window *bw, - browser_mouse_state mouse, int x, int y); -bool html_keypress(struct content *c, uint32_t key); -void html_overflow_scroll_callback(void *client_data, - struct scrollbar_msg_data *scrollbar_data); -void html_search(struct content *c, void *context, - search_flags_t flags, const char *string); -void html_search_clear(struct content *c); - - -/* in render/html_script.c */ -dom_hubbub_error html_process_script(void *ctx, dom_node *node); - -/** - * Attempt script execution for defer and async scripts - * - * execute scripts using algorithm found in: - * http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#the-script-element - * - * \param htmlc html content. - * \return NSERROR_OK error code. - */ -nserror html_script_exec(html_content *htmlc); - -/** - * Free all script resources and references for a html content. - * - * \param htmlc html content. - * \return NSERROR_OK or error code. - */ -nserror html_script_free(html_content *htmlc); - -/** - * Ensure the html content javascript context is invalidated. - * - * \param htmlc html content. - * \return NSERROR_OK or error code. - */ -nserror html_script_invalidate_ctx(html_content *htmlc); - -/* in render/html_forms.c */ -struct form *html_forms_get_forms(const char *docenc, dom_html_document *doc); -struct form_control *html_forms_get_control_for_node(struct form *forms, - dom_node *node); - -/* in render/html_css.c */ -nserror html_css_init(void); -void html_css_fini(void); - -/** - * Initialise core stylesheets for a content - * - * \param c content structure to update - * \return nserror - */ -nserror html_css_new_stylesheets(html_content *c); -nserror html_css_quirks_stylesheets(html_content *c); -nserror html_css_free_stylesheets(html_content *html); - -bool html_css_process_link(html_content *htmlc, dom_node *node); -bool html_css_process_style(html_content *htmlc, dom_node *node); -bool html_css_update_style(html_content *c, dom_node *style); - -nserror html_css_new_selection_context(html_content *c, - css_select_ctx **ret_select_ctx); - -/* in render/html_css_fetcher.c */ -/** - * Register the fetcher for the pseudo x-ns-css scheme. - * - * \return NSERROR_OK on successful registration or error code on failure. - */ -nserror html_css_fetcher_register(void); -nserror html_css_fetcher_add_item(dom_string *data, nsurl *base_url, - uint32_t *key); - -/* in render/html_object.c */ - -/** - * Start a fetch for an object required by a page. - * - * \param c content of type CONTENT_HTML - * \param url URL of object to fetch (copied) - * \param box box that will contain the object - * \param permitted_types bitmap of acceptable types - * \param available_width estimate of width of object - * \param available_height estimate of height of object - * \param background this is a background image - * \return true on success, false on memory exhaustion - */ -bool html_fetch_object(html_content *c, nsurl *url, struct box *box, - content_type permitted_types, - int available_width, int available_height, - bool background); - -nserror html_object_free_objects(html_content *html); -nserror html_object_close_objects(html_content *html); -nserror html_object_open_objects(html_content *html, struct browser_window *bw); -nserror html_object_abort_objects(html_content *html); - -/* Events */ -/** - * Construct an event and fire it at the DOM - * - */ -bool fire_dom_event(dom_string *type, dom_node *target, - bool bubbles, bool cancelable); - -/* Useful dom_string pointers */ -struct dom_string; - -extern struct dom_string *html_dom_string_map; -extern struct dom_string *html_dom_string_id; -extern struct dom_string *html_dom_string_name; -extern struct dom_string *html_dom_string_area; -extern struct dom_string *html_dom_string_a; -extern struct dom_string *html_dom_string_nohref; -extern struct dom_string *html_dom_string_href; -extern struct dom_string *html_dom_string_target; -extern struct dom_string *html_dom_string_shape; -extern struct dom_string *html_dom_string_default; -extern struct dom_string *html_dom_string_rect; -extern struct dom_string *html_dom_string_rectangle; -extern struct dom_string *html_dom_string_coords; -extern struct dom_string *html_dom_string_circle; -extern struct dom_string *html_dom_string_poly; -extern struct dom_string *html_dom_string_polygon; -extern struct dom_string *html_dom_string_text_javascript; -extern struct dom_string *html_dom_string_type; -extern struct dom_string *html_dom_string_src; - -#endif - - diff --git a/render/html_object.c b/render/html_object.c deleted file mode 100644 index 74e4bf0f3..000000000 --- a/render/html_object.c +++ /dev/null @@ -1,729 +0,0 @@ -/* - * Copyright 2013 Vincent Sanders - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Processing for html content object operations. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "utils/corestrings.h" -#include "utils/config.h" -#include "utils/log.h" -#include "utils/nsoption.h" -#include "netsurf/content.h" -#include "netsurf/misc.h" -#include "content/hlcache.h" -#include "css/utils.h" -#include "desktop/scrollbar.h" -#include "desktop/gui_internal.h" - -#include "render/box.h" -#include "render/html_internal.h" - -/* break reference loop */ -static void html_object_refresh(void *p); - -/** - * Retrieve objects used by HTML document - * - * \param h Content to retrieve objects from - * \param n Pointer to location to receive number of objects - * \return Pointer to list of objects - */ -struct content_html_object *html_get_objects(hlcache_handle *h, unsigned int *n) -{ - html_content *c = (html_content *) hlcache_handle_get_content(h); - - assert(c != NULL); - assert(n != NULL); - - *n = c->num_objects; - - return c->object_list; -} - -/** - * Handle object fetching or loading failure. - * - * \param box box containing object which failed to load - * \param content document of type CONTENT_HTML - * \param background the object was the background image for the box - */ - -static void -html_object_failed(struct box *box, html_content *content, bool background) -{ - /* Nothing to do */ - return; -} - -/** - * Update a box whose content has completed rendering. - */ - -static void -html_object_done(struct box *box, - hlcache_handle *object, - bool background) -{ - struct box *b; - - if (background) { - box->background = object; - return; - } - - box->object = object; - - /* Normalise the box type, now it has been replaced. */ - switch (box->type) { - case BOX_TABLE: - box->type = BOX_BLOCK; - break; - default: - /* TODO: Any other box types need mapping? */ - break; - } - - if (!(box->flags & REPLACE_DIM)) { - /* invalidate parent min, max widths */ - for (b = box; b; b = b->parent) - b->max_width = UNKNOWN_MAX_WIDTH; - - /* delete any clones of this box */ - while (box->next && (box->next->flags & CLONE)) { - /* box_free_box(box->next); */ - box->next = box->next->next; - } - } -} - -/** - * Callback for hlcache_handle_retrieve() for objects. - */ - -static nserror -html_object_callback(hlcache_handle *object, - const hlcache_event *event, - void *pw) -{ - struct content_html_object *o = pw; - html_content *c = (html_content *) o->parent; - int x, y; - struct box *box; - - box = o->box; - if (box == NULL && - event->type != CONTENT_MSG_ERROR && - event->type != CONTENT_MSG_ERRORCODE) { - return NSERROR_OK; - } - - switch (event->type) { - case CONTENT_MSG_LOADING: - if (c->base.status != CONTENT_STATUS_LOADING && c->bw != NULL) - content_open(object, - c->bw, &c->base, - box->object_params); - break; - - case CONTENT_MSG_READY: - if (content_can_reformat(object)) { - /* TODO: avoid knowledge of box internals here */ - content_reformat(object, false, - box->max_width != UNKNOWN_MAX_WIDTH ? - box->width : 0, - box->max_width != UNKNOWN_MAX_WIDTH ? - box->height : 0); - - /* Adjust parent content for new object size */ - html_object_done(box, object, o->background); - if (c->base.status == CONTENT_STATUS_READY || - c->base.status == CONTENT_STATUS_DONE) - content__reformat(&c->base, false, - c->base.available_width, - c->base.height); - } - break; - - case CONTENT_MSG_DONE: - c->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - - html_object_done(box, object, o->background); - - if (c->base.status != CONTENT_STATUS_LOADING && - box->flags & REPLACE_DIM) { - union content_msg_data data; - - if (!box_visible(box)) - break; - - box_coords(box, &x, &y); - - data.redraw.x = x + box->padding[LEFT]; - data.redraw.y = y + box->padding[TOP]; - data.redraw.width = box->width; - data.redraw.height = box->height; - data.redraw.full_redraw = true; - - content_broadcast(&c->base, CONTENT_MSG_REDRAW, &data); - } - break; - - case CONTENT_MSG_ERRORCODE: - case CONTENT_MSG_ERROR: - hlcache_handle_release(object); - - o->content = NULL; - - if (box != NULL) { - c->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", - c->base.active); - - content_add_error(&c->base, "?", 0); - html_object_failed(box, c, o->background); - } - break; - - case CONTENT_MSG_REDRAW: - if (c->base.status != CONTENT_STATUS_LOADING) { - union content_msg_data data = event->data; - - if (!box_visible(box)) - break; - - box_coords(box, &x, &y); - - if (object == box->background) { - /* Redraw request is for background */ - css_fixed hpos = 0, vpos = 0; - css_unit hunit = CSS_UNIT_PX; - css_unit vunit = CSS_UNIT_PX; - int width = box->padding[LEFT] + box->width + - box->padding[RIGHT]; - int height = box->padding[TOP] + box->height + - box->padding[BOTTOM]; - int t, h, l, w; - - /* Need to know background-position */ - css_computed_background_position(box->style, - &hpos, &hunit, &vpos, &vunit); - - w = content_get_width(box->background); - if (hunit == CSS_UNIT_PCT) { - l = (width - w) * hpos / INTTOFIX(100); - } else { - l = FIXTOINT(nscss_len2px(&c->len_ctx, - hpos, hunit, - box->style)); - } - - h = content_get_height(box->background); - if (vunit == CSS_UNIT_PCT) { - t = (height - h) * vpos / INTTOFIX(100); - } else { - t = FIXTOINT(nscss_len2px(&c->len_ctx, - vpos, vunit, - box->style)); - } - - /* Redraw area depends on background-repeat */ - switch (css_computed_background_repeat( - box->style)) { - case CSS_BACKGROUND_REPEAT_REPEAT: - data.redraw.x = 0; - data.redraw.y = 0; - data.redraw.width = box->width; - data.redraw.height = box->height; - break; - - case CSS_BACKGROUND_REPEAT_REPEAT_X: - data.redraw.x = 0; - data.redraw.y += t; - data.redraw.width = box->width; - break; - - case CSS_BACKGROUND_REPEAT_REPEAT_Y: - data.redraw.x += l; - data.redraw.y = 0; - data.redraw.height = box->height; - break; - - case CSS_BACKGROUND_REPEAT_NO_REPEAT: - data.redraw.x += l; - data.redraw.y += t; - break; - - default: - break; - } - - data.redraw.object_width = box->width; - data.redraw.object_height = box->height; - - /* Add offset to box */ - data.redraw.x += x; - data.redraw.y += y; - data.redraw.object_x += x; - data.redraw.object_y += y; - - content_broadcast(&c->base, - CONTENT_MSG_REDRAW, &data); - break; - - } else { - /* Non-background case */ - if (hlcache_handle_get_content(object) == - event->data.redraw.object) { - - int w = content_get_width(object); - int h = content_get_height(object); - - if (w != 0) { - data.redraw.x = - data.redraw.x * - box->width / w; - data.redraw.width = - data.redraw.width * - box->width / w; - } - - if (h != 0) { - data.redraw.y = - data.redraw.y * - box->height / h; - data.redraw.height = - data.redraw.height * - box->height / h; - } - - data.redraw.object_width = box->width; - data.redraw.object_height = box->height; - } - - data.redraw.x += x + box->padding[LEFT]; - data.redraw.y += y + box->padding[TOP]; - data.redraw.object_x += x + box->padding[LEFT]; - data.redraw.object_y += y + box->padding[TOP]; - } - - content_broadcast(&c->base, CONTENT_MSG_REDRAW, &data); - } - break; - - case CONTENT_MSG_REFRESH: - if (content_get_type(object) == CONTENT_HTML) { - /* only for HTML objects */ - guit->misc->schedule(event->data.delay * 1000, - html_object_refresh, o); - } - - break; - - case CONTENT_MSG_LINK: - /* Don't care about favicons that aren't on top level content */ - break; - - case CONTENT_MSG_GETCTX: - *(event->data.jscontext) = NULL; - break; - - case CONTENT_MSG_SCROLL: - if (box->scroll_x != NULL) - scrollbar_set(box->scroll_x, event->data.scroll.x0, - false); - if (box->scroll_y != NULL) - scrollbar_set(box->scroll_y, event->data.scroll.y0, - false); - break; - - case CONTENT_MSG_DRAGSAVE: - { - union content_msg_data msg_data; - if (event->data.dragsave.content == NULL) - msg_data.dragsave.content = object; - else - msg_data.dragsave.content = - event->data.dragsave.content; - - content_broadcast(&c->base, CONTENT_MSG_DRAGSAVE, &msg_data); - } - break; - - case CONTENT_MSG_SAVELINK: - case CONTENT_MSG_POINTER: - case CONTENT_MSG_SELECTMENU: - case CONTENT_MSG_GADGETCLICK: - /* These messages are for browser window layer. - * we're not interested, so pass them on. */ - content_broadcast(&c->base, event->type, &event->data); - break; - - case CONTENT_MSG_CARET: - { - union html_focus_owner focus_owner; - focus_owner.content = box; - - switch (event->data.caret.type) { - case CONTENT_CARET_REMOVE: - case CONTENT_CARET_HIDE: - html_set_focus(c, HTML_FOCUS_CONTENT, focus_owner, - true, 0, 0, 0, NULL); - break; - case CONTENT_CARET_SET_POS: - html_set_focus(c, HTML_FOCUS_CONTENT, focus_owner, - false, event->data.caret.pos.x, - event->data.caret.pos.y, - event->data.caret.pos.height, - event->data.caret.pos.clip); - break; - } - } - break; - - case CONTENT_MSG_DRAG: - { - html_drag_type drag_type = HTML_DRAG_NONE; - union html_drag_owner drag_owner; - drag_owner.content = box; - - switch (event->data.drag.type) { - case CONTENT_DRAG_NONE: - drag_type = HTML_DRAG_NONE; - drag_owner.no_owner = true; - break; - case CONTENT_DRAG_SCROLL: - drag_type = HTML_DRAG_CONTENT_SCROLL; - break; - case CONTENT_DRAG_SELECTION: - drag_type = HTML_DRAG_CONTENT_SELECTION; - break; - } - html_set_drag_type(c, drag_type, drag_owner, - event->data.drag.rect); - } - break; - - case CONTENT_MSG_SELECTION: - { - html_selection_type sel_type; - union html_selection_owner sel_owner; - - if (event->data.selection.selection) { - sel_type = HTML_SELECTION_CONTENT; - sel_owner.content = box; - } else { - sel_type = HTML_SELECTION_NONE; - sel_owner.none = true; - } - html_set_selection(c, sel_type, sel_owner, - event->data.selection.read_only); - } - break; - - default: - break; - } - - if (c->base.status == CONTENT_STATUS_READY && - c->base.active == 0 && - (event->type == CONTENT_MSG_LOADING || - event->type == CONTENT_MSG_DONE || - event->type == CONTENT_MSG_ERROR || - event->type == CONTENT_MSG_ERRORCODE)) { - /* all objects have arrived */ - content__reformat(&c->base, false, c->base.available_width, - c->base.height); - content_set_done(&c->base); - } else if (nsoption_bool(incremental_reflow) && - event->type == CONTENT_MSG_DONE && - box != NULL && - !(box->flags & REPLACE_DIM) && - (c->base.status == CONTENT_STATUS_READY || - c->base.status == CONTENT_STATUS_DONE)) { - /* 1) the configuration option to reflow pages while - * objects are fetched is set - * 2) an object is newly fetched & converted, - * 3) the box's dimensions need to change due to being replaced - * 4) the object's parent HTML is ready for reformat, - */ - uint64_t ms_now; - nsu_getmonotonic_ms(&ms_now); - if (ms_now > c->base.reformat_time) { - /* The time since the previous reformat is - * more than the configured minimum time - * between reformats so reformat the page to - * display newly fetched objects - */ - content__reformat(&c->base, - false, - c->base.available_width, - c->base.height); - } - } - - return NSERROR_OK; -} - -/** - * Start a fetch for an object required by a page, replacing an existing object. - * - * \param object Object to replace - * \param url URL of object to fetch (copied) - * \return true on success, false on memory exhaustion - */ - -static bool html_replace_object(struct content_html_object *object, nsurl *url) -{ - html_content *c; - hlcache_child_context child; - html_content *page; - nserror error; - - assert(object != NULL); - assert(object->box != NULL); - - c = (html_content *) object->parent; - - child.charset = c->encoding; - child.quirks = c->base.quirks; - - if (object->content != NULL) { - /* remove existing object */ - if (content_get_status(object->content) != CONTENT_STATUS_DONE) { - c->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", - c->base.active); - } - - hlcache_handle_release(object->content); - object->content = NULL; - - object->box->object = NULL; - } - - /* initialise fetch */ - error = hlcache_handle_retrieve(url, HLCACHE_RETRIEVE_SNIFF_TYPE, - content_get_url(&c->base), NULL, - html_object_callback, object, &child, - object->permitted_types, - &object->content); - - if (error != NSERROR_OK) - return false; - - for (page = c; page != NULL; page = page->page) { - page->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - - page->base.status = CONTENT_STATUS_READY; - } - - return true; -} - -/** - * schedule callback for object refresh - */ - -static void html_object_refresh(void *p) -{ - struct content_html_object *object = p; - nsurl *refresh_url; - - assert(content_get_type(object->content) == CONTENT_HTML); - - refresh_url = content_get_refresh_url(object->content); - - /* Ignore if refresh URL has gone - * (may happen if fetch errored) */ - if (refresh_url == NULL) - return; - - content_invalidate_reuse_data(object->content); - - if (!html_replace_object(object, refresh_url)) { - /** \todo handle memory exhaustion */ - } -} - -nserror html_object_open_objects(html_content *html, struct browser_window *bw) -{ - struct content_html_object *object, *next; - - for (object = html->object_list; object != NULL; object = next) { - next = object->next; - - if (object->content == NULL || object->box == NULL) - continue; - - if (content_get_type(object->content) == CONTENT_NONE) - continue; - - content_open(object->content, - bw, - &html->base, - object->box->object_params); - } - return NSERROR_OK; -} - -nserror html_object_abort_objects(html_content *htmlc) -{ - struct content_html_object *object; - - for (object = htmlc->object_list; - object != NULL; - object = object->next) { - if (object->content == NULL) - continue; - - switch (content_get_status(object->content)) { - case CONTENT_STATUS_DONE: - /* already loaded: do nothing */ - break; - - case CONTENT_STATUS_READY: - hlcache_handle_abort(object->content); - /* Active count will be updated when - * html_object_callback receives - * CONTENT_MSG_DONE from this object - */ - break; - - default: - hlcache_handle_abort(object->content); - hlcache_handle_release(object->content); - object->content = NULL; - if (object->box != NULL) { - htmlc->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", - htmlc->base.active); - } - break; - - } - } - - return NSERROR_OK; -} - -nserror html_object_close_objects(html_content *html) -{ - struct content_html_object *object, *next; - - for (object = html->object_list; object != NULL; object = next) { - next = object->next; - - if (object->content == NULL || object->box == NULL) - continue; - - if (content_get_type(object->content) == CONTENT_NONE) - continue; - - if (content_get_type(object->content) == CONTENT_HTML) { - guit->misc->schedule(-1, html_object_refresh, object); - } - - content_close(object->content); - } - return NSERROR_OK; -} - -nserror html_object_free_objects(html_content *html) -{ - while (html->object_list != NULL) { - struct content_html_object *victim = html->object_list; - - if (victim->content != NULL) { - NSLOG(netsurf, INFO, "object %p", victim->content); - - if (content_get_type(victim->content) == CONTENT_HTML) { - guit->misc->schedule(-1, html_object_refresh, victim); - } - hlcache_handle_release(victim->content); - } - - html->object_list = victim->next; - free(victim); - } - return NSERROR_OK; -} - - - -/* exported interface documented in render/html_internal.h */ -bool html_fetch_object(html_content *c, nsurl *url, struct box *box, - content_type permitted_types, - int available_width, int available_height, - bool background) -{ - struct content_html_object *object; - hlcache_child_context child; - nserror error; - - /* If we've already been aborted, don't bother attempting the fetch */ - if (c->aborted) - return true; - - child.charset = c->encoding; - child.quirks = c->base.quirks; - - object = calloc(1, sizeof(struct content_html_object)); - if (object == NULL) { - return false; - } - - object->parent = (struct content *) c; - object->next = NULL; - object->content = NULL; - object->box = box; - object->permitted_types = permitted_types; - object->background = background; - - error = hlcache_handle_retrieve(url, - HLCACHE_RETRIEVE_SNIFF_TYPE, - content_get_url(&c->base), NULL, - html_object_callback, object, &child, - object->permitted_types, &object->content); - if (error != NSERROR_OK) { - free(object); - return error != NSERROR_NOMEM; - } - - /* add to content object list */ - object->next = c->object_list; - c->object_list = object; - - c->num_objects++; - if (box != NULL) { - c->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - } - - return true; -} diff --git a/render/html_redraw.c b/render/html_redraw.c deleted file mode 100644 index 9a97e5ec5..000000000 --- a/render/html_redraw.c +++ /dev/null @@ -1,1951 +0,0 @@ -/* - * Copyright 2004-2008 James Bursa - * Copyright 2004-2007 John M Bell - * Copyright 2004-2007 Richard Wilson - * Copyright 2005-2006 Adrian Lees - * Copyright 2006 Rob Kendrick - * Copyright 2008 Michael Drake - * Copyright 2009 Paul Blokus - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * - * Redrawing CONTENT_HTML implementation. - */ - -#include "utils/config.h" -#include -#include -#include -#include -#include -#include - -#include "utils/log.h" -#include "utils/messages.h" -#include "utils/utils.h" -#include "utils/nsoption.h" -#include "netsurf/content.h" -#include "netsurf/browser_window.h" -#include "netsurf/plotters.h" -#include "netsurf/bitmap.h" -#include "netsurf/layout.h" -#include "content/content_protected.h" -#include "css/utils.h" -#include "desktop/selection.h" -#include "desktop/print.h" -#include "desktop/scrollbar.h" -#include "desktop/textarea.h" -#include "desktop/gui_internal.h" - -#include "render/box.h" -#include "render/font.h" -#include "render/form_internal.h" -#include "render/html_internal.h" -#include "render/layout.h" -#include "render/search.h" - - -bool html_redraw_debug = false; - -/** - * Determine if a box has a background that needs drawing - * - * \param box Box to consider - * \return True if box has a background, false otherwise. - */ -static bool html_redraw_box_has_background(struct box *box) -{ - if (box->background != NULL) - return true; - - if (box->style != NULL) { - css_color colour; - - css_computed_background_color(box->style, &colour); - - if (nscss_color_is_transparent(colour) == false) - return true; - } - - return false; -} - -/** - * Find the background box for a box - * - * \param box Box to find background box for - * \return Pointer to background box, or NULL if there is none - */ -static struct box *html_redraw_find_bg_box(struct box *box) -{ - /* Thanks to backwards compatibility, CSS defines the following: - * - * + If the box is for the root element and it has a background, - * use that (and then process the body box with no special case) - * + If the box is for the root element and it has no background, - * then use the background (if any) from the body element as if - * it were specified on the root. Then, when the box for the body - * element is processed, ignore the background. - * + For any other box, just use its own styling. - */ - if (box->parent == NULL) { - /* Root box */ - if (html_redraw_box_has_background(box)) - return box; - - /* No background on root box: consider body box, if any */ - if (box->children != NULL) { - if (html_redraw_box_has_background(box->children)) - return box->children; - } - } else if (box->parent != NULL && box->parent->parent == NULL) { - /* Body box: only render background if root has its own */ - if (html_redraw_box_has_background(box) && - html_redraw_box_has_background(box->parent)) - return box; - } else { - /* Any other box */ - if (html_redraw_box_has_background(box)) - return box; - } - - return NULL; -} - -/** - * Redraw a short text string, complete with highlighting - * (for selection/search) - * - * \param utf8_text pointer to UTF-8 text string - * \param utf8_len length of string, in bytes - * \param offset byte offset within textual representation - * \param space width of space that follows string (0 = no space) - * \param fstyle text style to use (pass text size unscaled) - * \param x x ordinate at which to plot text - * \param y y ordinate at which to plot text - * \param clip pointer to current clip rectangle - * \param height height of text string - * \param scale current display scale (1.0 = 100%) - * \param excluded exclude this text string from the selection - * \param c Content being redrawn. - * \param sel Selection context - * \param search Search context - * \param ctx current redraw context - * \return true iff successful and redraw should proceed - */ - -bool -text_redraw(const char *utf8_text, - size_t utf8_len, - size_t offset, - int space, - const plot_font_style_t *fstyle, - int x, - int y, - const struct rect *clip, - int height, - float scale, - bool excluded, - struct content *c, - const struct selection *sel, - struct search_context *search, - const struct redraw_context *ctx) -{ - bool highlighted = false; - plot_font_style_t plot_fstyle = *fstyle; - nserror res; - - /* Need scaled text size to pass to plotters */ - plot_fstyle.size *= scale; - - /* is this box part of a selection? */ - if (!excluded && ctx->interactive == true) { - unsigned len = utf8_len + (space ? 1 : 0); - unsigned start_idx; - unsigned end_idx; - - /* first try the browser window's current selection */ - if (selection_defined(sel) && selection_highlighted(sel, - offset, offset + len, - &start_idx, &end_idx)) { - highlighted = true; - } - - /* what about the current search operation, if any? */ - if (!highlighted && (search != NULL) && - search_term_highlighted(c, - offset, offset + len, - &start_idx, &end_idx, - search)) { - highlighted = true; - } - - /* \todo make search terms visible within selected text */ - if (highlighted) { - struct rect r; - unsigned endtxt_idx = end_idx; - bool clip_changed = false; - bool text_visible = true; - int startx, endx; - plot_style_t pstyle_fill_hback = *plot_style_fill_white; - plot_font_style_t fstyle_hback = plot_fstyle; - - if (end_idx > utf8_len) { - /* adjust for trailing space, not present in - * utf8_text */ - assert(end_idx == utf8_len + 1); - endtxt_idx = utf8_len; - } - - res = guit->layout->width(fstyle, - utf8_text, start_idx, - &startx); - if (res != NSERROR_OK) { - startx = 0; - } - - res = guit->layout->width(fstyle, - utf8_text, endtxt_idx, - &endx); - if (res != NSERROR_OK) { - endx = 0; - } - - /* is there a trailing space that should be highlighted - * as well? */ - if (end_idx > utf8_len) { - endx += space; - } - - if (scale != 1.0) { - startx *= scale; - endx *= scale; - } - - /* draw any text preceding highlighted portion */ - if ((start_idx > 0) && - (ctx->plot->text(ctx, - &plot_fstyle, - x, - y + (int)(height * 0.75 * scale), - utf8_text, - start_idx) != NSERROR_OK)) - return false; - - pstyle_fill_hback.fill_colour = fstyle->foreground; - - /* highlighted portion */ - r.x0 = x + startx; - r.y0 = y; - r.x1 = x + endx; - r.y1 = y + height * scale; - res = ctx->plot->rectangle(ctx, &pstyle_fill_hback, &r); - if (res != NSERROR_OK) { - return false; - } - - if (start_idx > 0) { - int px0 = max(x + startx, clip->x0); - int px1 = min(x + endx, clip->x1); - - if (px0 < px1) { - r.x0 = px0; - r.y0 = clip->y0; - r.x1 = px1; - r.y1 = clip->y1; - res = ctx->plot->clip(ctx, &r); - if (res != NSERROR_OK) { - return false; - } - - clip_changed = true; - } else { - text_visible = false; - } - } - - fstyle_hback.background = - pstyle_fill_hback.fill_colour; - fstyle_hback.foreground = colour_to_bw_furthest( - pstyle_fill_hback.fill_colour); - - if (text_visible && - (ctx->plot->text(ctx, - &fstyle_hback, - x, - y + (int)(height * 0.75 * scale), - utf8_text, - endtxt_idx) != NSERROR_OK)) { - return false; - } - - /* draw any text succeeding highlighted portion */ - if (endtxt_idx < utf8_len) { - int px0 = max(x + endx, clip->x0); - if (px0 < clip->x1) { - - r.x0 = px0; - r.y0 = clip->y0; - r.x1 = clip->x1; - r.y1 = clip->y1; - res = ctx->plot->clip(ctx, &r); - if (res != NSERROR_OK) { - return false; - } - - clip_changed = true; - - res = ctx->plot->text(ctx, - &plot_fstyle, - x, - y + (int)(height * 0.75 * scale), - utf8_text, - utf8_len); - if (res != NSERROR_OK) { - return false; - } - } - } - - if (clip_changed && - (ctx->plot->clip(ctx, clip) != NSERROR_OK)) { - return false; - } - } - } - - if (!highlighted) { - res = ctx->plot->text(ctx, - &plot_fstyle, - x, - y + (int) (height * 0.75 * scale), - utf8_text, - utf8_len); - if (res != NSERROR_OK) { - return false; - } - } - return true; -} - - -/** - * Plot a checkbox. - * - * \param x left coordinate - * \param y top coordinate - * \param width dimensions of checkbox - * \param height dimensions of checkbox - * \param selected the checkbox is selected - * \param ctx current redraw context - * \return true if successful, false otherwise - */ - -static bool html_redraw_checkbox(int x, int y, int width, int height, - bool selected, const struct redraw_context *ctx) -{ - double z; - nserror res; - struct rect rect; - - z = width * 0.15; - if (z == 0) { - z = 1; - } - - rect.x0 = x; - rect.y0 = y ; - rect.x1 = x + width; - rect.y1 = y + height; - res = ctx->plot->rectangle(ctx, plot_style_fill_wbasec, &rect); - if (res != NSERROR_OK) { - return false; - } - - /* dark line across top */ - rect.y1 = y; - res = ctx->plot->line(ctx, plot_style_stroke_darkwbasec, &rect); - if (res != NSERROR_OK) { - return false; - } - - /* dark line across left */ - rect.x1 = x; - rect.y1 = y + height; - res = ctx->plot->line(ctx, plot_style_stroke_darkwbasec, &rect); - if (res != NSERROR_OK) { - return false; - } - - /* light line across right */ - rect.x0 = x + width; - rect.x1 = x + width; - res = ctx->plot->line(ctx, plot_style_stroke_lightwbasec, &rect); - if (res != NSERROR_OK) { - return false; - } - - /* light line across bottom */ - rect.x0 = x; - rect.y0 = y + height; - res = ctx->plot->line(ctx, plot_style_stroke_lightwbasec, &rect); - if (res != NSERROR_OK) { - return false; - } - - if (selected) { - if (width < 12 || height < 12) { - /* render a solid box instead of a tick */ - rect.x0 = x + z + z; - rect.y0 = y + z + z; - rect.x1 = x + width - z; - rect.y1 = y + height - z; - res = ctx->plot->rectangle(ctx, plot_style_fill_wblobc, &rect); - if (res != NSERROR_OK) { - return false; - } - } else { - /* render a tick, as it'll fit comfortably */ - rect.x0 = x + width - z; - rect.y0 = y + z; - rect.x1 = x + (z * 3); - rect.y1 = y + height - z; - res = ctx->plot->line(ctx, plot_style_stroke_wblobc, &rect); - if (res != NSERROR_OK) { - return false; - } - - rect.x0 = x + (z * 3); - rect.y0 = y + height - z; - rect.x1 = x + z + z; - rect.y1 = y + (height / 2); - res = ctx->plot->line(ctx, plot_style_stroke_wblobc, &rect); - if (res != NSERROR_OK) { - return false; - } - } - } - return true; -} - - -/** - * Plot a radio icon. - * - * \param x left coordinate - * \param y top coordinate - * \param width dimensions of radio icon - * \param height dimensions of radio icon - * \param selected the radio icon is selected - * \param ctx current redraw context - * \return true if successful, false otherwise - */ -static bool html_redraw_radio(int x, int y, int width, int height, - bool selected, const struct redraw_context *ctx) -{ - nserror res; - - /* plot background of radio button */ - res = ctx->plot->disc(ctx, - plot_style_fill_wbasec, - x + width * 0.5, - y + height * 0.5, - width * 0.5 - 1); - if (res != NSERROR_OK) { - return false; - } - - /* plot dark arc */ - res = ctx->plot->arc(ctx, - plot_style_fill_darkwbasec, - x + width * 0.5, - y + height * 0.5, - width * 0.5 - 1, - 45, - 225); - if (res != NSERROR_OK) { - return false; - } - - /* plot light arc */ - res = ctx->plot->arc(ctx, - plot_style_fill_lightwbasec, - x + width * 0.5, - y + height * 0.5, - width * 0.5 - 1, - 225, - 45); - if (res != NSERROR_OK) { - return false; - } - - if (selected) { - /* plot selection blob */ - res = ctx->plot->disc(ctx, - plot_style_fill_wblobc, - x + width * 0.5, - y + height * 0.5, - width * 0.3 - 1); - if (res != NSERROR_OK) { - return false; - } - } - - return true; -} - - -/** - * Plot a file upload input. - * - * \param x left coordinate - * \param y top coordinate - * \param width dimensions of input - * \param height dimensions of input - * \param box box of input - * \param scale scale for redraw - * \param background_colour current background colour - * \param len_ctx Length conversion context - * \param ctx current redraw context - * \return true if successful, false otherwise - */ - -static bool html_redraw_file(int x, int y, int width, int height, - struct box *box, float scale, colour background_colour, - const nscss_len_ctx *len_ctx, - const struct redraw_context *ctx) -{ - int text_width; - const char *text; - size_t length; - plot_font_style_t fstyle; - nserror res; - - font_plot_style_from_css(len_ctx, box->style, &fstyle); - fstyle.background = background_colour; - - if (box->gadget->value) { - text = box->gadget->value; - } else { - text = messages_get("Form_Drop"); - } - length = strlen(text); - - res = guit->layout->width(&fstyle, text, length, &text_width); - if (res != NSERROR_OK) { - return false; - } - text_width *= scale; - if (width < text_width + 8) { - x = x + width - text_width - 4; - } else { - x = x + 4; - } - - res = ctx->plot->text(ctx, &fstyle, x, y + height * 0.75, text, length); - if (res != NSERROR_OK) { - return false; - } - return true; -} - - -/** - * Plot background images. - * - * The reason for the presence of \a background is the backwards compatibility - * mess that is backgrounds on <body>. The background will be drawn relative - * to \a box, using the background information contained within \a background. - * - * \param x coordinate of box - * \param y coordinate of box - * \param box box to draw background image of - * \param scale scale for redraw - * \param clip current clip rectangle - * \param background_colour current background colour - * \param background box containing background details (usually \a box) - * \param len_ctx Length conversion context - * \param ctx current redraw context - * \return true if successful, false otherwise - */ - -static bool html_redraw_background(int x, int y, struct box *box, float scale, - const struct rect *clip, colour *background_colour, - struct box *background, - const nscss_len_ctx *len_ctx, - const struct redraw_context *ctx) -{ - bool repeat_x = false; - bool repeat_y = false; - bool plot_colour = true; - bool plot_content; - bool clip_to_children = false; - struct box *clip_box = box; - int ox = x, oy = y; - int width, height; - css_fixed hpos = 0, vpos = 0; - css_unit hunit = CSS_UNIT_PX, vunit = CSS_UNIT_PX; - struct box *parent; - struct rect r = *clip; - css_color bgcol; - plot_style_t pstyle_fill_bg = { - .fill_type = PLOT_OP_TYPE_SOLID, - .fill_colour = *background_colour, - }; - nserror res; - - if (ctx->background_images == false) - return true; - - plot_content = (background->background != NULL); - - if (plot_content) { - if (!box->parent) { - /* Root element, special case: - * background origin calc. is based on margin box */ - x -= box->margin[LEFT] * scale; - y -= box->margin[TOP] * scale; - width = box->margin[LEFT] + box->padding[LEFT] + - box->width + box->padding[RIGHT] + - box->margin[RIGHT]; - height = box->margin[TOP] + box->padding[TOP] + - box->height + box->padding[BOTTOM] + - box->margin[BOTTOM]; - } else { - width = box->padding[LEFT] + box->width + - box->padding[RIGHT]; - height = box->padding[TOP] + box->height + - box->padding[BOTTOM]; - } - /* handle background-repeat */ - switch (css_computed_background_repeat(background->style)) { - case CSS_BACKGROUND_REPEAT_REPEAT: - repeat_x = repeat_y = true; - /* optimisation: only plot the colour if - * bitmap is not opaque */ - plot_colour = !content_get_opaque(background->background); - break; - - case CSS_BACKGROUND_REPEAT_REPEAT_X: - repeat_x = true; - break; - - case CSS_BACKGROUND_REPEAT_REPEAT_Y: - repeat_y = true; - break; - - case CSS_BACKGROUND_REPEAT_NO_REPEAT: - break; - - default: - break; - } - - /* handle background-position */ - css_computed_background_position(background->style, - &hpos, &hunit, &vpos, &vunit); - if (hunit == CSS_UNIT_PCT) { - x += (width - - content_get_width(background->background)) * - scale * FIXTOFLT(hpos) / 100.; - } else { - x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit, - background->style)) * scale); - } - - if (vunit == CSS_UNIT_PCT) { - y += (height - - content_get_height(background->background)) * - scale * FIXTOFLT(vpos) / 100.; - } else { - y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit, - background->style)) * scale); - } - } - - /* special case for table rows as their background needs - * to be clipped to all the cells */ - if (box->type == BOX_TABLE_ROW) { - css_fixed h = 0, v = 0; - css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; - - for (parent = box->parent; - ((parent) && (parent->type != BOX_TABLE)); - parent = parent->parent); - assert(parent && (parent->style)); - - css_computed_border_spacing(parent->style, &h, &hu, &v, &vu); - - clip_to_children = (h > 0) || (v > 0); - - if (clip_to_children) - clip_box = box->children; - } - - for (; clip_box; clip_box = clip_box->next) { - /* clip to child boxes if needed */ - if (clip_to_children) { - assert(clip_box->type == BOX_TABLE_CELL); - - /* update clip.* to the child cell */ - r.x0 = ox + (clip_box->x * scale); - r.y0 = oy + (clip_box->y * scale); - r.x1 = r.x0 + (clip_box->padding[LEFT] + - clip_box->width + - clip_box->padding[RIGHT]) * scale; - r.y1 = r.y0 + (clip_box->padding[TOP] + - clip_box->height + - clip_box->padding[BOTTOM]) * scale; - - if (r.x0 < clip->x0) r.x0 = clip->x0; - if (r.y0 < clip->y0) r.y0 = clip->y0; - if (r.x1 > clip->x1) r.x1 = clip->x1; - if (r.y1 > clip->y1) r.y1 = clip->y1; - - css_computed_background_color(clip_box->style, &bgcol); - - /* attributes override */ - /* if the background content is opaque there - * is no need to plot underneath it. - */ - if ((r.x0 >= r.x1) || - (r.y0 >= r.y1) || - (nscss_color_is_transparent(bgcol) == false) || - ((clip_box->background != NULL) && - content_get_opaque(clip_box->background))) - continue; - } - - /* plot the background colour */ - css_computed_background_color(background->style, &bgcol); - - if (nscss_color_is_transparent(bgcol) == false) { - *background_colour = nscss_color_to_ns(bgcol); - pstyle_fill_bg.fill_colour = *background_colour; - if (plot_colour) { - res = ctx->plot->rectangle(ctx, &pstyle_fill_bg, &r); - if (res != NSERROR_OK) { - return false; - } - } - } - /* and plot the image */ - if (plot_content) { - width = content_get_width(background->background); - height = content_get_height(background->background); - - /* ensure clip area only as large as required */ - if (!repeat_x) { - if (r.x0 < x) - r.x0 = x; - if (r.x1 > x + width * scale) - r.x1 = x + width * scale; - } - if (!repeat_y) { - if (r.y0 < y) - r.y0 = y; - if (r.y1 > y + height * scale) - r.y1 = y + height * scale; - } - /* valid clipping rectangles only */ - if ((r.x0 < r.x1) && (r.y0 < r.y1)) { - struct content_redraw_data bg_data; - - res = ctx->plot->clip(ctx, &r); - if (res != NSERROR_OK) { - return false; - } - - bg_data.x = x; - bg_data.y = y; - bg_data.width = ceilf(width * scale); - bg_data.height = ceilf(height * scale); - bg_data.background_colour = *background_colour; - bg_data.scale = scale; - bg_data.repeat_x = repeat_x; - bg_data.repeat_y = repeat_y; - - /* We just continue if redraw fails */ - content_redraw(background->background, - &bg_data, &r, ctx); - } - } - - /* only rows being clipped to child boxes loop */ - if (!clip_to_children) - return true; - } - return true; -} - - -/** - * Plot an inline's background and/or background image. - * - * \param x coordinate of box - * \param y coordinate of box - * \param box BOX_INLINE which created the background - * \param scale scale for redraw - * \param clip coordinates of clip rectangle - * \param b coordinates of border edge rectangle - * \param first true if this is the first rectangle associated with the inline - * \param last true if this is the last rectangle associated with the inline - * \param background_colour updated to current background colour if plotted - * \param len_ctx Length conversion context - * \param ctx current redraw context - * \return true if successful, false otherwise - */ - -static bool html_redraw_inline_background(int x, int y, struct box *box, - float scale, const struct rect *clip, struct rect b, - bool first, bool last, colour *background_colour, - const nscss_len_ctx *len_ctx, - const struct redraw_context *ctx) -{ - struct rect r = *clip; - bool repeat_x = false; - bool repeat_y = false; - bool plot_colour = true; - bool plot_content; - css_fixed hpos = 0, vpos = 0; - css_unit hunit = CSS_UNIT_PX, vunit = CSS_UNIT_PX; - css_color bgcol; - plot_style_t pstyle_fill_bg = { - .fill_type = PLOT_OP_TYPE_SOLID, - .fill_colour = *background_colour, - }; - nserror res; - - plot_content = (box->background != NULL); - - if (html_redraw_printing && nsoption_bool(remove_backgrounds)) - return true; - - if (plot_content) { - /* handle background-repeat */ - switch (css_computed_background_repeat(box->style)) { - case CSS_BACKGROUND_REPEAT_REPEAT: - repeat_x = repeat_y = true; - /* optimisation: only plot the colour if - * bitmap is not opaque - */ - plot_colour = !content_get_opaque(box->background); - break; - - case CSS_BACKGROUND_REPEAT_REPEAT_X: - repeat_x = true; - break; - - case CSS_BACKGROUND_REPEAT_REPEAT_Y: - repeat_y = true; - break; - - case CSS_BACKGROUND_REPEAT_NO_REPEAT: - break; - - default: - break; - } - - /* handle background-position */ - css_computed_background_position(box->style, - &hpos, &hunit, &vpos, &vunit); - if (hunit == CSS_UNIT_PCT) { - x += (b.x1 - b.x0 - - content_get_width(box->background) * - scale) * FIXTOFLT(hpos) / 100.; - - if (!repeat_x && ((hpos < 2 && !first) || - (hpos > 98 && !last))){ - plot_content = false; - } - } else { - x += (int) (FIXTOFLT(nscss_len2px(len_ctx, hpos, hunit, - box->style)) * scale); - } - - if (vunit == CSS_UNIT_PCT) { - y += (b.y1 - b.y0 - - content_get_height(box->background) * - scale) * FIXTOFLT(vpos) / 100.; - } else { - y += (int) (FIXTOFLT(nscss_len2px(len_ctx, vpos, vunit, - box->style)) * scale); - } - } - - /* plot the background colour */ - css_computed_background_color(box->style, &bgcol); - - if (nscss_color_is_transparent(bgcol) == false) { - *background_colour = nscss_color_to_ns(bgcol); - pstyle_fill_bg.fill_colour = *background_colour; - - if (plot_colour) { - res = ctx->plot->rectangle(ctx, &pstyle_fill_bg, &r); - if (res != NSERROR_OK) { - return false; - } - } - } - /* and plot the image */ - if (plot_content) { - int width = content_get_width(box->background); - int height = content_get_height(box->background); - - if (!repeat_x) { - if (r.x0 < x) - r.x0 = x; - if (r.x1 > x + width * scale) - r.x1 = x + width * scale; - } - if (!repeat_y) { - if (r.y0 < y) - r.y0 = y; - if (r.y1 > y + height * scale) - r.y1 = y + height * scale; - } - /* valid clipping rectangles only */ - if ((r.x0 < r.x1) && (r.y0 < r.y1)) { - struct content_redraw_data bg_data; - - res = ctx->plot->clip(ctx, &r); - if (res != NSERROR_OK) { - return false; - } - - bg_data.x = x; - bg_data.y = y; - bg_data.width = ceilf(width * scale); - bg_data.height = ceilf(height * scale); - bg_data.background_colour = *background_colour; - bg_data.scale = scale; - bg_data.repeat_x = repeat_x; - bg_data.repeat_y = repeat_y; - - /* We just continue if redraw fails */ - content_redraw(box->background, &bg_data, &r, ctx); - } - } - - return true; -} - - -/** - * Plot text decoration for an inline box. - * - * \param box box to plot decorations for, of type BOX_INLINE - * \param x x coordinate of parent of box - * \param y y coordinate of parent of box - * \param scale scale for redraw - * \param colour colour for decorations - * \param ratio position of line as a ratio of line height - * \param ctx current redraw context - * \return true if successful, false otherwise - */ - -static bool -html_redraw_text_decoration_inline(struct box *box, - int x, int y, - float scale, - colour colour, - float ratio, - const struct redraw_context *ctx) -{ - struct box *c; - plot_style_t plot_style_box = { - .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_colour = colour, - }; - nserror res; - struct rect rect; - - for (c = box->next; - c && c != box->inline_end; - c = c->next) { - if (c->type != BOX_TEXT) { - continue; - } - rect.x0 = (x + c->x) * scale; - rect.y0 = (y + c->y + c->height * ratio) * scale; - rect.x1 = (x + c->x + c->width) * scale; - rect.y1 = (y + c->y + c->height * ratio) * scale; - res = ctx->plot->line(ctx, &plot_style_box, &rect); - if (res != NSERROR_OK) { - return false; - } - } - return true; -} - - -/** - * Plot text decoration for an non-inline box. - * - * \param box box to plot decorations for, of type other than BOX_INLINE - * \param x x coordinate of box - * \param y y coordinate of box - * \param scale scale for redraw - * \param colour colour for decorations - * \param ratio position of line as a ratio of line height - * \param ctx current redraw context - * \return true if successful, false otherwise - */ - -static bool -html_redraw_text_decoration_block(struct box *box, - int x, int y, - float scale, - colour colour, - float ratio, - const struct redraw_context *ctx) -{ - struct box *c; - plot_style_t plot_style_box = { - .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_colour = colour, - }; - nserror res; - struct rect rect; - - /* draw through text descendants */ - for (c = box->children; c; c = c->next) { - if (c->type == BOX_TEXT) { - rect.x0 = (x + c->x) * scale; - rect.y0 = (y + c->y + c->height * ratio) * scale; - rect.x1 = (x + c->x + c->width) * scale; - rect.y1 = (y + c->y + c->height * ratio) * scale; - res = ctx->plot->line(ctx, &plot_style_box, &rect); - if (res != NSERROR_OK) { - return false; - } - } else if ((c->type == BOX_INLINE_CONTAINER) || (c->type == BOX_BLOCK)) { - if (!html_redraw_text_decoration_block(c, - x + c->x, y + c->y, - scale, colour, ratio, ctx)) - return false; - } - } - return true; -} - - -/** - * Plot text decoration for a box. - * - * \param box box to plot decorations for - * \param x_parent x coordinate of parent of box - * \param y_parent y coordinate of parent of box - * \param scale scale for redraw - * \param background_colour current background colour - * \param ctx current redraw context - * \return true if successful, false otherwise - */ - -static bool html_redraw_text_decoration(struct box *box, - int x_parent, int y_parent, float scale, - colour background_colour, const struct redraw_context *ctx) -{ - static const enum css_text_decoration_e decoration[] = { - CSS_TEXT_DECORATION_UNDERLINE, CSS_TEXT_DECORATION_OVERLINE, - CSS_TEXT_DECORATION_LINE_THROUGH }; - static const float line_ratio[] = { 0.9, 0.1, 0.5 }; - colour fgcol; - unsigned int i; - css_color col; - - css_computed_color(box->style, &col); - fgcol = nscss_color_to_ns(col); - - /* antialias colour for under/overline */ - if (html_redraw_printing == false) - fgcol = blend_colour(background_colour, fgcol); - - if (box->type == BOX_INLINE) { - if (!box->inline_end) - return true; - for (i = 0; i != NOF_ELEMENTS(decoration); i++) - if (css_computed_text_decoration(box->style) & - decoration[i]) - if (!html_redraw_text_decoration_inline(box, - x_parent, y_parent, scale, - fgcol, line_ratio[i], ctx)) - return false; - } else { - for (i = 0; i != NOF_ELEMENTS(decoration); i++) - if (css_computed_text_decoration(box->style) & - decoration[i]) - if (!html_redraw_text_decoration_block(box, - x_parent + box->x, - y_parent + box->y, - scale, - fgcol, line_ratio[i], ctx)) - return false; - } - - return true; -} - - -/** - * Redraw the text content of a box, possibly partially highlighted - * because the text has been selected, or matches a search operation. - * - * \param html The html content to redraw text within. - * \param box box with text content - * \param x x co-ord of box - * \param y y co-ord of box - * \param clip current clip rectangle - * \param scale current scale setting (1.0 = 100%) - * \param current_background_color - * \param ctx current redraw context - * \return true iff successful and redraw should proceed - */ - -static bool html_redraw_text_box(const html_content *html, struct box *box, - int x, int y, const struct rect *clip, float scale, - colour current_background_color, - const struct redraw_context *ctx) -{ - bool excluded = (box->object != NULL); - plot_font_style_t fstyle; - - font_plot_style_from_css(&html->len_ctx, box->style, &fstyle); - fstyle.background = current_background_color; - - if (!text_redraw(box->text, box->length, box->byte_offset, - box->space, &fstyle, x, y, - clip, box->height, scale, excluded, - (struct content *)html, &html->sel, - html->search, ctx)) - return false; - - return true; -} - -bool html_redraw_box(const html_content *html, struct box *box, - int x_parent, int y_parent, - const struct rect *clip, float scale, - colour current_background_color, - const struct redraw_context *ctx); - -/** - * Draw the various children of a box. - * - * \param html html content - * \param box box to draw children of - * \param x_parent coordinate of parent box - * \param y_parent coordinate of parent box - * \param clip clip rectangle - * \param scale scale for redraw - * \param current_background_color background colour under this box - * \param ctx current redraw context - * \return true if successful, false otherwise - */ - -static bool html_redraw_box_children(const html_content *html, struct box *box, - int x_parent, int y_parent, - const struct rect *clip, float scale, - colour current_background_color, - const struct redraw_context *ctx) -{ - struct box *c; - - for (c = box->children; c; c = c->next) { - - if (c->type != BOX_FLOAT_LEFT && c->type != BOX_FLOAT_RIGHT) - if (!html_redraw_box(html, c, - x_parent + box->x - - scrollbar_get_offset(box->scroll_x), - y_parent + box->y - - scrollbar_get_offset(box->scroll_y), - clip, scale, current_background_color, - ctx)) - return false; - } - for (c = box->float_children; c; c = c->next_float) - if (!html_redraw_box(html, c, - x_parent + box->x - - scrollbar_get_offset(box->scroll_x), - y_parent + box->y - - scrollbar_get_offset(box->scroll_y), - clip, scale, current_background_color, - ctx)) - return false; - - return true; -} - -/** - * Recursively draw a box. - * - * \param html html content - * \param box box to draw - * \param x_parent coordinate of parent box - * \param y_parent coordinate of parent box - * \param clip clip rectangle - * \param scale scale for redraw - * \param current_background_color background colour under this box - * \param ctx current redraw context - * \return true if successful, false otherwise - * - * x, y, clip_[xy][01] are in target coordinates. - */ - -bool html_redraw_box(const html_content *html, struct box *box, - int x_parent, int y_parent, - const struct rect *clip, const float scale, - colour current_background_color, - const struct redraw_context *ctx) -{ - const struct plotter_table *plot = ctx->plot; - int x, y; - int width, height; - int padding_left, padding_top, padding_width, padding_height; - int border_left, border_top, border_right, border_bottom; - struct rect r; - struct rect rect; - int x_scrolled, y_scrolled; - struct box *bg_box = NULL; - bool has_x_scroll, has_y_scroll; - css_computed_clip_rect css_rect; - enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; - enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; - - if (html_redraw_printing && (box->flags & PRINTED)) - return true; - - if (box->style != NULL) { - overflow_x = css_computed_overflow_x(box->style); - overflow_y = css_computed_overflow_y(box->style); - } - - /* avoid trivial FP maths */ - if (scale == 1.0) { - x = x_parent + box->x; - y = y_parent + box->y; - width = box->width; - height = box->height; - padding_left = box->padding[LEFT]; - padding_top = box->padding[TOP]; - padding_width = padding_left + box->width + box->padding[RIGHT]; - padding_height = padding_top + box->height + - box->padding[BOTTOM]; - border_left = box->border[LEFT].width; - border_top = box->border[TOP].width; - border_right = box->border[RIGHT].width; - border_bottom = box->border[BOTTOM].width; - } else { - x = (x_parent + box->x) * scale; - y = (y_parent + box->y) * scale; - width = box->width * scale; - height = box->height * scale; - /* left and top padding values are normally zero, - * so avoid trivial FP maths */ - padding_left = box->padding[LEFT] ? box->padding[LEFT] * scale - : 0; - padding_top = box->padding[TOP] ? box->padding[TOP] * scale - : 0; - padding_width = (box->padding[LEFT] + box->width + - box->padding[RIGHT]) * scale; - padding_height = (box->padding[TOP] + box->height + - box->padding[BOTTOM]) * scale; - border_left = box->border[LEFT].width * scale; - border_top = box->border[TOP].width * scale; - border_right = box->border[RIGHT].width * scale; - border_bottom = box->border[BOTTOM].width * scale; - } - - /* calculate rectangle covering this box and descendants */ - if (box->style && overflow_x != CSS_OVERFLOW_VISIBLE && - box->parent != NULL) { - /* box contents clipped to box size */ - r.x0 = x - border_left; - r.x1 = x + padding_width + border_right; - } else { - /* box contents can hang out of the box; use descendant box */ - if (scale == 1.0) { - r.x0 = x + box->descendant_x0; - r.x1 = x + box->descendant_x1 + 1; - } else { - r.x0 = x + box->descendant_x0 * scale; - r.x1 = x + box->descendant_x1 * scale + 1; - } - if (!box->parent) { - /* root element */ - int margin_left, margin_right; - if (scale == 1.0) { - margin_left = box->margin[LEFT]; - margin_right = box->margin[RIGHT]; - } else { - margin_left = box->margin[LEFT] * scale; - margin_right = box->margin[RIGHT] * scale; - } - r.x0 = x - border_left - margin_left < r.x0 ? - x - border_left - margin_left : r.x0; - r.x1 = x + padding_width + border_right + - margin_right > r.x1 ? - x + padding_width + border_right + - margin_right : r.x1; - } - } - - /* calculate rectangle covering this box and descendants */ - if (box->style && overflow_y != CSS_OVERFLOW_VISIBLE && - box->parent != NULL) { - /* box contents clipped to box size */ - r.y0 = y - border_top; - r.y1 = y + padding_height + border_bottom; - } else { - /* box contents can hang out of the box; use descendant box */ - if (scale == 1.0) { - r.y0 = y + box->descendant_y0; - r.y1 = y + box->descendant_y1 + 1; - } else { - r.y0 = y + box->descendant_y0 * scale; - r.y1 = y + box->descendant_y1 * scale + 1; - } - if (!box->parent) { - /* root element */ - int margin_top, margin_bottom; - if (scale == 1.0) { - margin_top = box->margin[TOP]; - margin_bottom = box->margin[BOTTOM]; - } else { - margin_top = box->margin[TOP] * scale; - margin_bottom = box->margin[BOTTOM] * scale; - } - r.y0 = y - border_top - margin_top < r.y0 ? - y - border_top - margin_top : r.y0; - r.y1 = y + padding_height + border_bottom + - margin_bottom > r.y1 ? - y + padding_height + border_bottom + - margin_bottom : r.y1; - } - } - - /* return if the rectangle is completely outside the clip rectangle */ - if (clip->y1 < r.y0 || r.y1 < clip->y0 || - clip->x1 < r.x0 || r.x1 < clip->x0) - return true; - - /*if the rectangle is under the page bottom but it can fit in a page, - don't print it now*/ - if (html_redraw_printing) { - if (r.y1 > html_redraw_printing_border) { - if (r.y1 - r.y0 <= html_redraw_printing_border && - (box->type == BOX_TEXT || - box->type == BOX_TABLE_CELL - || box->object || box->gadget)) { - /*remember the highest of all points from the - not printed elements*/ - if (r.y0 < html_redraw_printing_top_cropped) - html_redraw_printing_top_cropped = r.y0; - return true; - } - } - else box->flags |= PRINTED; /*it won't be printed anymore*/ - } - - /* if visibility is hidden render children only */ - if (box->style && css_computed_visibility(box->style) == - CSS_VISIBILITY_HIDDEN) { - if ((ctx->plot->group_start) && - (ctx->plot->group_start(ctx, "hidden box") != NSERROR_OK)) - return false; - if (!html_redraw_box_children(html, box, x_parent, y_parent, - &r, scale, current_background_color, ctx)) - return false; - return ((!ctx->plot->group_end) || (ctx->plot->group_end(ctx) == NSERROR_OK)); - } - - if ((ctx->plot->group_start) && - (ctx->plot->group_start(ctx,"vis box") != NSERROR_OK)) { - return false; - } - - if (box->style != NULL && - css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE && - css_computed_clip(box->style, &css_rect) == - CSS_CLIP_RECT) { - /* We have an absolutly positioned box with a clip rect */ - if (css_rect.left_auto == false) - r.x0 = x - border_left + FIXTOINT(nscss_len2px( - &html->len_ctx, - css_rect.left, css_rect.lunit, - box->style)); - - if (css_rect.top_auto == false) - r.y0 = y - border_top + FIXTOINT(nscss_len2px( - &html->len_ctx, - css_rect.top, css_rect.tunit, - box->style)); - - if (css_rect.right_auto == false) - r.x1 = x - border_left + FIXTOINT(nscss_len2px( - &html->len_ctx, - css_rect.right, css_rect.runit, - box->style)); - - if (css_rect.bottom_auto == false) - r.y1 = y - border_top + FIXTOINT(nscss_len2px( - &html->len_ctx, - css_rect.bottom, css_rect.bunit, - box->style)); - - /* find intersection of clip rectangle and box */ - if (r.x0 < clip->x0) r.x0 = clip->x0; - if (r.y0 < clip->y0) r.y0 = clip->y0; - if (clip->x1 < r.x1) r.x1 = clip->x1; - if (clip->y1 < r.y1) r.y1 = clip->y1; - /* Nothing to do for invalid rectangles */ - if (r.x0 >= r.x1 || r.y0 >= r.y1) - /* not an error */ - return ((!ctx->plot->group_end) || - (ctx->plot->group_end(ctx) == NSERROR_OK)); - /* clip to it */ - if (ctx->plot->clip(ctx, &r) != NSERROR_OK) - return false; - - } else if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || - box->type == BOX_TABLE_CELL || box->object) { - /* find intersection of clip rectangle and box */ - if (r.x0 < clip->x0) r.x0 = clip->x0; - if (r.y0 < clip->y0) r.y0 = clip->y0; - if (clip->x1 < r.x1) r.x1 = clip->x1; - if (clip->y1 < r.y1) r.y1 = clip->y1; - /* no point trying to draw 0-width/height boxes */ - if (r.x0 == r.x1 || r.y0 == r.y1) - /* not an error */ - return ((!ctx->plot->group_end) || - (ctx->plot->group_end(ctx) == NSERROR_OK)); - /* clip to it */ - if (ctx->plot->clip(ctx, &r) != NSERROR_OK) - return false; - } else { - /* clip box is fine, clip to it */ - r = *clip; - if (ctx->plot->clip(ctx, &r) != NSERROR_OK) - return false; - } - - /* background colour and image for block level content and replaced - * inlines */ - - bg_box = html_redraw_find_bg_box(box); - - /* bg_box == NULL implies that this box should not have - * its background rendered. Otherwise filter out linebreaks, - * optimize away non-differing inlines, only plot background - * for BOX_TEXT it's in an inline */ - if (bg_box && bg_box->type != BOX_BR && - bg_box->type != BOX_TEXT && - bg_box->type != BOX_INLINE_END && - (bg_box->type != BOX_INLINE || bg_box->object || - bg_box->flags & IFRAME || box->flags & REPLACE_DIM || - (bg_box->gadget != NULL && - (bg_box->gadget->type == GADGET_TEXTAREA || - bg_box->gadget->type == GADGET_TEXTBOX || - bg_box->gadget->type == GADGET_PASSWORD)))) { - /* find intersection of clip box and border edge */ - struct rect p; - p.x0 = x - border_left < r.x0 ? r.x0 : x - border_left; - p.y0 = y - border_top < r.y0 ? r.y0 : y - border_top; - p.x1 = x + padding_width + border_right < r.x1 ? - x + padding_width + border_right : r.x1; - p.y1 = y + padding_height + border_bottom < r.y1 ? - y + padding_height + border_bottom : r.y1; - if (!box->parent) { - /* Root element, special case: - * background covers margins too */ - int m_left, m_top, m_right, m_bottom; - if (scale == 1.0) { - m_left = box->margin[LEFT]; - m_top = box->margin[TOP]; - m_right = box->margin[RIGHT]; - m_bottom = box->margin[BOTTOM]; - } else { - m_left = box->margin[LEFT] * scale; - m_top = box->margin[TOP] * scale; - m_right = box->margin[RIGHT] * scale; - m_bottom = box->margin[BOTTOM] * scale; - } - p.x0 = p.x0 - m_left < r.x0 ? r.x0 : p.x0 - m_left; - p.y0 = p.y0 - m_top < r.y0 ? r.y0 : p.y0 - m_top; - p.x1 = p.x1 + m_right < r.x1 ? p.x1 + m_right : r.x1; - p.y1 = p.y1 + m_bottom < r.y1 ? p.y1 + m_bottom : r.y1; - } - /* valid clipping rectangles only */ - if ((p.x0 < p.x1) && (p.y0 < p.y1)) { - /* plot background */ - if (!html_redraw_background(x, y, box, scale, &p, - ¤t_background_color, bg_box, - &html->len_ctx, ctx)) - return false; - /* restore previous graphics window */ - if (ctx->plot->clip(ctx, &r) != NSERROR_OK) - return false; - } - } - - /* borders for block level content and replaced inlines */ - if (box->style && - box->type != BOX_TEXT && - box->type != BOX_INLINE_END && - (box->type != BOX_INLINE || box->object || - box->flags & IFRAME || box->flags & REPLACE_DIM || - (box->gadget != NULL && - (box->gadget->type == GADGET_TEXTAREA || - box->gadget->type == GADGET_TEXTBOX || - box->gadget->type == GADGET_PASSWORD))) && - (border_top || border_right || border_bottom || border_left)) { - if (!html_redraw_borders(box, x_parent, y_parent, - padding_width, padding_height, &r, - scale, ctx)) - return false; - } - - /* backgrounds and borders for non-replaced inlines */ - if (box->style && box->type == BOX_INLINE && box->inline_end && - (html_redraw_box_has_background(box) || - border_top || border_right || - border_bottom || border_left)) { - /* inline backgrounds and borders span other boxes and may - * wrap onto separate lines */ - struct box *ib; - struct rect b; /* border edge rectangle */ - struct rect p; /* clipped rect */ - bool first = true; - int ib_x; - int ib_y = y; - int ib_p_width; - int ib_b_left, ib_b_right; - - b.x0 = x - border_left; - b.x1 = x + padding_width + border_right; - b.y0 = y - border_top; - b.y1 = y + padding_height + border_bottom; - - p.x0 = b.x0 < r.x0 ? r.x0 : b.x0; - p.x1 = b.x1 < r.x1 ? b.x1 : r.x1; - p.y0 = b.y0 < r.y0 ? r.y0 : b.y0; - p.y1 = b.y1 < r.y1 ? b.y1 : r.y1; - for (ib = box; ib; ib = ib->next) { - /* to get extents of rectangle(s) associated with - * inline, cycle though all boxes in inline, skipping - * over floats */ - if (ib->type == BOX_FLOAT_LEFT || - ib->type == BOX_FLOAT_RIGHT) - continue; - if (scale == 1.0) { - ib_x = x_parent + ib->x; - ib_y = y_parent + ib->y; - ib_p_width = ib->padding[LEFT] + ib->width + - ib->padding[RIGHT]; - ib_b_left = ib->border[LEFT].width; - ib_b_right = ib->border[RIGHT].width; - } else { - ib_x = (x_parent + ib->x) * scale; - ib_y = (y_parent + ib->y) * scale; - ib_p_width = (ib->padding[LEFT] + ib->width + - ib->padding[RIGHT]) * scale; - ib_b_left = ib->border[LEFT].width * scale; - ib_b_right = ib->border[RIGHT].width * scale; - } - - if ((ib->flags & NEW_LINE) && ib != box) { - /* inline element has wrapped, plot background - * and borders */ - if (!html_redraw_inline_background( - x, y, box, scale, &p, b, - first, false, - ¤t_background_color, - &html->len_ctx, ctx)) - return false; - /* restore previous graphics window */ - if (ctx->plot->clip(ctx, &r) != NSERROR_OK) - return false; - if (!html_redraw_inline_borders(box, b, &r, - scale, first, false, ctx)) - return false; - /* reset coords */ - b.x0 = ib_x - ib_b_left; - b.y0 = ib_y - border_top - padding_top; - b.y1 = ib_y + padding_height - padding_top + - border_bottom; - - p.x0 = b.x0 < r.x0 ? r.x0 : b.x0; - p.y0 = b.y0 < r.y0 ? r.y0 : b.y0; - p.y1 = b.y1 < r.y1 ? b.y1 : r.y1; - - first = false; - } - - /* increase width for current box */ - b.x1 = ib_x + ib_p_width + ib_b_right; - p.x1 = b.x1 < r.x1 ? b.x1 : r.x1; - - if (ib == box->inline_end) - /* reached end of BOX_INLINE span */ - break; - } - /* plot background and borders for last rectangle of - * the inline */ - if (!html_redraw_inline_background(x, ib_y, box, scale, &p, b, - first, true, ¤t_background_color, - &html->len_ctx, ctx)) - return false; - /* restore previous graphics window */ - if (ctx->plot->clip(ctx, &r) != NSERROR_OK) - return false; - if (!html_redraw_inline_borders(box, b, &r, scale, first, true, - ctx)) - return false; - - } - - /* Debug outlines */ - if (html_redraw_debug) { - int margin_left, margin_right; - int margin_top, margin_bottom; - if (scale == 1.0) { - /* avoid trivial fp maths */ - margin_left = box->margin[LEFT]; - margin_top = box->margin[TOP]; - margin_right = box->margin[RIGHT]; - margin_bottom = box->margin[BOTTOM]; - } else { - margin_left = box->margin[LEFT] * scale; - margin_top = box->margin[TOP] * scale; - margin_right = box->margin[RIGHT] * scale; - margin_bottom = box->margin[BOTTOM] * scale; - } - /* Content edge -- blue */ - rect.x0 = x + padding_left; - rect.y0 = y + padding_top; - rect.x1 = x + padding_left + width; - rect.y1 = y + padding_top + height; - if (ctx->plot->rectangle(ctx, plot_style_content_edge, &rect) != NSERROR_OK) - return false; - - /* Padding edge -- red */ - rect.x0 = x; - rect.y0 = y; - rect.x1 = x + padding_width; - rect.y1 = y + padding_height; - if (ctx->plot->rectangle(ctx, plot_style_padding_edge, &rect) != NSERROR_OK) - return false; - - /* Margin edge -- yellow */ - rect.x0 = x - border_left - margin_left; - rect.y0 = y - border_top - margin_top; - rect.x1 = x + padding_width + border_right + margin_right; - rect.y1 = y + padding_height + border_bottom + margin_bottom; - if (ctx->plot->rectangle(ctx, plot_style_margin_edge, &rect) != NSERROR_OK) - return false; - } - - /* clip to the padding edge for objects, or boxes with overflow hidden - * or scroll, unless it's the root element */ - if (box->parent != NULL) { - bool need_clip = false; - if (box->object || box->flags & IFRAME || - (overflow_x != CSS_OVERFLOW_VISIBLE && - overflow_y != CSS_OVERFLOW_VISIBLE)) { - r.x0 = x; - r.y0 = y; - r.x1 = x + padding_width; - r.y1 = y + padding_height; - if (r.x0 < clip->x0) r.x0 = clip->x0; - if (r.y0 < clip->y0) r.y0 = clip->y0; - if (clip->x1 < r.x1) r.x1 = clip->x1; - if (clip->y1 < r.y1) r.y1 = clip->y1; - if (r.x1 <= r.x0 || r.y1 <= r.y0) { - return (!ctx->plot->group_end || - (ctx->plot->group_end(ctx) == NSERROR_OK)); - } - need_clip = true; - - } else if (overflow_x != CSS_OVERFLOW_VISIBLE) { - r.x0 = x; - r.y0 = clip->y0; - r.x1 = x + padding_width; - r.y1 = clip->y1; - if (r.x0 < clip->x0) r.x0 = clip->x0; - if (clip->x1 < r.x1) r.x1 = clip->x1; - if (r.x1 <= r.x0) { - return (!ctx->plot->group_end || - (ctx->plot->group_end(ctx) == NSERROR_OK)); - } - need_clip = true; - - } else if (overflow_y != CSS_OVERFLOW_VISIBLE) { - r.x0 = clip->x0; - r.y0 = y; - r.x1 = clip->x1; - r.y1 = y + padding_height; - if (r.y0 < clip->y0) r.y0 = clip->y0; - if (clip->y1 < r.y1) r.y1 = clip->y1; - if (r.y1 <= r.y0) { - return (!ctx->plot->group_end || - (ctx->plot->group_end(ctx) == NSERROR_OK)); - } - need_clip = true; - } - - if (need_clip && - (box->type == BOX_BLOCK || - box->type == BOX_INLINE_BLOCK || - box->type == BOX_TABLE_CELL || box->object)) { - if (ctx->plot->clip(ctx, &r) != NSERROR_OK) - return false; - } - } - - /* text decoration */ - if ((box->type != BOX_TEXT) && - box->style && - css_computed_text_decoration(box->style) != CSS_TEXT_DECORATION_NONE) { - if (!html_redraw_text_decoration(box, x_parent, y_parent, - scale, current_background_color, ctx)) - return false; - } - - if (box->object && width != 0 && height != 0) { - struct content_redraw_data obj_data; - - x_scrolled = x - scrollbar_get_offset(box->scroll_x) * scale; - y_scrolled = y - scrollbar_get_offset(box->scroll_y) * scale; - - obj_data.x = x_scrolled + padding_left; - obj_data.y = y_scrolled + padding_top; - obj_data.width = width; - obj_data.height = height; - obj_data.background_colour = current_background_color; - obj_data.scale = scale; - obj_data.repeat_x = false; - obj_data.repeat_y = false; - - if (content_get_type(box->object) == CONTENT_HTML) { - obj_data.x /= scale; - obj_data.y /= scale; - } - - if (!content_redraw(box->object, &obj_data, &r, ctx)) { - /* Show image fail */ - /* Unicode (U+FFFC) 'OBJECT REPLACEMENT CHARACTER' */ - const char *obj = "\xef\xbf\xbc"; - int obj_width; - int obj_x = x + padding_left; - nserror res; - - rect.x0 = x + padding_left; - rect.y0 = y + padding_top; - rect.x1 = x + padding_left + width - 1; - rect.y1 = y + padding_top + height - 1; - res = ctx->plot->rectangle(ctx, plot_style_broken_object, &rect); - if (res != NSERROR_OK) { - return false; - } - - res = guit->layout->width(plot_fstyle_broken_object, - obj, - sizeof(obj) - 1, - &obj_width); - if (res != NSERROR_OK) { - obj_x += 1; - } else { - obj_x += width / 2 - obj_width / 2; - } - - if (ctx->plot->text(ctx, - plot_fstyle_broken_object, - obj_x, y + padding_top + (int)(height * 0.75), - obj, sizeof(obj) - 1) != NSERROR_OK) - return false; - } - - } else if (box->iframe) { - /* Offset is passed to browser window redraw unscaled */ - browser_window_redraw(box->iframe, - (x + padding_left) / scale, - (y + padding_top) / scale, &r, ctx); - - } else if (box->gadget && box->gadget->type == GADGET_CHECKBOX) { - if (!html_redraw_checkbox(x + padding_left, y + padding_top, - width, height, box->gadget->selected, ctx)) - return false; - - } else if (box->gadget && box->gadget->type == GADGET_RADIO) { - if (!html_redraw_radio(x + padding_left, y + padding_top, - width, height, box->gadget->selected, ctx)) - return false; - - } else if (box->gadget && box->gadget->type == GADGET_FILE) { - if (!html_redraw_file(x + padding_left, y + padding_top, - width, height, box, scale, - current_background_color, &html->len_ctx, ctx)) - return false; - - } else if (box->gadget && - (box->gadget->type == GADGET_TEXTAREA || - box->gadget->type == GADGET_PASSWORD || - box->gadget->type == GADGET_TEXTBOX)) { - textarea_redraw(box->gadget->data.text.ta, x, y, - current_background_color, scale, &r, ctx); - - } else if (box->text) { - if (!html_redraw_text_box(html, box, x, y, &r, scale, - current_background_color, ctx)) - return false; - - } else { - if (!html_redraw_box_children(html, box, x_parent, y_parent, &r, - scale, current_background_color, ctx)) - return false; - } - - if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || - box->type == BOX_TABLE_CELL || box->type == BOX_INLINE) - if (ctx->plot->clip(ctx, clip) != NSERROR_OK) - return false; - - /* list marker */ - if (box->list_marker) { - if (!html_redraw_box(html, box->list_marker, - x_parent + box->x - - scrollbar_get_offset(box->scroll_x), - y_parent + box->y - - scrollbar_get_offset(box->scroll_y), - clip, scale, current_background_color, ctx)) - return false; - } - - /* scrollbars */ - if (((box->style && box->type != BOX_BR && - box->type != BOX_TABLE && box->type != BOX_INLINE && - (overflow_x == CSS_OVERFLOW_SCROLL || - overflow_x == CSS_OVERFLOW_AUTO || - overflow_y == CSS_OVERFLOW_SCROLL || - overflow_y == CSS_OVERFLOW_AUTO)) || - (box->object && content_get_type(box->object) == - CONTENT_HTML)) && box->parent != NULL) { - - has_x_scroll = box_hscrollbar_present(box); - has_y_scroll = box_vscrollbar_present(box); - - if (!box_handle_scrollbars((struct content *)html, - box, has_x_scroll, has_y_scroll)) - return false; - - if (box->scroll_x != NULL) - scrollbar_redraw(box->scroll_x, - x_parent + box->x, - y_parent + box->y + box->padding[TOP] + - box->height + box->padding[BOTTOM] - - SCROLLBAR_WIDTH, clip, scale, ctx); - if (box->scroll_y != NULL) - scrollbar_redraw(box->scroll_y, - x_parent + box->x + box->padding[LEFT] + - box->width + box->padding[RIGHT] - - SCROLLBAR_WIDTH, - y_parent + box->y, clip, scale, ctx); - } - - if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || - box->type == BOX_TABLE_CELL || box->type == BOX_INLINE) { - if (ctx->plot->clip(ctx, clip) != NSERROR_OK) - return false; - } - - return ((!plot->group_end) || (ctx->plot->group_end(ctx) == NSERROR_OK)); -} - -/** - * Draw a CONTENT_HTML using the current set of plotters (plot). - * - * \param c content of type CONTENT_HTML - * \param data redraw data for this content redraw - * \param clip current clip region - * \param ctx current redraw context - * \return true if successful, false otherwise - * - * x, y, clip_[xy][01] are in target coordinates. - */ - -bool html_redraw(struct content *c, struct content_redraw_data *data, - const struct rect *clip, const struct redraw_context *ctx) -{ - html_content *html = (html_content *) c; - struct box *box; - bool result = true; - bool select, select_only; - plot_style_t pstyle_fill_bg = { - .fill_type = PLOT_OP_TYPE_SOLID, - .fill_colour = data->background_colour, - }; - - box = html->layout; - assert(box); - - /* The select menu needs special treating because, when opened, it - * reaches beyond its layout box. - */ - select = false; - select_only = false; - if (ctx->interactive && html->visible_select_menu != NULL) { - struct form_control *control = html->visible_select_menu; - select = true; - /* check if the redraw rectangle is completely inside of the - select menu */ - select_only = form_clip_inside_select_menu(control, - data->scale, clip); - } - - if (!select_only) { - /* clear to background colour */ - result = (ctx->plot->clip(ctx, clip) == NSERROR_OK); - - if (html->background_colour != NS_TRANSPARENT) - pstyle_fill_bg.fill_colour = html->background_colour; - - result &= (ctx->plot->rectangle(ctx, &pstyle_fill_bg, clip) == NSERROR_OK); - - result &= html_redraw_box(html, box, data->x, data->y, clip, - data->scale, pstyle_fill_bg.fill_colour, ctx); - } - - if (select) { - int menu_x, menu_y; - box = html->visible_select_menu->box; - box_coords(box, &menu_x, &menu_y); - - menu_x -= box->border[LEFT].width; - menu_y += box->height + box->border[BOTTOM].width + - box->padding[BOTTOM] + box->padding[TOP]; - result &= form_redraw_select_menu(html->visible_select_menu, - data->x + menu_x, data->y + menu_y, - data->scale, clip, ctx); - } - - return result; - -} diff --git a/render/html_redraw_border.c b/render/html_redraw_border.c deleted file mode 100644 index 07c503c41..000000000 --- a/render/html_redraw_border.c +++ /dev/null @@ -1,928 +0,0 @@ -/* - * Copyright 2017 Vincent Sanders - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * - * Redrawing CONTENT_HTML borders implementation. - */ - -#include -#include - -#include "utils/log.h" -#include "netsurf/plotters.h" -#include "netsurf/css.h" - -#include "render/box.h" -#include "render/html_internal.h" - - -static plot_style_t plot_style_bdr = { - .stroke_type = PLOT_OP_TYPE_DASH, -}; -static plot_style_t plot_style_fillbdr = { - .fill_type = PLOT_OP_TYPE_SOLID, -}; -static plot_style_t plot_style_fillbdr_dark = { - .fill_type = PLOT_OP_TYPE_SOLID, -}; -static plot_style_t plot_style_fillbdr_light = { - .fill_type = PLOT_OP_TYPE_SOLID, -}; -static plot_style_t plot_style_fillbdr_ddark = { - .fill_type = PLOT_OP_TYPE_SOLID, -}; -static plot_style_t plot_style_fillbdr_dlight = { - .fill_type = PLOT_OP_TYPE_SOLID, -}; - - -static inline nserror -plot_clipped_rectangle(const struct redraw_context *ctx, - const plot_style_t *style, - const struct rect *clip, - struct rect *rect) -{ - nserror res; - - rect->x0 = (clip->x0 > rect->x0) ? clip->x0 : rect->x0; - rect->y0 = (clip->y0 > rect->y0) ? clip->y0 : rect->y0; - rect->x1 = (clip->x1 < rect->x1) ? clip->x1 : rect->x1; - rect->y1 = (clip->y1 < rect->y1) ? clip->y1 : rect->y1; - if ((rect->x0 < rect->x1) && (rect->y0 < rect->y1)) { - /* valid clip rectangles only */ - res = ctx->plot->rectangle(ctx, style, rect); - } else { - res = NSERROR_OK; - } - return res; -} - - -/** - * Draw one border. - * - * \param side index of border side (TOP, RIGHT, BOTTOM, LEFT) - * \param p array of precomputed border vertices - * \param c colour for border - * \param style border line style - * \param thickness border thickness - * \param rectangular whether border is rectangular - * \param clip cliping area for redrawing border. - * \param ctx current redraw context - * \return NSERROR_OK if successful otherwise appropriate error code - */ -static nserror -html_redraw_border_plot(const int side, - const int *p, - colour c, - enum css_border_style_e style, - int thickness, - bool rectangular, - const struct rect *clip, - const struct redraw_context *ctx) -{ - int z[8]; /* Vertices of border part */ - unsigned int light = side; - plot_style_t *plot_style_bdr_in; - plot_style_t *plot_style_bdr_out; - nserror res = NSERROR_OK; - struct rect rect; - - if (c == NS_TRANSPARENT) { - return res; - } - - plot_style_bdr.stroke_type = PLOT_OP_TYPE_DASH; - plot_style_bdr.stroke_colour = c; - plot_style_bdr.stroke_width = thickness; - plot_style_fillbdr.fill_colour = c; - plot_style_fillbdr_dark.fill_colour = darken_colour(c); - plot_style_fillbdr_light.fill_colour = lighten_colour(c); - plot_style_fillbdr_ddark.fill_colour = double_darken_colour(c); - plot_style_fillbdr_dlight.fill_colour = double_lighten_colour(c); - - switch (style) { - case CSS_BORDER_STYLE_DOTTED: - plot_style_bdr.stroke_type = PLOT_OP_TYPE_DOT; - /* fall through */ - case CSS_BORDER_STYLE_DASHED: - rect.x0 = (p[0] + p[2]) / 2; - rect.y0 = (p[1] + p[3]) / 2; - rect.x1 = (p[4] + p[6]) / 2; - rect.y1 = (p[5] + p[7]) / 2; - res = ctx->plot->line(ctx, &plot_style_bdr, &rect); - break; - - case CSS_BORDER_STYLE_SOLID: - /* fall through to default */ - default: - if (rectangular || thickness == 1) { - - if (side == TOP || side == RIGHT) { - rect.x0 = p[2]; - rect.y0 = p[3]; - if ((side == TOP) && - (p[4] - p[6] != 0)) { - rect.x1 = p[4]; - } else { - rect.x1 = p[6]; - } - rect.y1 = p[7]; - } else { - rect.x0 = p[6]; - rect.y0 = p[7]; - rect.x1 = p[2]; - if ((side == LEFT) && - (p[1] - p[3] != 0)) { - rect.y1 = p[1]; - } else { - rect.y1 = p[3]; - } - } - res = plot_clipped_rectangle(ctx, - &plot_style_fillbdr, - clip, - &rect); - } else { - res = ctx->plot->polygon(ctx, &plot_style_fillbdr, p, 4); - } - break; - - case CSS_BORDER_STYLE_DOUBLE: - z[0] = p[0]; - z[1] = p[1]; - z[2] = (p[0] * 2 + p[2]) / 3; - z[3] = (p[1] * 2 + p[3]) / 3; - z[4] = (p[6] * 2 + p[4]) / 3; - z[5] = (p[7] * 2 + p[5]) / 3; - z[6] = p[6]; - z[7] = p[7]; - res = ctx->plot->polygon(ctx, &plot_style_fillbdr, z, 4); - if (res == NSERROR_OK) { - z[0] = p[2]; - z[1] = p[3]; - z[2] = (p[2] * 2 + p[0]) / 3; - z[3] = (p[3] * 2 + p[1]) / 3; - z[4] = (p[4] * 2 + p[6]) / 3; - z[5] = (p[5] * 2 + p[7]) / 3; - z[6] = p[4]; - z[7] = p[5]; - res = ctx->plot->polygon(ctx, &plot_style_fillbdr, z, 4); - } - break; - - case CSS_BORDER_STYLE_GROOVE: - light = 3 - light; - /* fall through */ - case CSS_BORDER_STYLE_RIDGE: - /* choose correct colours for each part of the border line */ - if (light <= 1) { - plot_style_bdr_in = &plot_style_fillbdr_dark; - plot_style_bdr_out = &plot_style_fillbdr_light; - } else { - plot_style_bdr_in = &plot_style_fillbdr_light; - plot_style_bdr_out = &plot_style_fillbdr_dark; - } - - /* Render border */ - if ((rectangular || thickness == 2) && thickness != 1) { - /* Border made up from two parts and can be - * plotted with rectangles - */ - - /* First part */ - if (side == TOP || side == RIGHT) { - rect.x0 = (p[0] + p[2]) / 2; - rect.y0 = (p[1] + p[3]) / 2; - rect.x1 = p[6]; - rect.y1 = p[7]; - } else { - rect.x0 = p[6]; - rect.y0 = p[7]; - rect.x1 = (p[0] + p[2]) / 2; - rect.y1 = (p[1] + p[3]) / 2; - } - res = plot_clipped_rectangle(ctx, - plot_style_bdr_in, - clip, - &rect); - if (res != NSERROR_OK) { - return res; - } - - /* Second part */ - if (side == TOP || side == RIGHT) { - rect.x0 = p[2]; - rect.y0 = p[3]; - rect.x1 = (p[6] + p[4]) / 2; - rect.y1 = (p[7] + p[5]) / 2; - } else { - rect.x0 = (p[6] + p[4]) / 2; - rect.y0 = (p[7] + p[5]) / 2; - rect.x1 = p[2]; - rect.y1 = p[3]; - } - res = plot_clipped_rectangle(ctx, - plot_style_bdr_out, - clip, - &rect); - } else if (thickness == 1) { - /* Border made up from one part which can be - * plotted as a rectangle - */ - - if (side == TOP || side == RIGHT) { - rect.x0 = p[2]; - rect.y0 = p[3]; - rect.x1 = p[6]; - rect.y1 = p[7]; - rect.x1 = ((side == TOP) && (p[4] - p[6] != 0)) ? - rect.x1 + p[4] - p[6] : rect.x1; - - res = plot_clipped_rectangle(ctx, - plot_style_bdr_in, - clip, - &rect); - } else { - rect.x0 = p[6]; - rect.y0 = p[7]; - rect.x1 = p[2]; - rect.y1 = p[3]; - rect.y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ? - rect.y1 + p[1] - p[3] : rect.y1; - res = plot_clipped_rectangle(ctx, - plot_style_bdr_out, - clip, - &rect); - } - } else { - /* Border made up from two parts and can't be - * plotted with rectangles - */ - z[0] = p[0]; - z[1] = p[1]; - z[2] = (p[0] + p[2]) / 2; - z[3] = (p[1] + p[3]) / 2; - z[4] = (p[6] + p[4]) / 2; - z[5] = (p[7] + p[5]) / 2; - z[6] = p[6]; - z[7] = p[7]; - res = ctx->plot->polygon(ctx, plot_style_bdr_in, z, 4); - if (res == NSERROR_OK) { - z[0] = p[2]; - z[1] = p[3]; - z[6] = p[4]; - z[7] = p[5]; - res = ctx->plot->polygon(ctx, - plot_style_bdr_out, - z, - 4); - } - } - break; - - case CSS_BORDER_STYLE_INSET: - light = (light + 2) % 4; - /* fall through */ - case CSS_BORDER_STYLE_OUTSET: - /* choose correct colours for each part of the border line */ - switch (light) { - case 0: - plot_style_bdr_in = &plot_style_fillbdr_light; - plot_style_bdr_out = &plot_style_fillbdr_dlight; - break; - case 1: - plot_style_bdr_in = &plot_style_fillbdr_ddark; - plot_style_bdr_out = &plot_style_fillbdr_dark; - break; - case 2: - plot_style_bdr_in = &plot_style_fillbdr_dark; - plot_style_bdr_out = &plot_style_fillbdr_ddark; - break; - case 3: - plot_style_bdr_in = &plot_style_fillbdr_dlight; - plot_style_bdr_out = &plot_style_fillbdr_light; - break; - default: - plot_style_bdr_in = &plot_style_fillbdr; - plot_style_bdr_out = &plot_style_fillbdr; - break; - } - - /* Render border */ - if ((rectangular || thickness == 2) && thickness != 1) { - /* Border made up from two parts and can be - * plotted with rectangles - */ - - /* First part */ - if (side == TOP || side == RIGHT) { - rect.x0 = (p[0] + p[2]) / 2; - rect.y0 = (p[1] + p[3]) / 2; - rect.x1 = p[6]; - rect.y1 = p[7]; - } else { - rect.x0 = p[6]; - rect.y0 = p[7]; - rect.x1 = (p[0] + p[2]) / 2; - rect.y1 = (p[1] + p[3]) / 2; - } - res = plot_clipped_rectangle(ctx, - plot_style_bdr_in, - clip, - &rect); - if (res != NSERROR_OK) { - return res; - } - - /* Second part */ - if (side == TOP || side == RIGHT) { - rect.x0 = p[2]; - rect.y0 = p[3]; - rect.x1 = (p[6] + p[4]) / 2; - rect.y1 = (p[7] + p[5]) / 2; - } else { - rect.x0 = (p[6] + p[4]) / 2; - rect.y0 = (p[7] + p[5]) / 2; - rect.x1 = p[2]; - rect.y1 = p[3]; - } - res = plot_clipped_rectangle(ctx, - plot_style_bdr_out, - clip, - &rect); - } else if (thickness == 1) { - /* Border made up from one part which can be - * plotted as a rectangle - */ - - if (side == TOP || side == RIGHT) { - rect.x0 = p[2]; - rect.y0 = p[3]; - rect.x1 = p[6]; - rect.y1 = p[7]; - rect.x1 = ((side == TOP) && (p[4] - p[6] != 0)) ? - rect.x1 + p[4] - p[6] : rect.x1; - res = plot_clipped_rectangle(ctx, - plot_style_bdr_in, - clip, - &rect); - } else { - rect.x0 = p[6]; - rect.y0 = p[7]; - rect.x1 = p[2]; - rect.y1 = p[3]; - rect.y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ? - rect.y1 + p[1] - p[3] : rect.y1; - res = plot_clipped_rectangle(ctx, - plot_style_bdr_out, - clip, - &rect); - } - } else { - /* Border made up from two parts and can't be - * plotted with rectangles - */ - - z[0] = p[0]; - z[1] = p[1]; - z[2] = (p[0] + p[2]) / 2; - z[3] = (p[1] + p[3]) / 2; - z[4] = (p[6] + p[4]) / 2; - z[5] = (p[7] + p[5]) / 2; - z[6] = p[6]; - z[7] = p[7]; - res = ctx->plot->polygon(ctx, plot_style_bdr_in, z, 4); - if (res != NSERROR_OK) { - return res; - } - z[0] = p[2]; - z[1] = p[3]; - z[6] = p[4]; - z[7] = p[5]; - res = ctx->plot->polygon(ctx, plot_style_bdr_out, z, 4); - } - break; - } - - return res; -} - - -/** - * Draw borders for a box. - * - * \param box box to draw - * \param x_parent coordinate of left padding edge of parent of box - * \param y_parent coordinate of top padding edge of parent of box - * \param p_width width of padding box - * \param p_height height of padding box - * \param clip cliping area for redrawing border. - * \param scale scale for redraw - * \param ctx current redraw context - * \return true if successful, false otherwise - */ -bool -html_redraw_borders(struct box *box, - int x_parent, - int y_parent, - int p_width, - int p_height, - const struct rect *clip, - float scale, - const struct redraw_context *ctx) -{ - unsigned int sides[] = { LEFT, RIGHT, TOP, BOTTOM }; - int top = box->border[TOP].width; - int right = box->border[RIGHT].width; - int bottom = box->border[BOTTOM].width; - int left = box->border[LEFT].width; - int x, y; - unsigned int i, side; - int p[8]; /* Box border vertices */ - int z[8]; /* Border vertices */ - bool square_end_1 = false; - bool square_end_2 = false; - nserror res; - - x = x_parent + box->x; - y = y_parent + box->y; - - if (scale != 1.0) { - top *= scale; - right *= scale; - bottom *= scale; - left *= scale; - x *= scale; - y *= scale; - } - - assert(box->style); - - /* Calculate border vertices - * - * A----------------------+ - * | \ / | - * | B--------------+ | - * | | | | - * | +--------------C | - * | / \ | - * +----------------------D - */ - p[0] = x - left; p[1] = y - top; /* A */ - p[2] = x; p[3] = y; /* B */ - p[4] = x + p_width; p[5] = y + p_height; /* C */ - p[6] = x + p_width + right; p[7] = y + p_height + bottom; /* D */ - - for (i = 0; i != 4; i++) { - colour col = 0; - side = sides[i]; /* plot order */ - - if (box->border[side].width == 0 || - nscss_color_is_transparent(box->border[side].c)) { - continue; - } - - switch (side) { - case LEFT: - square_end_1 = (top == 0); - square_end_2 = (bottom == 0); - - z[0] = p[0]; z[1] = p[7]; - z[2] = p[2]; z[3] = p[5]; - z[4] = p[2]; z[5] = p[3]; - z[6] = p[0]; z[7] = p[1]; - - if (nscss_color_is_transparent(box->border[TOP].c) == false && - box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) { - /* make border overhang top corner fully, - * if top border is opaque - */ - z[5] -= top; - square_end_1 = true; - } - if (nscss_color_is_transparent(box->border[BOTTOM].c) == false && - box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) { - /* make border overhang bottom corner fully, - * if bottom border is opaque - */ - z[3] += bottom; - square_end_2 = true; - } - - col = nscss_color_to_ns(box->border[side].c); - - res = html_redraw_border_plot(side, - z, - col, - box->border[side].style, - box->border[side].width * scale, - square_end_1 && square_end_2, - clip, - ctx); - if (res != NSERROR_OK) { - return false; - } - break; - - case RIGHT: - square_end_1 = (top == 0); - square_end_2 = (bottom == 0); - - z[0] = p[6]; z[1] = p[1]; - z[2] = p[4]; z[3] = p[3]; - z[4] = p[4]; z[5] = p[5]; - z[6] = p[6]; z[7] = p[7]; - - if (nscss_color_is_transparent(box->border[TOP].c) == false && - box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) { - /* make border overhang top corner fully, - * if top border is opaque - */ - z[3] -= top; - square_end_1 = true; - } - if (nscss_color_is_transparent(box->border[BOTTOM].c) == false && - box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) { - /* make border overhang bottom corner fully, - * if bottom border is opaque - */ - z[5] += bottom; - square_end_2 = true; - } - - col = nscss_color_to_ns(box->border[side].c); - - res = html_redraw_border_plot(side, - z, - col, - box->border[side].style, - box->border[side].width * scale, - square_end_1 && square_end_2, - clip, - ctx); - if (res != NSERROR_OK) { - return false; - } - break; - - case TOP: - if (clip->y0 > p[3]) { - /* clip rectangle is below border; nothing to - * plot - */ - continue; - } - - square_end_1 = (left == 0); - square_end_2 = (right == 0); - - z[0] = p[2]; z[1] = p[3]; - z[2] = p[0]; z[3] = p[1]; - z[4] = p[6]; z[5] = p[1]; - z[6] = p[4]; z[7] = p[3]; - - if (box->border[TOP].style == CSS_BORDER_STYLE_SOLID && - box->border[TOP].c == box->border[LEFT].c) { - /* don't bother overlapping left corner if - * it's the same colour anyway - */ - z[2] += left; - square_end_1 = true; - } - if (box->border[TOP].style == CSS_BORDER_STYLE_SOLID && - box->border[TOP].c == box->border[RIGHT].c) { - /* don't bother overlapping right corner if - * it's the same colour anyway - */ - z[4] -= right; - square_end_2 = true; - } - - col = nscss_color_to_ns(box->border[side].c); - - res = html_redraw_border_plot(side, - z, - col, - box->border[side].style, - box->border[side].width * scale, - square_end_1 && square_end_2, - clip, - ctx); - if (res != NSERROR_OK) { - return false; - } - break; - - case BOTTOM: - if (clip->y1 < p[5]) { - /* clip rectangle is above border; nothing to - * plot - */ - continue; - } - - square_end_1 = (left == 0); - square_end_2 = (right == 0); - - z[0] = p[4]; z[1] = p[5]; - z[2] = p[6]; z[3] = p[7]; - z[4] = p[0]; z[5] = p[7]; - z[6] = p[2]; z[7] = p[5]; - - if (box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID && - box->border[BOTTOM].c == box->border[LEFT].c) { - /* don't bother overlapping left corner if - * it's the same colour anyway - */ - z[4] += left; - square_end_1 = true; - } - if (box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID && - box->border[BOTTOM].c == box->border[RIGHT].c) { - /* don't bother overlapping right corner if - * it's the same colour anyway - */ - z[2] -= right; - square_end_2 = true; - } - - col = nscss_color_to_ns(box->border[side].c); - - res = html_redraw_border_plot(side, - z, - col, - box->border[side].style, - box->border[side].width * scale, - square_end_1 && square_end_2, - clip, - ctx); - if (res != NSERROR_OK) { - return false; - } - break; - - default: - assert(side == TOP || side == BOTTOM || - side == LEFT || side == RIGHT); - break; - } - } - - return true; -} - - -/** - * Draw an inline's borders. - * - * \param box BOX_INLINE which created the border - * \param b coordinates of border edge rectangle - * \param clip cliping area for redrawing border. - * \param scale scale for redraw - * \param first true if this is the first rectangle associated with the inline - * \param last true if this is the last rectangle associated with the inline - * \param ctx current redraw context - * \return true if successful, false otherwise - */ -bool -html_redraw_inline_borders(struct box *box, - struct rect b, - const struct rect *clip, - float scale, - bool first, - bool last, - const struct redraw_context *ctx) -{ - int top = box->border[TOP].width; - int right = box->border[RIGHT].width; - int bottom = box->border[BOTTOM].width; - int left = box->border[LEFT].width; - colour col; - int p[8]; /* Box border vertices */ - int z[8]; /* Border vertices */ - bool square_end_1; - bool square_end_2; - nserror res; - - if (scale != 1.0) { - top *= scale; - right *= scale; - bottom *= scale; - left *= scale; - } - - /* Calculate border vertices - * - * A----------------------+ - * | \ / | - * | B--------------+ | - * | | | | - * | +--------------C | - * | / \ | - * +----------------------D - */ - p[0] = b.x0; p[1] = b.y0; /* A */ - p[2] = first ? b.x0 + left : b.x0; p[3] = b.y0 + top; /* B */ - p[4] = last ? b.x1 - right : b.x1; p[5] = b.y1 - bottom; /* C */ - p[6] = b.x1; p[7] = b.y1; /* D */ - - assert(box->style); - - /* Left */ - square_end_1 = (top == 0); - square_end_2 = (bottom == 0); - if (left != 0 && - first && - nscss_color_is_transparent(box->border[LEFT].c) == false) { - col = nscss_color_to_ns(box->border[LEFT].c); - - z[0] = p[0]; z[1] = p[7]; - z[2] = p[2]; z[3] = p[5]; - z[4] = p[2]; z[5] = p[3]; - z[6] = p[0]; z[7] = p[1]; - - if (nscss_color_is_transparent(box->border[TOP].c) == false && - box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) { - /* make border overhang top corner fully, - * if top border is opaque - */ - z[5] -= top; - square_end_1 = true; - } - - if (nscss_color_is_transparent(box->border[BOTTOM].c) == false && - box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) { - /* make border overhang bottom corner fully, - * if bottom border is opaque - */ - z[3] += bottom; - square_end_2 = true; - } - - res = html_redraw_border_plot(LEFT, - z, - col, - box->border[LEFT].style, - left, - square_end_1 && square_end_2, - clip, - ctx); - if (res != NSERROR_OK) { - return false; - } - } - - /* Right */ - square_end_1 = (top == 0); - square_end_2 = (bottom == 0); - if (right != 0 && - last && - nscss_color_is_transparent(box->border[RIGHT].c) == false) { - col = nscss_color_to_ns(box->border[RIGHT].c); - - z[0] = p[6]; z[1] = p[1]; - z[2] = p[4]; z[3] = p[3]; - z[4] = p[4]; z[5] = p[5]; - z[6] = p[6]; z[7] = p[7]; - - if (nscss_color_is_transparent(box->border[TOP].c) == false && - box->border[TOP].style != CSS_BORDER_STYLE_DOUBLE) { - /* make border overhang top corner fully, - * if top border is opaque - */ - z[3] -= top; - square_end_1 = true; - } - - if (nscss_color_is_transparent(box->border[BOTTOM].c) == false && - box->border[BOTTOM].style != CSS_BORDER_STYLE_DOUBLE) { - /* make border overhang bottom corner fully, - * if bottom border is opaque - */ - z[5] += bottom; - square_end_2 = true; - } - - res = html_redraw_border_plot(RIGHT, - z, - col, - box->border[RIGHT].style, - right, - square_end_1 && square_end_2, - clip, - ctx); - if (res != NSERROR_OK) { - return false; - } - } - - /* Top */ - square_end_1 = (left == 0); - square_end_2 = (right == 0); - if (top != 0 && - nscss_color_is_transparent(box->border[TOP].c) == false) { - col = nscss_color_to_ns(box->border[TOP].c); - - z[0] = p[2]; z[1] = p[3]; - z[2] = p[0]; z[3] = p[1]; - z[4] = p[6]; z[5] = p[1]; - z[6] = p[4]; z[7] = p[3]; - - if (first && - box->border[TOP].style == CSS_BORDER_STYLE_SOLID && - box->border[TOP].c == box->border[LEFT].c) { - /* don't bother overlapping left corner if - * it's the same colour anyway - */ - z[2] += left; - square_end_1 = true; - } - - if (last && - box->border[TOP].style == CSS_BORDER_STYLE_SOLID && - box->border[TOP].c == box->border[RIGHT].c) { - /* don't bother overlapping right corner if - * it's the same colour anyway - */ - z[4] -= right; - square_end_2 = true; - } - - res = html_redraw_border_plot(TOP, - z, - col, - box->border[TOP].style, - top, - square_end_1 && square_end_2, - clip, - ctx); - if (res != NSERROR_OK) { - return false; - } - } - - /* Bottom */ - square_end_1 = (left == 0); - square_end_2 = (right == 0); - if (bottom != 0 && - nscss_color_is_transparent(box->border[BOTTOM].c) == false) { - col = nscss_color_to_ns(box->border[BOTTOM].c); - - z[0] = p[4]; z[1] = p[5]; - z[2] = p[6]; z[3] = p[7]; - z[4] = p[0]; z[5] = p[7]; - z[6] = p[2]; z[7] = p[5]; - - if (first && - box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID && - box->border[BOTTOM].c == box->border[LEFT].c) { - /* don't bother overlapping left corner if - * it's the same colour anyway - */ - z[4] += left; - square_end_1 = true; - } - - if (last && - box->border[BOTTOM].style == CSS_BORDER_STYLE_SOLID && - box->border[BOTTOM].c == box->border[RIGHT].c) { - /* don't bother overlapping right corner if - * it's the same colour anyway - */ - z[2] -= right; - square_end_2 = true; - } - - res = html_redraw_border_plot(BOTTOM, - z, - col, - box->border[BOTTOM].style, - bottom, - square_end_1 && square_end_2, - clip, - ctx); - if (res != NSERROR_OK) { - return false; - } - } - - return true; -} diff --git a/render/html_script.c b/render/html_script.c deleted file mode 100644 index c73a4806d..000000000 --- a/render/html_script.c +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright 2012 Vincent Sanders - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Content for text/html scripts (implementation). - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "utils/config.h" -#include "utils/corestrings.h" -#include "utils/log.h" -#include "utils/messages.h" -#include "netsurf/content.h" -#include "javascript/js.h" -#include "content/content_protected.h" -#include "content/fetch.h" -#include "content/hlcache.h" - -#include "render/html_internal.h" - -typedef bool (script_handler_t)(struct jscontext *jscontext, const char *data, size_t size) ; - - -static script_handler_t *select_script_handler(content_type ctype) -{ - if (ctype == CONTENT_JS) { - return js_exec; - } - return NULL; -} - - -/* exported internal interface documented in render/html_internal.h */ -nserror html_script_exec(html_content *c) -{ - unsigned int i; - struct html_script *s; - script_handler_t *script_handler; - - if (c->jscontext == NULL) { - return NSERROR_BAD_PARAMETER; - } - - for (i = 0, s = c->scripts; i != c->scripts_count; i++, s++) { - if (s->already_started) { - continue; - } - - if ((s->type == HTML_SCRIPT_ASYNC) || - (s->type == HTML_SCRIPT_DEFER)) { - /* ensure script content is present */ - if (s->data.handle == NULL) - continue; - - /* ensure script content fetch status is not an error */ - if (content_get_status(s->data.handle) == - CONTENT_STATUS_ERROR) - continue; - - /* ensure script handler for content type */ - script_handler = select_script_handler( - content_get_type(s->data.handle)); - if (script_handler == NULL) - continue; /* unsupported type */ - - if (content_get_status(s->data.handle) == - CONTENT_STATUS_DONE) { - /* external script is now available */ - const char *data; - unsigned long size; - data = content_get_source_data( - s->data.handle, &size ); - script_handler(c->jscontext, data, size); - - s->already_started = true; - - } - } - } - - return NSERROR_OK; -} - -/* create new html script entry */ -static struct html_script * -html_process_new_script(html_content *c, - dom_string *mimetype, - enum html_script_type type) -{ - struct html_script *nscript; - /* add space for new script entry */ - nscript = realloc(c->scripts, - sizeof(struct html_script) * (c->scripts_count + 1)); - if (nscript == NULL) { - return NULL; - } - - c->scripts = nscript; - - /* increment script entry count */ - nscript = &c->scripts[c->scripts_count]; - c->scripts_count++; - - nscript->already_started = false; - nscript->parser_inserted = false; - nscript->force_async = true; - nscript->ready_exec = false; - nscript->async = false; - nscript->defer = false; - - nscript->type = type; - - nscript->mimetype = dom_string_ref(mimetype); /* reference mimetype */ - - return nscript; -} - -/** - * Callback for asyncronous scripts - */ -static nserror -convert_script_async_cb(hlcache_handle *script, - const hlcache_event *event, - void *pw) -{ - html_content *parent = pw; - unsigned int i; - struct html_script *s; - - /* Find script */ - for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) { - if (s->type == HTML_SCRIPT_ASYNC && s->data.handle == script) - break; - } - - assert(i != parent->scripts_count); - - switch (event->type) { - case CONTENT_MSG_LOADING: - break; - - case CONTENT_MSG_READY: - break; - - case CONTENT_MSG_DONE: - NSLOG(netsurf, INFO, "script %d done '%s'", i, - nsurl_access(hlcache_handle_get_url(script))); - parent->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); - - break; - - case CONTENT_MSG_ERROR: - NSLOG(netsurf, INFO, "script %s failed: %s", - nsurl_access(hlcache_handle_get_url(script)), - event->data.error); - /* fall through */ - - case CONTENT_MSG_ERRORCODE: - hlcache_handle_release(script); - s->data.handle = NULL; - parent->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); - content_add_error(&parent->base, "?", 0); - - break; - - default: - break; - } - - /* if there are no active fetches remaining begin post parse - * conversion - */ - if (html_can_begin_conversion(parent)) { - html_begin_conversion(parent); - } - - return NSERROR_OK; -} - -/** - * Callback for defer scripts - */ -static nserror -convert_script_defer_cb(hlcache_handle *script, - const hlcache_event *event, - void *pw) -{ - html_content *parent = pw; - unsigned int i; - struct html_script *s; - - /* Find script */ - for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) { - if (s->type == HTML_SCRIPT_DEFER && s->data.handle == script) - break; - } - - assert(i != parent->scripts_count); - - switch (event->type) { - - case CONTENT_MSG_DONE: - NSLOG(netsurf, INFO, "script %d done '%s'", i, - nsurl_access(hlcache_handle_get_url(script))); - parent->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); - - break; - - case CONTENT_MSG_ERROR: - NSLOG(netsurf, INFO, "script %s failed: %s", - nsurl_access(hlcache_handle_get_url(script)), - event->data.error); - /* fall through */ - - case CONTENT_MSG_ERRORCODE: - hlcache_handle_release(script); - s->data.handle = NULL; - parent->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); - content_add_error(&parent->base, "?", 0); - - break; - - default: - break; - } - - /* if there are no active fetches remaining begin post parse - * conversion - */ - if (html_can_begin_conversion(parent)) { - html_begin_conversion(parent); - } - - return NSERROR_OK; -} - -/** - * Callback for syncronous scripts - */ -static nserror -convert_script_sync_cb(hlcache_handle *script, - const hlcache_event *event, - void *pw) -{ - html_content *parent = pw; - unsigned int i; - struct html_script *s; - script_handler_t *script_handler; - dom_hubbub_error err; - - /* Find script */ - for (i = 0, s = parent->scripts; i != parent->scripts_count; i++, s++) { - if (s->type == HTML_SCRIPT_SYNC && s->data.handle == script) - break; - } - - assert(i != parent->scripts_count); - - switch (event->type) { - case CONTENT_MSG_DONE: - NSLOG(netsurf, INFO, "script %d done '%s'", i, - nsurl_access(hlcache_handle_get_url(script))); - parent->base.active--; - NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); - - s->already_started = true; - - /* attempt to execute script */ - script_handler = select_script_handler(content_get_type(s->data.handle)); - if (script_handler != NULL && parent->jscontext != NULL) { - /* script has a handler */ - const char *data; - unsigned long size; - data = content_get_source_data(s->data.handle, &size ); - script_handler(parent->jscontext, data, size); - } - - /* continue parse */ - err = dom_hubbub_parser_pause(parent->parser, false); - if (err != DOM_HUBBUB_OK) { - NSLOG(netsurf, INFO, "unpause returned 0x%x", err); - } - - break; - - case CONTENT_MSG_ERROR: - NSLOG(netsurf, INFO, "script %s failed: %s", - nsurl_access(hlcache_handle_get_url(script)), - event->data.error); - /* fall through */ - - case CONTENT_MSG_ERRORCODE: - hlcache_handle_release(script); - s->data.handle = NULL; - parent->base.active--; - - NSLOG(netsurf, INFO, "%d fetches active", parent->base.active); - content_add_error(&parent->base, "?", 0); - - s->already_started = true; - - /* continue parse */ - err = dom_hubbub_parser_pause(parent->parser, false); - if (err != DOM_HUBBUB_OK) { - NSLOG(netsurf, INFO, "unpause returned 0x%x", err); - } - - break; - - default: - break; - } - - /* if there are no active fetches remaining begin post parse - * conversion - */ - if (html_can_begin_conversion(parent)) { - html_begin_conversion(parent); - } - - return NSERROR_OK; -} - -/** - * process a script with a src tag - */ -static dom_hubbub_error -exec_src_script(html_content *c, - dom_node *node, - dom_string *mimetype, - dom_string *src) -{ - nserror ns_error; - nsurl *joined; - hlcache_child_context child; - struct html_script *nscript; - bool async; - bool defer; - enum html_script_type script_type; - hlcache_handle_callback script_cb; - dom_hubbub_error ret = DOM_HUBBUB_OK; - dom_exception exc; /* returned by libdom functions */ - - /* src url */ - ns_error = nsurl_join(c->base_url, dom_string_data(src), &joined); - if (ns_error != NSERROR_OK) { - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); - return DOM_HUBBUB_NOMEM; - } - - NSLOG(netsurf, INFO, "script %i '%s'", c->scripts_count, - nsurl_access(joined)); - - /* there are three ways to process the script tag at this point: - * - * Syncronously pause the parent parse and continue after - * the script has downloaded and executed. (default) - * Async Start the script downloading and execute it when it - * becomes available. - * Defered Start the script downloading and execute it when - * the page has completed parsing, may be set along - * with async where it is ignored. - */ - - /* we interpret the presence of the async and defer attribute - * as true and ignore its value, technically only the empty - * value or the attribute name itself are valid. However - * various browsers interpret this in various ways the most - * compatible approach is to be liberal and accept any - * value. Note setting the values to "false" still makes them true! - */ - exc = dom_element_has_attribute(node, corestring_dom_async, &async); - if (exc != DOM_NO_ERR) { - return DOM_HUBBUB_OK; /* dom error */ - } - - if (async) { - /* asyncronous script */ - script_type = HTML_SCRIPT_ASYNC; - script_cb = convert_script_async_cb; - - } else { - exc = dom_element_has_attribute(node, - corestring_dom_defer, &defer); - if (exc != DOM_NO_ERR) { - return DOM_HUBBUB_OK; /* dom error */ - } - - if (defer) { - /* defered script */ - script_type = HTML_SCRIPT_DEFER; - script_cb = convert_script_defer_cb; - } else { - /* syncronous script */ - script_type = HTML_SCRIPT_SYNC; - script_cb = convert_script_sync_cb; - } - } - - nscript = html_process_new_script(c, mimetype, script_type); - if (nscript == NULL) { - nsurl_unref(joined); - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); - return DOM_HUBBUB_NOMEM; - } - - /* set up child fetch encoding and quirks */ - child.charset = c->encoding; - child.quirks = c->base.quirks; - - ns_error = hlcache_handle_retrieve(joined, - 0, - content_get_url(&c->base), - NULL, - script_cb, - c, - &child, - CONTENT_SCRIPT, - &nscript->data.handle); - - - nsurl_unref(joined); - - if (ns_error != NSERROR_OK) { - /* @todo Deal with fetch error better. currently assume - * fetch never became active - */ - /* mark duff script fetch as already started */ - nscript->already_started = true; - NSLOG(netsurf, INFO, "Fetch failed with error %d", ns_error); - } else { - /* update base content active fetch count */ - c->base.active++; - NSLOG(netsurf, INFO, "%d fetches active", c->base.active); - - switch (script_type) { - case HTML_SCRIPT_SYNC: - ret = DOM_HUBBUB_HUBBUB_ERR | HUBBUB_PAUSED; - - case HTML_SCRIPT_ASYNC: - break; - - case HTML_SCRIPT_DEFER: - break; - - default: - assert(0); - } - } - - return ret; -} - -static dom_hubbub_error -exec_inline_script(html_content *c, dom_node *node, dom_string *mimetype) -{ - dom_string *script; - dom_exception exc; /* returned by libdom functions */ - struct lwc_string_s *lwcmimetype; - script_handler_t *script_handler; - struct html_script *nscript; - - /* does not appear to be a src so script is inline content */ - exc = dom_node_get_text_content(node, &script); - if ((exc != DOM_NO_ERR) || (script == NULL)) { - return DOM_HUBBUB_OK; /* no contents, skip */ - } - - nscript = html_process_new_script(c, mimetype, HTML_SCRIPT_INLINE); - if (nscript == NULL) { - dom_string_unref(script); - - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); - return DOM_HUBBUB_NOMEM; - - } - - nscript->data.string = script; - nscript->already_started = true; - - /* ensure script handler for content type */ - dom_string_intern(mimetype, &lwcmimetype); - script_handler = select_script_handler(content_factory_type_from_mime_type(lwcmimetype)); - lwc_string_unref(lwcmimetype); - - if (script_handler != NULL) { - script_handler(c->jscontext, - dom_string_data(script), - dom_string_byte_length(script)); - } - return DOM_HUBBUB_OK; -} - - -/** - * process script node parser callback - * - * - */ -dom_hubbub_error -html_process_script(void *ctx, dom_node *node) -{ - html_content *c = (html_content *)ctx; - dom_exception exc; /* returned by libdom functions */ - dom_string *src, *mimetype; - dom_hubbub_error err = DOM_HUBBUB_OK; - - /* ensure javascript context is available */ - /* We should only ever be here if scripting was enabled for this - * content so it's correct to make a javascript context if there - * isn't one already. */ - if (c->jscontext == NULL) { - union content_msg_data msg_data; - - msg_data.jscontext = &c->jscontext; - content_broadcast(&c->base, CONTENT_MSG_GETCTX, &msg_data); - NSLOG(netsurf, INFO, "javascript context %p ", c->jscontext); - if (c->jscontext == NULL) { - /* no context and it could not be created, abort */ - return DOM_HUBBUB_OK; - } - } - - NSLOG(netsurf, INFO, "content %p parser %p node %p", c, c->parser, - node); - - exc = dom_element_get_attribute(node, corestring_dom_type, &mimetype); - if (exc != DOM_NO_ERR || mimetype == NULL) { - mimetype = dom_string_ref(corestring_dom_text_javascript); - } - - exc = dom_element_get_attribute(node, corestring_dom_src, &src); - if (exc != DOM_NO_ERR || src == NULL) { - err = exec_inline_script(c, node, mimetype); - } else { - err = exec_src_script(c, node, mimetype, src); - dom_string_unref(src); - } - - dom_string_unref(mimetype); - - return err; -} - -/* exported internal interface documented in render/html_internal.h */ -nserror html_script_free(html_content *html) -{ - unsigned int i; - - for (i = 0; i != html->scripts_count; i++) { - if (html->scripts[i].mimetype != NULL) { - dom_string_unref(html->scripts[i].mimetype); - } - - if ((html->scripts[i].type == HTML_SCRIPT_INLINE) && - (html->scripts[i].data.string != NULL)) { - - dom_string_unref(html->scripts[i].data.string); - - } else if ((html->scripts[i].type == HTML_SCRIPT_SYNC) && - (html->scripts[i].data.handle != NULL)) { - - hlcache_handle_release(html->scripts[i].data.handle); - - } - } - free(html->scripts); - - return NSERROR_OK; -} - -/* exported internal interface documented in render/html_internal.h */ -nserror html_script_invalidate_ctx(html_content *htmlc) -{ - htmlc->jscontext = NULL; - return NSERROR_OK; -} diff --git a/render/imagemap.c b/render/imagemap.c deleted file mode 100644 index 0d3b42a1b..000000000 --- a/render/imagemap.c +++ /dev/null @@ -1,801 +0,0 @@ -/* - * Copyright 2004 John M Bell - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/* - * Much of this shamelessly copied from utils/messages.c - */ - -#include -#include -#include -#include - -#include - -#include "utils/log.h" -#include "utils/corestrings.h" -#include "content/content_protected.h" -#include "content/hlcache.h" - -#include "render/box.h" -#include "render/html_internal.h" -#include "render/imagemap.h" - -#define HASH_SIZE 31 /* fixed size hash table */ - -typedef enum { - IMAGEMAP_DEFAULT, - IMAGEMAP_RECT, - IMAGEMAP_CIRCLE, - IMAGEMAP_POLY -} imagemap_entry_type; - -struct mapentry { - imagemap_entry_type type; /**< type of shape */ - nsurl *url; /**< absolute url to go to */ - char *target; /**< target frame (if any) */ - union { - struct { - int x; /**< x coordinate of centre */ - int y; /**< y coordinate of center */ - int r; /**< radius of circle */ - } circle; - struct { - int x0; /**< left hand edge */ - int y0; /**< top edge */ - int x1; /**< right hand edge */ - int y1; /**< bottom edge */ - } rect; - struct { - int num; /**< number of points */ - float *xcoords; /**< x coordinates */ - float *ycoords; /**< y coordinates */ - } poly; - } bounds; - struct mapentry *next; /**< next entry in list */ -}; - -struct imagemap { - char *key; /**< key for this entry */ - struct mapentry *list; /**< pointer to linked list of entries */ - struct imagemap *next; /**< next entry in this hash chain */ -}; - -/** - * Create hashtable of imagemaps - * - * \param c The containing content - * \return true on success, false otherwise - */ -static bool imagemap_create(html_content *c) -{ - assert(c != NULL); - - if (c->imagemaps == NULL) { - c->imagemaps = calloc(HASH_SIZE, sizeof(struct imagemap *)); - if (c->imagemaps == NULL) { - return false; - } - } - - return true; -} - -/** - * Hash function. - * - * \param key The key to hash. - * \return The hashed value. - */ -static unsigned int imagemap_hash(const char *key) -{ - unsigned int z = 0; - - if (key == 0) return 0; - - for (; *key != 0; key++) { - z += *key & 0x1f; - } - - return (z % (HASH_SIZE - 1)) + 1; -} - -/** - * Add an imagemap to the hashtable, creating it if it doesn't exist - * - * \param c The containing content - * \param key The name of the imagemap - * \param list List of map regions - * \return true on succes, false otherwise - */ -static bool -imagemap_add(html_content *c, dom_string *key, struct mapentry *list) -{ - struct imagemap *map; - unsigned int slot; - - assert(c != NULL); - assert(key != NULL); - assert(list != NULL); - - if (imagemap_create(c) == false) - return false; - - map = calloc(1, sizeof(*map)); - if (map == NULL) - return false; - - /* \todo Stop relying on NULL termination of dom_string */ - map->key = strdup(dom_string_data(key)); - if (map->key == NULL) { - free(map); - return false; - } - - map->list = list; - - slot = imagemap_hash(map->key); - - map->next = c->imagemaps[slot]; - c->imagemaps[slot] = map; - - return true; -} - -/** - * Free list of imagemap entries - * - * \param list Pointer to head of list - */ -static void imagemap_freelist(struct mapentry *list) -{ - struct mapentry *entry, *prev; - - assert(list != NULL); - - entry = list; - - while (entry != NULL) { - prev = entry; - - nsurl_unref(entry->url); - - if (entry->target) - free(entry->target); - - if (entry->type == IMAGEMAP_POLY) { - free(entry->bounds.poly.xcoords); - free(entry->bounds.poly.ycoords); - } - - entry = entry->next; - free(prev); - } -} - -/** - * Destroy hashtable of imagemaps - * - * \param c The containing content - */ -void imagemap_destroy(html_content *c) -{ - unsigned int i; - - assert(c != NULL); - - /* no imagemaps -> return */ - if (c->imagemaps == NULL) - return; - - for (i = 0; i != HASH_SIZE; i++) { - struct imagemap *map, *next; - - map = c->imagemaps[i]; - while (map != NULL) { - next = map->next; - imagemap_freelist(map->list); - free(map->key); - free(map); - map = next; - } - } - - free(c->imagemaps); -} - -/** - * Dump imagemap data to the log - * - * \param c The containing content - */ -void imagemap_dump(html_content *c) -{ - unsigned int i; - - int j; - - assert(c != NULL); - - if (c->imagemaps == NULL) - return; - - for (i = 0; i != HASH_SIZE; i++) { - struct imagemap *map; - struct mapentry *entry; - - map = c->imagemaps[i]; - while (map != NULL) { - NSLOG(netsurf, INFO, "Imagemap: %s", map->key); - - for (entry = map->list; entry; entry = entry->next) { - switch (entry->type) { - case IMAGEMAP_DEFAULT: - NSLOG(netsurf, INFO, "\tDefault: %s", - nsurl_access(entry->url)); - break; - case IMAGEMAP_RECT: - NSLOG(netsurf, INFO, - "\tRectangle: %s: [(%d,%d),(%d,%d)]", - nsurl_access(entry->url), - entry->bounds.rect.x0, - entry->bounds.rect.y0, - entry->bounds.rect.x1, - entry->bounds.rect.y1); - break; - case IMAGEMAP_CIRCLE: - NSLOG(netsurf, INFO, - "\tCircle: %s: [(%d,%d),%d]", - nsurl_access(entry->url), - entry->bounds.circle.x, - entry->bounds.circle.y, - entry->bounds.circle.r); - break; - case IMAGEMAP_POLY: - NSLOG(netsurf, INFO, - "\tPolygon: %s:", - nsurl_access(entry->url)); - for (j = 0; j != entry->bounds.poly.num; - j++) { - fprintf(stderr, "(%d,%d) ", - (int)entry->bounds.poly.xcoords[j], - (int)entry->bounds.poly.ycoords[j]); - } - fprintf(stderr,"\n"); - break; - } - } - map = map->next; - } - } -} - -/** - * Adds an imagemap entry to the list - * - * \param c The html content that the imagemap belongs to - * \param n The xmlNode representing the entry to add - * \param base_url Base URL for resolving relative URLs - * \param entry Pointer to list of entries - * \param tagtype The type of tag - * \return false on memory exhaustion, true otherwise - */ -static bool -imagemap_addtolist(const struct html_content *c, dom_node *n, nsurl *base_url, - struct mapentry **entry, dom_string *tagtype) -{ - dom_exception exc; - dom_string *href = NULL, *target = NULL, *shape = NULL; - dom_string *coords = NULL; - struct mapentry *new_map, *temp; - bool ret = true; - - if (dom_string_caseless_isequal(tagtype, corestring_dom_area)) { - bool nohref = false; - exc = dom_element_has_attribute(n, - corestring_dom_nohref, &nohref); - if ((exc != DOM_NO_ERR) || nohref) - /* Skip */ - goto ok_out; - } - - exc = dom_element_get_attribute(n, corestring_dom_href, &href); - if (exc != DOM_NO_ERR || href == NULL) { - /* No href="" attribute, skip this element */ - goto ok_out; - } - - exc = dom_element_get_attribute(n, corestring_dom_target, &target); - if (exc != DOM_NO_ERR) { - goto ok_out; - } - - exc = dom_element_get_attribute(n, corestring_dom_shape, &shape); - if (exc != DOM_NO_ERR) { - goto ok_out; - } - - /* If there's no shape, we default to rectangles */ - if (shape == NULL) - shape = dom_string_ref(corestring_dom_rect); - - if (!dom_string_caseless_lwc_isequal(shape, corestring_lwc_default)) { - /* If not 'default' and there's no 'coords' give up */ - exc = dom_element_get_attribute(n, corestring_dom_coords, - &coords); - if (exc != DOM_NO_ERR || coords == NULL) { - goto ok_out; - } - } - - new_map = calloc(1, sizeof(*new_map)); - if (new_map == NULL) { - goto bad_out; - } - - if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_rect) || - dom_string_caseless_lwc_isequal(shape, corestring_lwc_rectangle)) - new_map->type = IMAGEMAP_RECT; - else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_circle)) - new_map->type = IMAGEMAP_CIRCLE; - else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_poly) || - dom_string_caseless_lwc_isequal(shape, corestring_lwc_polygon)) - new_map->type = IMAGEMAP_POLY; - else if (dom_string_caseless_lwc_isequal(shape, corestring_lwc_default)) - new_map->type = IMAGEMAP_DEFAULT; - else - goto bad_out; - - if (box_extract_link(c, href, base_url, &new_map->url) == false) - goto bad_out; - - if (new_map->url == NULL) { - /* non-fatal error -> ignore this */ - goto ok_free_map_out; - } - - if (target != NULL) { - /* Copy target into the map */ - new_map->target = malloc(dom_string_byte_length(target) + 1); - if (new_map->target == NULL) - goto bad_out; - /* Safe, but relies on dom_strings being NULL terminated */ - /* \todo Do this better */ - strcpy(new_map->target, dom_string_data(target)); - } - - if (new_map->type != IMAGEMAP_DEFAULT) { - int x, y; - float *xcoords, *ycoords; - /* coordinates are a comma-separated list of values */ - char *val = strtok((char *)dom_string_data(coords), ","); - int num = 1; - - switch (new_map->type) { - case IMAGEMAP_RECT: - /* (left, top, right, bottom) */ - while (val != NULL && num <= 4) { - switch (num) { - case 1: - new_map->bounds.rect.x0 = atoi(val); - break; - case 2: - new_map->bounds.rect.y0 = atoi(val); - break; - case 3: - new_map->bounds.rect.x1 = atoi(val); - break; - case 4: - new_map->bounds.rect.y1 = atoi(val); - break; - } - - num++; - val = strtok(NULL, ","); - } - break; - case IMAGEMAP_CIRCLE: - /* (x, y, radius ) */ - while (val != NULL && num <= 3) { - switch (num) { - case 1: - new_map->bounds.circle.x = atoi(val); - break; - case 2: - new_map->bounds.circle.y = atoi(val); - break; - case 3: - new_map->bounds.circle.r = atoi(val); - break; - } - - num++; - val = strtok(NULL, ","); - } - break; - case IMAGEMAP_POLY: - new_map->bounds.poly.xcoords = NULL; - new_map->bounds.poly.ycoords = NULL; - - while (val != NULL) { - x = atoi(val); - - val = strtok(NULL, ","); - if (val == NULL) - break; - - y = atoi(val); - - xcoords = realloc(new_map->bounds.poly.xcoords, - num * sizeof(float)); - if (xcoords == NULL) { - goto bad_out; - } - new_map->bounds.poly.xcoords = xcoords; - - ycoords = realloc(new_map->bounds.poly.ycoords, - num * sizeof(float)); - if (ycoords == NULL) { - goto bad_out; - } - new_map->bounds.poly.ycoords = ycoords; - - new_map->bounds.poly.xcoords[num - 1] = x; - new_map->bounds.poly.ycoords[num - 1] = y; - - num++; - val = strtok(NULL, ","); - } - - new_map->bounds.poly.num = num - 1; - - break; - default: - break; - } - } - - new_map->next = NULL; - - if (*entry) { - /* add to END of list */ - for (temp = (*entry); temp->next != NULL; temp = temp->next) - ; - temp->next = new_map; - } else { - (*entry) = new_map; - } - - /* All good, linked in, let's clean up */ - goto ok_out; - -bad_out: - ret = false; -ok_free_map_out: - if (new_map != NULL) { - if (new_map->url != NULL) - nsurl_unref(new_map->url); - if (new_map->type == IMAGEMAP_POLY && - new_map->bounds.poly.ycoords != NULL) - free(new_map->bounds.poly.ycoords); - if (new_map->type == IMAGEMAP_POLY && - new_map->bounds.poly.xcoords != NULL) - free(new_map->bounds.poly.xcoords); - if (new_map->target != NULL) - free(new_map->target); - - free(new_map); - } -ok_out: - if (href != NULL) - dom_string_unref(href); - if (target != NULL) - dom_string_unref(target); - if (shape != NULL) - dom_string_unref(shape); - if (coords != NULL) - dom_string_unref(coords); - - return ret; -} - -/** - * Extract an imagemap from html source - * - * \param node XML node containing map - * \param c Content containing document - * \param entry List of map entries - * \param tname The sub-tags to consider on this pass - * \return false on memory exhaustion, true otherwise - */ -static bool -imagemap_extract_map_entries(dom_node *node, html_content *c, - struct mapentry **entry, dom_string *tname) -{ - dom_nodelist *nlist; - dom_exception exc; - unsigned long ent; - uint32_t tag_count; - - exc = dom_element_get_elements_by_tag_name(node, tname, &nlist); - if (exc != DOM_NO_ERR) { - return false; - } - - exc = dom_nodelist_get_length(nlist, &tag_count); - if (exc != DOM_NO_ERR) { - dom_nodelist_unref(nlist); - return false; - } - - for (ent = 0; ent < tag_count; ++ent) { - dom_node *subnode; - - exc = dom_nodelist_item(nlist, ent, &subnode); - if (exc != DOM_NO_ERR) { - dom_nodelist_unref(nlist); - return false; - } - if (imagemap_addtolist(c, subnode, c->base_url, - entry, tname) == false) { - dom_node_unref(subnode); - dom_nodelist_unref(nlist); - return false; - } - dom_node_unref(subnode); - } - - dom_nodelist_unref(nlist); - - return true; -} - -/** - * Extract an imagemap from html source - * - * \param node XML node containing map - * \param c Content containing document - * \param entry List of map entries - * \return false on memory exhaustion, true otherwise - */ -static bool imagemap_extract_map(dom_node *node, html_content *c, - struct mapentry **entry) -{ - if (imagemap_extract_map_entries(node, c, entry, - corestring_dom_area) == false) - return false; - return imagemap_extract_map_entries(node, c, entry, - corestring_dom_a); -} - -/** - * Extract all imagemaps from a document tree - * - * \param c The content to extract imagemaps from. - * \return false on memory exhaustion, true otherwise - */ -nserror -imagemap_extract(html_content *c) -{ - dom_nodelist *nlist; - dom_exception exc; - unsigned long mapnr; - uint32_t maybe_maps; - nserror ret = NSERROR_OK; - - exc = dom_document_get_elements_by_tag_name(c->document, - corestring_dom_map, - &nlist); - if (exc != DOM_NO_ERR) { - return NSERROR_DOM; - } - - exc = dom_nodelist_get_length(nlist, &maybe_maps); - if (exc != DOM_NO_ERR) { - ret = NSERROR_DOM; - goto out_nlist; - } - - for (mapnr = 0; mapnr < maybe_maps; ++mapnr) { - dom_node *node; - dom_string *name; - exc = dom_nodelist_item(nlist, mapnr, &node); - if (exc != DOM_NO_ERR) { - ret = NSERROR_DOM; - goto out_nlist; - } - - exc = dom_element_get_attribute(node, corestring_dom_id, - &name); - if (exc != DOM_NO_ERR) { - dom_node_unref(node); - ret = NSERROR_DOM; - goto out_nlist; - } - - if (name == NULL) { - exc = dom_element_get_attribute(node, - corestring_dom_name, - &name); - if (exc != DOM_NO_ERR) { - dom_node_unref(node); - ret = NSERROR_DOM; - goto out_nlist; - } - } - - if (name != NULL) { - struct mapentry *entry = NULL; - if (imagemap_extract_map(node, c, &entry) == false) { - if (entry != NULL) { - imagemap_freelist(entry); - } - - dom_string_unref(name); - dom_node_unref(node); - ret = NSERROR_NOMEM; /** @todo check this */ - goto out_nlist; - } - - /* imagemap_extract_map may not extract anything, - * so entry can still be NULL here. This isn't an - * error as it just means that we've encountered - * an incorrectly defined ... block - */ - if ((entry != NULL) && - (imagemap_add(c, name, entry) == false)) { - imagemap_freelist(entry); - - dom_string_unref(name); - dom_node_unref(node); - ret = NSERROR_NOMEM; /** @todo check this */ - goto out_nlist; - } - } - - dom_string_unref(name); - dom_node_unref(node); - } - -out_nlist: - - dom_nodelist_unref(nlist); - - return ret; -} - -/** - * Test if a point lies within an arbitrary polygon - * Modified from comp.graphics.algorithms FAQ 2.03 - * - * \param num Number of vertices - * \param xpt Array of x coordinates - * \param ypt Array of y coordinates - * \param x Left hand edge of containing box - * \param y Top edge of containing box - * \param click_x X coordinate of click - * \param click_y Y coordinate of click - * \return 1 if point is in polygon, 0 if outside. 0 or 1 if on boundary - */ -static int -imagemap_point_in_poly(int num, float *xpt, float *ypt, unsigned long x, - unsigned long y, unsigned long click_x, unsigned long click_y) -{ - int i, j, c = 0; - - assert(xpt != NULL); - assert(ypt != NULL); - - for (i = 0, j = num - 1; i < num; j = i++) { - if ((((ypt[i] + y <= click_y) && (click_y < ypt[j] + y)) || - ((ypt[j] + y <= click_y) && (click_y < ypt[i] + y))) && - (click_x < (xpt[j] - xpt[i]) * - (click_y - (ypt[i] + y)) / (ypt[j] - ypt[i]) + xpt[i] + x)) - c = !c; - } - - return c; -} - -/** - * Retrieve url associated with imagemap entry - * - * \param c The containing content - * \param key The map name to search for - * \param x The left edge of the containing box - * \param y The top edge of the containing box - * \param click_x The horizontal location of the click - * \param click_y The vertical location of the click - * \param target Pointer to location to receive target pointer (if any) - * \return The url associated with this area, or NULL if not found - */ -nsurl *imagemap_get(struct html_content *c, const char *key, - unsigned long x, unsigned long y, - unsigned long click_x, unsigned long click_y, - const char **target) -{ - unsigned int slot = 0; - struct imagemap *map; - struct mapentry *entry; - unsigned long cx, cy; - - assert(c != NULL); - - if (key == NULL) - return NULL; - - if (c->imagemaps == NULL) - return NULL; - - slot = imagemap_hash(key); - - for (map = c->imagemaps[slot]; map != NULL; map = map->next) { - if (map->key != NULL && strcasecmp(map->key, key) == 0) - break; - } - - if (map == NULL || map->list == NULL) - return NULL; - - for (entry = map->list; entry; entry = entry->next) { - switch (entry->type) { - case IMAGEMAP_DEFAULT: - /* just return the URL. no checks required */ - if (target) - *target = entry->target; - return entry->url; - break; - case IMAGEMAP_RECT: - if (click_x >= x + entry->bounds.rect.x0 && - click_x <= x + entry->bounds.rect.x1 && - click_y >= y + entry->bounds.rect.y0 && - click_y <= y + entry->bounds.rect.y1) { - if (target) - *target = entry->target; - return entry->url; - } - break; - case IMAGEMAP_CIRCLE: - cx = x + entry->bounds.circle.x - click_x; - cy = y + entry->bounds.circle.y - click_y; - if ((cx * cx + cy * cy) <= - (unsigned long) (entry->bounds.circle.r * - entry->bounds.circle.r)) { - if (target) - *target = entry->target; - return entry->url; - } - break; - case IMAGEMAP_POLY: - if (imagemap_point_in_poly(entry->bounds.poly.num, - entry->bounds.poly.xcoords, - entry->bounds.poly.ycoords, x, y, - click_x, click_y)) { - if (target) - *target = entry->target; - return entry->url; - } - break; - } - } - - if (target) - *target = NULL; - - return NULL; -} diff --git a/render/imagemap.h b/render/imagemap.h deleted file mode 100644 index 3ae6819da..000000000 --- a/render/imagemap.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2004 John M Bell - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef _NETSURF_RENDER_IMAGEMAP_H_ -#define _NETSURF_RENDER_IMAGEMAP_H_ - -#include - -struct html_content; -struct hlcache_handle; -struct nsurl; - -void imagemap_destroy(struct html_content *c); -void imagemap_dump(struct html_content *c); -nserror imagemap_extract(struct html_content *c); - -struct nsurl *imagemap_get(struct html_content *c, const char *key, - unsigned long x, unsigned long y, - unsigned long click_x, unsigned long click_y, - const char **target); - -#endif diff --git a/render/layout.c b/render/layout.c deleted file mode 100644 index 121137adc..000000000 --- a/render/layout.c +++ /dev/null @@ -1,5432 +0,0 @@ -/* - * Copyright 2005 Richard Wilson - * Copyright 2006 James Bursa - * Copyright 2008 Michael Drake - * Copyright 2003 Phil Mellor - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * HTML layout implementation. - * - * Layout is carried out in two stages: - * - * 1. + calculation of minimum / maximum box widths, and - * + determination of whether block level boxes will have >zero height - * - * 2. + layout (position and dimensions) - * - * In most cases the functions for the two stages are a corresponding pair - * layout_minmax_X() and layout_X(). - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "utils/log.h" -#include "utils/talloc.h" -#include "utils/utils.h" -#include "utils/nsoption.h" -#include "netsurf/inttypes.h" -#include "netsurf/content.h" -#include "netsurf/browser_window.h" -#include "netsurf/layout.h" -#include "content/content_protected.h" -#include "css/utils.h" -#include "desktop/scrollbar.h" -#include "desktop/textarea.h" - -#include "render/box.h" -#include "render/font.h" -#include "render/form_internal.h" -#include "render/html_internal.h" -#include "render/layout.h" -#include "render/table.h" - -#define AUTO INT_MIN - -/* Fixed point percentage (a) of an integer (b), to an integer */ -#define FPCT_OF_INT_TOINT(a, b) (FIXTOINT(FDIV((a * b), F_100))) - -typedef uint8_t (*css_len_func)( - const css_computed_style *style, - css_fixed *length, css_unit *unit); -typedef uint8_t (*css_border_style_func)( - const css_computed_style *style); -typedef uint8_t (*css_border_color_func)( - const css_computed_style *style, - css_color *color); - -/** Array of per-side access functions for computed style margins. */ -static const css_len_func margin_funcs[4] = { - [TOP] = css_computed_margin_top, - [RIGHT] = css_computed_margin_right, - [BOTTOM] = css_computed_margin_bottom, - [LEFT] = css_computed_margin_left, -}; - -/** Array of per-side access functions for computed style paddings. */ -static const css_len_func padding_funcs[4] = { - [TOP] = css_computed_padding_top, - [RIGHT] = css_computed_padding_right, - [BOTTOM] = css_computed_padding_bottom, - [LEFT] = css_computed_padding_left, -}; - -/** Array of per-side access functions for computed style border_widths. */ -static const css_len_func border_width_funcs[4] = { - [TOP] = css_computed_border_top_width, - [RIGHT] = css_computed_border_right_width, - [BOTTOM] = css_computed_border_bottom_width, - [LEFT] = css_computed_border_left_width, -}; - -/** Array of per-side access functions for computed style border styles. */ -static const css_border_style_func border_style_funcs[4] = { - [TOP] = css_computed_border_top_style, - [RIGHT] = css_computed_border_right_style, - [BOTTOM] = css_computed_border_bottom_style, - [LEFT] = css_computed_border_left_style, -}; - -/** Array of per-side access functions for computed style border colors. */ -static const css_border_color_func border_color_funcs[4] = { - [TOP] = css_computed_border_top_color, - [RIGHT] = css_computed_border_right_color, - [BOTTOM] = css_computed_border_bottom_color, - [LEFT] = css_computed_border_left_color, -}; - -/* forward declaration to break cycles */ -static bool layout_block_context( - struct box *block, - int viewport_height, - html_content *content); -static void layout_minmax_block( - struct box *block, - const struct gui_layout_table *font_func, - const html_content *content); - - -/** - * Compute the size of replaced boxes with auto dimensions, according to - * content. - * - * \param box Box with object - * \param width Width value in px or AUTO. If AUTO, updated to value in px. - * \param height Height value in px or AUTO. If AUTO, updated to value in px. - * \param min_width Box's min width, as given by layout_find_dimensions. - * \param max_width Box's max width, as given by layout_find_dimensions. - * \param min_height Box's min height, as given by layout_find_dimensions. - * \param max_height Box's max height, as given by layout_find_dimensions. - * - * See CSS 2.1 sections 10.3 and 10.6. - */ -static void -layout_get_object_dimensions(struct box *box, - int *width, int *height, - int min_width, int max_width, - int min_height, int max_height) -{ - assert(box->object != NULL); - assert(width != NULL && height != NULL); - - if (*width == AUTO && *height == AUTO) { - /* No given dimensions */ - - bool scaled = false; - int intrinsic_width = content_get_width(box->object); - int intrinsic_height = content_get_height(box->object); - - /* use intrinsic dimensions */ - *width = intrinsic_width; - *height = intrinsic_height; - - /* Deal with min/max-width first */ - if (min_width > 0 && min_width > *width) { - *width = min_width; - scaled = true; - } - if (max_width >= 0 && max_width < *width) { - *width = max_width; - scaled = true; - } - - if (scaled && (intrinsic_width != 0)) { - /* Update height */ - *height = (*width * intrinsic_height) / - intrinsic_width; - } - - scaled = false; - /* Deal with min/max-height */ - if (min_height > 0 && min_height > *height) { - *height = min_height; - scaled = true; - } - if (max_height >= 0 && max_height < *height) { - *height = max_height; - scaled = true; - } - - if (scaled && (intrinsic_height != 0)) { - /* Update width */ - *width = (*height * intrinsic_width) / - intrinsic_height; - } - - } else if (*width == AUTO) { - /* Have given height; width is calculated from the given height - * and ratio of intrinsic dimensions */ - int intrinsic_width = content_get_width(box->object); - int intrinsic_height = content_get_height(box->object); - - if (intrinsic_height != 0) - *width = (*height * intrinsic_width) / - intrinsic_height; - else - *width = intrinsic_width; - - if (min_width > 0 && min_width > *width) - *width = min_width; - if (max_width >= 0 && max_width < *width) - *width = max_width; - - } else if (*height == AUTO) { - /* Have given width; height is calculated from the given width - * and ratio of intrinsic dimensions */ - int intrinsic_width = content_get_width(box->object); - int intrinsic_height = content_get_height(box->object); - - if (intrinsic_width != 0) - *height = (*width * intrinsic_height) / - intrinsic_width; - else - *height = intrinsic_height; - } -} - - -/** - * Calculate the text-indent length. - * - * \param style style of block - * \param width width of containing block - * \return length of indent - */ -static int layout_text_indent( - const nscss_len_ctx *len_ctx, - const css_computed_style *style, int width) -{ - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - css_computed_text_indent(style, &value, &unit); - - if (unit == CSS_UNIT_PCT) { - return FPCT_OF_INT_TOINT(value, width); - } else { - return FIXTOINT(nscss_len2px(len_ctx, value, unit, style)); - } -} - - -/** - * Determine width of margin, borders, and padding on one side of a box. - * - * \param len_ctx CSS length conversion context for document - * \param style style to measure - * \param side side of box to measure - * \param margin whether margin width is required - * \param border whether border width is required - * \param padding whether padding width is required - * \param fixed increased by sum of fixed margin, border, and padding - * \param frac increased by sum of fractional margin and padding - */ -static void -calculate_mbp_width(const nscss_len_ctx *len_ctx, - const css_computed_style *style, - unsigned int side, - bool margin, - bool border, - bool padding, - int *fixed, - float *frac) -{ - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - assert(style); - - /* margin */ - if (margin) { - enum css_margin_e type; - - type = margin_funcs[side](style, &value, &unit); - if (type == CSS_MARGIN_SET) { - if (unit == CSS_UNIT_PCT) { - *frac += FIXTOINT(FDIV(value, F_100)); - } else { - *fixed += FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } - } - - /* border */ - if (border) { - if (border_style_funcs[side](style) != - CSS_BORDER_STYLE_NONE) { - border_width_funcs[side](style, &value, &unit); - - *fixed += FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } - - /* padding */ - if (padding) { - padding_funcs[side](style, &value, &unit); - if (unit == CSS_UNIT_PCT) { - *frac += FIXTOINT(FDIV(value, F_100)); - } else { - *fixed += FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } -} - - -/** - * Calculate minimum and maximum width of a table. - * - * \param table box of type TABLE - * \param font_func Font functions - * \param content The HTML content we are laying out. - * \post table->min_width and table->max_width filled in, - * 0 <= table->min_width <= table->max_width - */ -static void layout_minmax_table(struct box *table, - const struct gui_layout_table *font_func, - const html_content *content) -{ - unsigned int i, j; - int border_spacing_h = 0; - int table_min = 0, table_max = 0; - int extra_fixed = 0; - float extra_frac = 0; - struct column *col; - struct box *row_group, *row, *cell; - enum css_width_e wtype; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - /* check if the widths have already been calculated */ - if (table->max_width != UNKNOWN_MAX_WIDTH) - return; - - if (table_calculate_column_types(&content->len_ctx, table) == false) { - NSLOG(netsurf, WARNING, - "Could not establish table column types."); - return; - } - col = table->col; - - /* start with 0 except for fixed-width columns */ - for (i = 0; i != table->columns; i++) { - if (col[i].type == COLUMN_WIDTH_FIXED) - col[i].min = col[i].max = col[i].width; - else - col[i].min = col[i].max = 0; - } - - /* border-spacing is used in the separated borders model */ - if (css_computed_border_collapse(table->style) == - CSS_BORDER_COLLAPSE_SEPARATE) { - css_fixed h = 0, v = 0; - css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; - - css_computed_border_spacing(table->style, &h, &hu, &v, &vu); - - border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx, - h, hu, table->style)); - } - - /* 1st pass: consider cells with colspan 1 only */ - for (row_group = table->children; row_group; row_group =row_group->next) - for (row = row_group->children; row; row = row->next) - for (cell = row->children; cell; cell = cell->next) { - assert(cell->type == BOX_TABLE_CELL); - assert(cell->style); - /** TODO: Handle colspan="0" correctly. - * It's currently converted to 1 in box normaisation */ - assert(cell->columns != 0); - - if (cell->columns != 1) - continue; - - layout_minmax_block(cell, font_func, content); - i = cell->start_column; - - if (col[i].positioned) - continue; - - /* update column min, max widths using cell widths */ - if (col[i].min < cell->min_width) - col[i].min = cell->min_width; - if (col[i].max < cell->max_width) - col[i].max = cell->max_width; - } - - /* 2nd pass: cells which span multiple columns */ - for (row_group = table->children; row_group; row_group =row_group->next) - for (row = row_group->children; row; row = row->next) - for (cell = row->children; cell; cell = cell->next) { - unsigned int flexible_columns = 0; - int min = 0, max = 0, fixed_width = 0, extra; - - if (cell->columns == 1) - continue; - - layout_minmax_block(cell, font_func, content); - i = cell->start_column; - - /* find min width so far of spanned columns, and count - * number of non-fixed spanned columns and total fixed width */ - for (j = 0; j != cell->columns; j++) { - min += col[i + j].min; - if (col[i + j].type == COLUMN_WIDTH_FIXED) - fixed_width += col[i + j].width; - else - flexible_columns++; - } - min += (cell->columns - 1) * border_spacing_h; - - /* distribute extra min to spanned columns */ - if (min < cell->min_width) { - if (flexible_columns == 0) { - extra = 1 + (cell->min_width - min) / - cell->columns; - for (j = 0; j != cell->columns; j++) { - col[i + j].min += extra; - if (col[i + j].max < col[i + j].min) - col[i + j].max = col[i + j].min; - } - } else { - extra = 1 + (cell->min_width - min) / - flexible_columns; - for (j = 0; j != cell->columns; j++) { - if (col[i + j].type != - COLUMN_WIDTH_FIXED) { - col[i + j].min += extra; - if (col[i + j].max < - col[i + j].min) - col[i + j].max = - col[i + j].min; - } - } - } - } - - /* find max width so far of spanned columns */ - for (j = 0; j != cell->columns; j++) - max += col[i + j].max; - max += (cell->columns - 1) * border_spacing_h; - - /* distribute extra max to spanned columns */ - if (max < cell->max_width && flexible_columns) { - extra = 1 + (cell->max_width - max) / flexible_columns; - for (j = 0; j != cell->columns; j++) - if (col[i + j].type != COLUMN_WIDTH_FIXED) - col[i + j].max += extra; - } - } - - for (i = 0; i != table->columns; i++) { - if (col[i].max < col[i].min) { - box_dump(stderr, table, 0, true); - assert(0); - } - table_min += col[i].min; - table_max += col[i].max; - } - - /* fixed width takes priority, unless it is too narrow */ - wtype = css_computed_width(table->style, &value, &unit); - if (wtype == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) { - int width = FIXTOINT(nscss_len2px(&content->len_ctx, - value, unit, table->style)); - if (table_min < width) - table_min = width; - if (table_max < width) - table_max = width; - } - - /* add margins, border, padding to min, max widths */ - calculate_mbp_width(&content->len_ctx, - table->style, LEFT, true, true, true, - &extra_fixed, &extra_frac); - calculate_mbp_width(&content->len_ctx, - table->style, RIGHT, true, true, true, - &extra_fixed, &extra_frac); - if (extra_fixed < 0) - extra_fixed = 0; - if (extra_frac < 0) - extra_frac = 0; - if (1.0 <= extra_frac) - extra_frac = 0.9; - table->min_width = (table_min + extra_fixed) / (1.0 - extra_frac); - table->max_width = (table_max + extra_fixed) / (1.0 - extra_frac); - table->min_width += (table->columns + 1) * border_spacing_h; - table->max_width += (table->columns + 1) * border_spacing_h; - - assert(0 <= table->min_width && table->min_width <= table->max_width); -} - -/** - * Helper to check if a box has percentage max width. - * - * \param[in] b Box to check. - * \return true iff box has percnetage max width. - */ -static inline bool box_has_percentage_max_width(struct box *b) -{ - css_unit unit = CSS_UNIT_PX; - enum css_max_width_e type; - css_fixed value = 0; - - assert(b != NULL); - - type = css_computed_max_width(b->style, &value, &unit); - return ((type == CSS_MAX_WIDTH_SET) && (unit == CSS_UNIT_PCT)); -} - -/** - * Calculate minimum and maximum width of a line. - * - * \param first a box in an inline container - * \param line_min updated to minimum width of line starting at first - * \param line_max updated to maximum width of line starting at first - * \param first_line true iff this is the first line in the inline container - * \param line_has_height updated to true or false, depending on line - * \param font_func Font functions. - * \return first box in next line, or 0 if no more lines - * \post 0 <= *line_min <= *line_max - */ -static struct box * -layout_minmax_line(struct box *first, - int *line_min, - int *line_max, - bool first_line, - bool *line_has_height, - const struct gui_layout_table *font_func, - const html_content *content) -{ - int min = 0, max = 0, width, height, fixed; - float frac; - size_t i, j; - struct box *b; - struct box *block; - plot_font_style_t fstyle; - bool no_wrap; - - assert(first->parent); - assert(first->parent->parent); - assert(first->parent->parent->style); - - block = first->parent->parent; - no_wrap = (css_computed_white_space(block->style) == - CSS_WHITE_SPACE_NOWRAP || - css_computed_white_space(block->style) == - CSS_WHITE_SPACE_PRE); - - *line_has_height = false; - - /* corresponds to the pass 1 loop in layout_line() */ - for (b = first; b; b = b->next) { - enum css_width_e wtype; - enum css_height_e htype; - enum css_box_sizing_e bs; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - assert(b->type == BOX_INLINE || b->type == BOX_INLINE_BLOCK || - b->type == BOX_FLOAT_LEFT || - b->type == BOX_FLOAT_RIGHT || - b->type == BOX_BR || b->type == BOX_TEXT || - b->type == BOX_INLINE_END); - - NSLOG(layout, DEBUG, "%p: min %i, max %i", b, min, max); - - if (b->type == BOX_BR) { - b = b->next; - break; - } - - if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT) { - assert(b->children); - if (b->children->type == BOX_BLOCK) - layout_minmax_block(b->children, font_func, - content); - else - layout_minmax_table(b->children, font_func, - content); - b->min_width = b->children->min_width; - b->max_width = b->children->max_width; - if (min < b->min_width) - min = b->min_width; - max += b->max_width; - continue; - } - - if (b->type == BOX_INLINE_BLOCK) { - layout_minmax_block(b, font_func, content); - if (min < b->min_width) - min = b->min_width; - max += b->max_width; - - if (b->flags & HAS_HEIGHT) - *line_has_height = true; - continue; - } - - assert(b->style); - font_plot_style_from_css(&content->len_ctx, b->style, &fstyle); - - if (b->type == BOX_INLINE && !b->object && - !(b->flags & REPLACE_DIM) && - !(b->flags & IFRAME)) { - fixed = frac = 0; - calculate_mbp_width(&content->len_ctx, - b->style, LEFT, true, true, true, - &fixed, &frac); - if (!b->inline_end) - calculate_mbp_width(&content->len_ctx, - b->style, RIGHT, - true, true, true, - &fixed, &frac); - if (0 < fixed) - max += fixed; - *line_has_height = true; - /* \todo update min width, consider fractional extra */ - } else if (b->type == BOX_INLINE_END) { - fixed = frac = 0; - calculate_mbp_width(&content->len_ctx, - b->inline_end->style, RIGHT, - true, true, true, - &fixed, &frac); - if (0 < fixed) - max += fixed; - - if (b->next) { - if (b->space == UNKNOWN_WIDTH) { - font_func->width(&fstyle, " ", 1, - &b->space); - } - max += b->space; - } - - *line_has_height = true; - continue; - } - - if (!b->object && !(b->flags & IFRAME) && !b->gadget && - !(b->flags & REPLACE_DIM)) { - /* inline non-replaced, 10.3.1 and 10.6.1 */ - bool no_wrap_box; - if (!b->text) - continue; - - no_wrap_box = (css_computed_white_space(b->style) == - CSS_WHITE_SPACE_NOWRAP || - css_computed_white_space(b->style) == - CSS_WHITE_SPACE_PRE); - - if (b->width == UNKNOWN_WIDTH) { - /** \todo handle errors */ - - /* If it's a select element, we must use the - * width of the widest option text */ - if (b->parent->parent->gadget && - b->parent->parent->gadget->type - == GADGET_SELECT) { - int opt_maxwidth = 0; - struct form_option *o; - - for (o = b->parent->parent->gadget-> - data.select.items; o; - o = o->next) { - int opt_width; - font_func->width(&fstyle, - o->text, - strlen(o->text), - &opt_width); - - if (opt_maxwidth < opt_width) - opt_maxwidth =opt_width; - } - - b->width = opt_maxwidth; - if (nsoption_bool(core_select_menu)) - b->width += SCROLLBAR_WIDTH; - - } else { - font_func->width(&fstyle, b->text, - b->length, &b->width); - b->flags |= MEASURED; - } - } - max += b->width; - if (b->next) { - if (b->space == UNKNOWN_WIDTH) { - font_func->width(&fstyle, " ", 1, - &b->space); - } - max += b->space; - } - - if (no_wrap) { - /* Don't wrap due to block style, - * so min is the same as max */ - min = max; - - } else if (no_wrap_box) { - /* This inline box can't be wrapped, - * for min, consider box's width */ - if (min < b->width) - min = b->width; - - } else if (b->parent->flags & NEED_MIN) { - /* If we care what the minimum width is, - * calculate it. (It's only needed if we're - * shrinking-to-fit.) */ - /* min = widest single word */ - i = 0; - do { - for (j = i; j != b->length && - b->text[j] != ' '; j++) - ; - font_func->width(&fstyle, b->text + i, - j - i, &width); - if (min < width) - min = width; - i = j + 1; - } while (j != b->length); - } - - *line_has_height = true; - - continue; - } - - /* inline replaced, 10.3.2 and 10.6.2 */ - assert(b->style); - - /* calculate box width */ - wtype = css_computed_width(b->style, &value, &unit); - bs = css_computed_box_sizing(block->style); - if (wtype == CSS_WIDTH_SET) { - if (unit == CSS_UNIT_PCT) { - width = AUTO; - } else { - width = FIXTOINT(nscss_len2px(&content->len_ctx, - value, unit, b->style)); - - if (bs == CSS_BOX_SIZING_BORDER_BOX) { - fixed = frac = 0; - calculate_mbp_width(&content->len_ctx, - block->style, LEFT, - false, true, true, - &fixed, &frac); - calculate_mbp_width(&content->len_ctx, - block->style, RIGHT, - false, true, true, - &fixed, &frac); - if (width < fixed) { - width = fixed; - } - } - if (width < 0) - width = 0; - } - } else { - width = AUTO; - } - - /* height */ - htype = css_computed_height(b->style, &value, &unit); - if (htype == CSS_HEIGHT_SET) { - height = FIXTOINT(nscss_len2px(&content->len_ctx, - value, unit, b->style)); - } else { - height = AUTO; - } - - if (b->object || (b->flags & REPLACE_DIM)) { - if (b->object) { - int temp_height = height; - layout_get_object_dimensions(b, - &width, &temp_height, - INT_MIN, INT_MAX, - INT_MIN, INT_MAX); - } - - fixed = frac = 0; - if (bs == CSS_BOX_SIZING_BORDER_BOX) { - calculate_mbp_width(&content->len_ctx, - b->style, LEFT, - true, false, false, - &fixed, &frac); - calculate_mbp_width(&content->len_ctx, - b->style, RIGHT, - true, false, false, - &fixed, &frac); - } else { - calculate_mbp_width(&content->len_ctx, - b->style, LEFT, - true, true, true, - &fixed, &frac); - calculate_mbp_width(&content->len_ctx, - b->style, RIGHT, - true, true, true, - &fixed, &frac); - } - if (0 < width + fixed) - width += fixed; - } else if (b->flags & IFRAME) { - /* TODO: handle percentage widths properly */ - if (width == AUTO) - width = 400; - - fixed = frac = 0; - if (bs == CSS_BOX_SIZING_BORDER_BOX) { - calculate_mbp_width(&content->len_ctx, - b->style, LEFT, - true, false, false, - &fixed, &frac); - calculate_mbp_width(&content->len_ctx, - b->style, RIGHT, - true, false, false, - &fixed, &frac); - } else { - calculate_mbp_width(&content->len_ctx, - b->style, LEFT, - true, true, true, - &fixed, &frac); - calculate_mbp_width(&content->len_ctx, - b->style, RIGHT, - true, true, true, - &fixed, &frac); - } - - if (0 < width + fixed) - width += fixed; - - } else { - /* form control with no object */ - if (width == AUTO) - width = FIXTOINT(nscss_len2px( - &content->len_ctx, - INTTOFIX(1), CSS_UNIT_EM, - b->style)); - } - - if (min < width && !box_has_percentage_max_width(b)) - min = width; - if (width > 0) - max += width; - - *line_has_height = true; - } - - if (first_line) { - /* todo: handle percentage values properly */ - /* todo: handle text-indent interaction with floats */ - int text_indent = layout_text_indent(&content->len_ctx, - first->parent->parent->style, 100); - min = (min + text_indent < 0) ? 0 : min + text_indent; - max = (max + text_indent < 0) ? 0 : max + text_indent; - } - - *line_min = min; - *line_max = max; - - NSLOG(layout, DEBUG, "line_min %i, line_max %i", min, max); - - assert(b != first); - assert(0 <= *line_min); - assert(*line_min <= *line_max); - return b; -} - - -/** - * Calculate minimum and maximum width of an inline container. - * - * \param inline_container box of type INLINE_CONTAINER - * \param[out] has_height set to true if container has height - * \param font_func Font functions. - * \post inline_container->min_width and inline_container->max_width filled in, - * 0 <= inline_container->min_width <= inline_container->max_width - */ -static void -layout_minmax_inline_container(struct box *inline_container, - bool *has_height, - const struct gui_layout_table *font_func, - const html_content *content) -{ - struct box *child; - int line_min = 0, line_max = 0; - int min = 0, max = 0; - bool first_line = true; - bool line_has_height; - - assert(inline_container->type == BOX_INLINE_CONTAINER); - - /* check if the widths have already been calculated */ - if (inline_container->max_width != UNKNOWN_MAX_WIDTH) - return; - - *has_height = false; - - for (child = inline_container->children; child; ) { - child = layout_minmax_line(child, &line_min, &line_max, - first_line, &line_has_height, font_func, - content); - if (min < line_min) - min = line_min; - if (max < line_max) - max = line_max; - first_line = false; - *has_height |= line_has_height; - } - - inline_container->min_width = min; - inline_container->max_width = max; - - assert(0 <= inline_container->min_width && - inline_container->min_width <= - inline_container->max_width); -} - - -/** - * Calculate minimum and maximum width of a block. - * - * \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL - * \param font_func font functions - * \param content The HTML content being layed out. - * \post block->min_width and block->max_width filled in, - * 0 <= block->min_width <= block->max_width - */ -static void layout_minmax_block( - struct box *block, - const struct gui_layout_table *font_func, - const html_content *content) -{ - struct box *child; - int min = 0, max = 0; - int extra_fixed = 0; - float extra_frac = 0; - enum css_width_e wtype = CSS_WIDTH_AUTO; - css_fixed width = 0; - css_unit wunit = CSS_UNIT_PX; - enum css_height_e htype = CSS_HEIGHT_AUTO; - css_fixed height = 0; - css_unit hunit = CSS_UNIT_PX; - enum css_box_sizing_e bs = CSS_BOX_SIZING_CONTENT_BOX; - bool child_has_height = false; - - assert(block->type == BOX_BLOCK || - block->type == BOX_INLINE_BLOCK || - block->type == BOX_TABLE_CELL); - - /* check if the widths have already been calculated */ - if (block->max_width != UNKNOWN_MAX_WIDTH) - return; - - if (block->style != NULL) { - wtype = css_computed_width(block->style, &width, &wunit); - htype = css_computed_height(block->style, &height, &hunit); - bs = css_computed_box_sizing(block->style); - } - - /* set whether the minimum width is of any interest for this box */ - if (((block->parent && (block->parent->type == BOX_FLOAT_LEFT || - block->parent->type == BOX_FLOAT_RIGHT)) || - block->type == BOX_INLINE_BLOCK) && - wtype != CSS_WIDTH_SET) { - /* box shrinks to fit; need minimum width */ - block->flags |= NEED_MIN; - } else if (block->type == BOX_TABLE_CELL) { - /* box shrinks to fit; need minimum width */ - block->flags |= NEED_MIN; - } else if (block->parent && (block->parent->flags & NEED_MIN) && - wtype != CSS_WIDTH_SET) { - /* box inside shrink-to-fit context; need minimum width */ - block->flags |= NEED_MIN; - } - - if (block->gadget && (block->gadget->type == GADGET_TEXTBOX || - block->gadget->type == GADGET_PASSWORD || - block->gadget->type == GADGET_FILE || - block->gadget->type == GADGET_TEXTAREA) && - block->style && wtype == CSS_WIDTH_AUTO) { - css_fixed size = INTTOFIX(10); - css_unit unit = CSS_UNIT_EM; - - min = max = FIXTOINT(nscss_len2px(&content->len_ctx, - size, unit, block->style)); - - block->flags |= HAS_HEIGHT; - } - - if (block->gadget && (block->gadget->type == GADGET_RADIO || - block->gadget->type == GADGET_CHECKBOX) && - block->style && wtype == CSS_WIDTH_AUTO) { - css_fixed size = INTTOFIX(1); - css_unit unit = CSS_UNIT_EM; - - /* form checkbox or radio button - * if width is AUTO, set it to 1em */ - min = max = FIXTOINT(nscss_len2px(&content->len_ctx, - size, unit, block->style)); - - block->flags |= HAS_HEIGHT; - } - - if (block->object) { - if (content_get_type(block->object) == CONTENT_HTML) { - layout_minmax_block(html_get_box_tree(block->object), - font_func, content); - min = html_get_box_tree(block->object)->min_width; - max = html_get_box_tree(block->object)->max_width; - } else { - min = max = content_get_width(block->object); - } - - block->flags |= HAS_HEIGHT; - } else if (block->flags & IFRAME) { - /** \todo do we need to know the min/max width of the iframe's - * content? */ - block->flags |= HAS_HEIGHT; - } else { - /* recurse through children */ - for (child = block->children; child; child = child->next) { - switch (child->type) { - case BOX_BLOCK: - layout_minmax_block(child, font_func, - content); - if (child->flags & HAS_HEIGHT) - child_has_height = true; - break; - case BOX_INLINE_CONTAINER: - if (block->flags & NEED_MIN) - child->flags |= NEED_MIN; - - layout_minmax_inline_container(child, - &child_has_height, font_func, - content); - if (child_has_height && - child == - child->parent->children) { - block->flags |= MAKE_HEIGHT; - } - break; - case BOX_TABLE: - layout_minmax_table(child, font_func, - content); - /* todo: fix for zero height tables */ - child_has_height = true; - child->flags |= MAKE_HEIGHT; - break; - default: - assert(0); - } - assert(child->max_width != UNKNOWN_MAX_WIDTH); - - if (child->style && - (css_computed_position(child->style) == - CSS_POSITION_ABSOLUTE || - css_computed_position(child->style) == - CSS_POSITION_FIXED)) { - /* This child is positioned out of normal flow, - * so it will have no affect on width */ - continue; - } - - if (min < child->min_width) - min = child->min_width; - if (max < child->max_width) - max = child->max_width; - - if (child_has_height) - block->flags |= HAS_HEIGHT; - } - } - - if (max < min) { - box_dump(stderr, block, 0, true); - assert(0); - } - - /* fixed width takes priority */ - if (block->type != BOX_TABLE_CELL && wtype == CSS_WIDTH_SET && - wunit != CSS_UNIT_PCT) { - min = max = FIXTOINT(nscss_len2px(&content->len_ctx, - width, wunit, block->style)); - if (bs == CSS_BOX_SIZING_BORDER_BOX) { - int border_box_fixed = 0; - float border_box_frac = 0; - calculate_mbp_width(&content->len_ctx, - block->style, LEFT, - false, true, true, - &border_box_fixed, &border_box_frac); - calculate_mbp_width(&content->len_ctx, - block->style, RIGHT, - false, true, true, - &border_box_fixed, &border_box_frac); - if (min < border_box_fixed) { - min = max = border_box_fixed; - } - } - } - - if (htype == CSS_HEIGHT_SET && hunit != CSS_UNIT_PCT && - height > INTTOFIX(0)) { - block->flags |= MAKE_HEIGHT; - block->flags |= HAS_HEIGHT; - } - - /* add margins, border, padding to min, max widths */ - /* Note: we don't know available width here so percentage margin - * and paddings are wrong. */ - if (bs == CSS_BOX_SIZING_BORDER_BOX && wtype == CSS_WIDTH_SET) { - /* Border and padding included in width, so just get margin */ - calculate_mbp_width(&content->len_ctx, - block->style, LEFT, true, false, false, - &extra_fixed, &extra_frac); - calculate_mbp_width(&content->len_ctx, - block->style, RIGHT, true, false, false, - &extra_fixed, &extra_frac); - } else { - calculate_mbp_width(&content->len_ctx, - block->style, LEFT, true, true, true, - &extra_fixed, &extra_frac); - calculate_mbp_width(&content->len_ctx, - block->style, RIGHT, true, true, true, - &extra_fixed, &extra_frac); - } - if (extra_fixed < 0) - extra_fixed = 0; - if (extra_frac < 0) - extra_frac = 0; - if (1.0 <= extra_frac) - extra_frac = 0.9; - if (block->style != NULL && - (css_computed_float(block->style) == CSS_FLOAT_LEFT || - css_computed_float(block->style) == CSS_FLOAT_RIGHT)) { - /* floated boxs */ - block->min_width = min + extra_fixed; - block->max_width = max + extra_fixed; - } else { - /* not floated */ - block->min_width = (min + extra_fixed) / (1.0 - extra_frac); - block->max_width = (max + extra_fixed) / (1.0 - extra_frac); - } - - assert(0 <= block->min_width && block->min_width <= block->max_width); -} - - -/** - * Adjust a specified width or height for the box-sizing property. - * - * This turns the specified dimension into a content-box dimension. - * - * \param len_ctx Length conversion context - * \param box gadget to adjust dimensions of - * \param available_width width of containing block - * \param setwidth set true if the dimension to be tweaked is a width, - * else set false for a height - * \param dimension current value for given width/height dimension. - * updated to new value after consideration of - * gadget properties. - */ -static void layout_handle_box_sizing( - const nscss_len_ctx *len_ctx, - struct box *box, - int available_width, - bool setwidth, - int *dimension) -{ - enum css_box_sizing_e bs; - - assert(box && box->style); - - bs = css_computed_box_sizing(box->style); - - if (bs == CSS_BOX_SIZING_BORDER_BOX) { - int orig = *dimension; - int fixed = 0; - float frac = 0; - - calculate_mbp_width(len_ctx, box->style, - setwidth ? LEFT : TOP, - false, true, true, &fixed, &frac); - calculate_mbp_width(len_ctx, box->style, - setwidth ? RIGHT : BOTTOM, - false, true, true, &fixed, &frac); - orig -= frac * available_width + fixed; - *dimension = orig > 0 ? orig : 0; - } -} - - -/** - * Calculate width, height, and thickness of margins, paddings, and borders. - * - * \param len_ctx Length conversion context - * \param available_width width of containing block - * \param viewport_height height of viewport in pixels or -ve if unknown - * \param box current box - * \param style style giving width, height, margins, paddings, - * and borders - * \param width updated to width, may be NULL - * \param height updated to height, may be NULL - * \param max_width updated to max-width, may be NULL - * \param min_width updated to min-width, may be NULL - * \param max_height updated to max-height, may be NULL - * \param min_height updated to min-height, may be NULL - * \param margin filled with margins, may be NULL - * \param padding filled with paddings, may be NULL - * \param border filled with border widths, may be NULL - */ -static void -layout_find_dimensions(const nscss_len_ctx *len_ctx, - int available_width, - int viewport_height, - struct box *box, - const css_computed_style *style, - int *width, - int *height, - int *max_width, - int *min_width, - int *max_height, - int *min_height, - int margin[4], - int padding[4], - struct box_border border[4]) -{ - struct box *containing_block = NULL; - unsigned int i; - - if (width) { - enum css_width_e wtype; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - wtype = css_computed_width(style, &value, &unit); - - if (wtype == CSS_WIDTH_SET) { - if (unit == CSS_UNIT_PCT) { - *width = FPCT_OF_INT_TOINT( - value, available_width); - } else { - *width = FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } else { - *width = AUTO; - } - - if (*width != AUTO) { - layout_handle_box_sizing(len_ctx, box, available_width, - true, width); - } - } - - if (height) { - enum css_height_e htype; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - htype = css_computed_height(style, &value, &unit); - - if (htype == CSS_HEIGHT_SET) { - if (unit == CSS_UNIT_PCT) { - enum css_height_e cbhtype; - - if (css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE && - box->parent) { - /* Box is absolutely positioned */ - assert(box->float_container); - containing_block = box->float_container; - } else if (box->float_container && - css_computed_position(box->style) != - CSS_POSITION_ABSOLUTE && - (css_computed_float(box->style) == - CSS_FLOAT_LEFT || - css_computed_float(box->style) == - CSS_FLOAT_RIGHT)) { - /* Box is a float */ - assert(box->parent && - box->parent->parent && - box->parent->parent->parent); - - containing_block = - box->parent->parent->parent; - } else if (box->parent && box->parent->type != - BOX_INLINE_CONTAINER) { - /* Box is a block level element */ - containing_block = box->parent; - } else if (box->parent && box->parent->type == - BOX_INLINE_CONTAINER) { - /* Box is an inline block */ - assert(box->parent->parent); - containing_block = box->parent->parent; - } - - if (containing_block) { - css_fixed f = 0; - css_unit u = CSS_UNIT_PX; - - cbhtype = css_computed_height( - containing_block->style, - &f, &u); - } - - if (containing_block && - containing_block->height != AUTO && - (css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE || - cbhtype == CSS_HEIGHT_SET)) { - /* Box is absolutely positioned or its - * containing block has a valid - * specified height. - * (CSS 2.1 Section 10.5) */ - *height = FPCT_OF_INT_TOINT(value, - containing_block->height); - } else if ((!box->parent || - !box->parent->parent) && - viewport_height >= 0) { - /* If root element or it's child - * (HTML or BODY) */ - *height = FPCT_OF_INT_TOINT(value, - viewport_height); - } else { - /* precentage height not permissible - * treat height as auto */ - *height = AUTO; - } - } else { - *height = FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } else { - *height = AUTO; - } - - if (*height != AUTO) { - layout_handle_box_sizing(len_ctx, box, available_width, - false, height); - } - } - - if (max_width) { - enum css_max_width_e type; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - type = css_computed_max_width(style, &value, &unit); - - if (type == CSS_MAX_WIDTH_SET) { - if (unit == CSS_UNIT_PCT) { - *max_width = FPCT_OF_INT_TOINT(value, - available_width); - } else { - *max_width = FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } else { - /* Inadmissible */ - *max_width = -1; - } - - if (*max_width != -1) { - layout_handle_box_sizing(len_ctx, box, available_width, - true, max_width); - } - } - - if (min_width) { - enum css_min_width_e type; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - type = ns_computed_min_width(style, &value, &unit); - - if (type == CSS_MIN_WIDTH_SET) { - if (unit == CSS_UNIT_PCT) { - *min_width = FPCT_OF_INT_TOINT(value, - available_width); - } else { - *min_width = FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } else { - /* Inadmissible */ - *min_width = 0; - } - - if (*min_width != 0) { - layout_handle_box_sizing(len_ctx, box, available_width, - true, min_width); - } - } - - if (max_height) { - enum css_max_height_e type; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - type = css_computed_max_height(style, &value, &unit); - - if (type == CSS_MAX_HEIGHT_SET) { - if (unit == CSS_UNIT_PCT) { - /* TODO: handle percentage */ - *max_height = -1; - } else { - *max_height = FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } else { - /* Inadmissible */ - *max_height = -1; - } - } - - if (min_height) { - enum css_min_height_e type; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - type = ns_computed_min_height(style, &value, &unit); - - if (type == CSS_MIN_HEIGHT_SET) { - if (unit == CSS_UNIT_PCT) { - /* TODO: handle percentage */ - *min_height = 0; - } else { - *min_height = FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } else { - /* Inadmissible */ - *min_height = 0; - } - } - - for (i = 0; i != 4; i++) { - if (margin) { - enum css_margin_e type = CSS_MARGIN_AUTO; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - type = margin_funcs[i](style, &value, &unit); - - if (type == CSS_MARGIN_SET) { - if (unit == CSS_UNIT_PCT) { - margin[i] = FPCT_OF_INT_TOINT(value, - available_width); - } else { - margin[i] = FIXTOINT(nscss_len2px( - len_ctx, - value, unit, style)); - } - } else { - margin[i] = AUTO; - } - } - - if (padding) { - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - padding_funcs[i](style, &value, &unit); - - if (unit == CSS_UNIT_PCT) { - padding[i] = FPCT_OF_INT_TOINT(value, - available_width); - } else { - padding[i] = FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - } - } - - /* Table cell borders are populated in table.c */ - if (border && box->type != BOX_TABLE_CELL) { - enum css_border_style_e bstyle = CSS_BORDER_STYLE_NONE; - css_color color = 0; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - border_width_funcs[i](style, &value, &unit); - bstyle = border_style_funcs[i](style); - border_color_funcs[i](style, &color); - - border[i].style = bstyle; - border[i].c = color; - - if (bstyle == CSS_BORDER_STYLE_HIDDEN || - bstyle == CSS_BORDER_STYLE_NONE) - /* spec unclear: following Mozilla */ - border[i].width = 0; - else - border[i].width = FIXTOINT(nscss_len2px(len_ctx, - value, unit, style)); - - /* Special case for border-collapse: make all borders - * on table/table-row-group/table-row zero width. */ - if (css_computed_border_collapse(style) == - CSS_BORDER_COLLAPSE_COLLAPSE && - (box->type == BOX_TABLE || - box->type == BOX_TABLE_ROW_GROUP || - box->type == BOX_TABLE_ROW)) - border[i].width = 0; - } - } -} - - -/** - * Find next block that current margin collapses to. - * - * \param len_ctx Length conversion context - * \param box box to start tree-order search from (top margin is included) - * \param block box responsible for current block fromatting context - * \param viewport_height height of viewport in px - * \param max_pos_margin updated to to maximum positive margin encountered - * \param max_neg_margin updated to to maximum negative margin encountered - * \return next box that current margin collapses to, or NULL if none. - */ -static struct box* -layout_next_margin_block(const nscss_len_ctx *len_ctx, - struct box *box, - struct box *block, - int viewport_height, - int *max_pos_margin, - int *max_neg_margin) -{ - assert(block != NULL); - - while (box != NULL) { - - if (box->type == BOX_INLINE_CONTAINER || (box->style && - (css_computed_position(box->style) != - CSS_POSITION_ABSOLUTE && - css_computed_position(box->style) != - CSS_POSITION_FIXED))) { - /* Not positioned */ - - /* Get margins */ - if (box->style) { - layout_find_dimensions(len_ctx, - box->parent->width, - viewport_height, box, - box->style, - NULL, NULL, NULL, NULL, - NULL, NULL, box->margin, - box->padding, box->border); - - /* Apply top margin */ - if (*max_pos_margin < box->margin[TOP]) - *max_pos_margin = box->margin[TOP]; - else if (*max_neg_margin < -box->margin[TOP]) - *max_neg_margin = -box->margin[TOP]; - } - - /* Check whether box is the box current margin collapses - * to */ - if (box->flags & MAKE_HEIGHT || - box->border[TOP].width || - box->padding[TOP] || - (box->style && - css_computed_overflow_y(box->style) != - CSS_OVERFLOW_VISIBLE) || - (box->type == BOX_INLINE_CONTAINER && - box != box->parent->children)) { - /* Collapse to this box; return it */ - return box; - } - } - - - /* Find next box */ - if (box->type == BOX_BLOCK && !box->object && box->children && - box->style && - css_computed_overflow_y(box->style) == - CSS_OVERFLOW_VISIBLE) { - /* Down into children. */ - box = box->children; - } else { - if (!box->next) { - /* No more siblings: - * Go up to first ancestor with a sibling. */ - do { - /* Apply bottom margin */ - if (*max_pos_margin < - box->margin[BOTTOM]) - *max_pos_margin = - box->margin[BOTTOM]; - else if (*max_neg_margin < - -box->margin[BOTTOM]) - *max_neg_margin = - -box->margin[BOTTOM]; - - box = box->parent; - } while (box != block && !box->next); - - if (box == block) { - /* Margins don't collapse with stuff - * outside the block formatting context - */ - return block; - } - } - - /* Apply bottom margin */ - if (*max_pos_margin < box->margin[BOTTOM]) - *max_pos_margin = box->margin[BOTTOM]; - else if (*max_neg_margin < -box->margin[BOTTOM]) - *max_neg_margin = -box->margin[BOTTOM]; - - /* To next sibling. */ - box = box->next; - - /* Get margins */ - if (box->style) { - layout_find_dimensions(len_ctx, - box->parent->width, - viewport_height, box, - box->style, - NULL, NULL, NULL, NULL, - NULL, NULL, box->margin, - box->padding, box->border); - } - } - } - - return NULL; -} - - -/** - * Find y coordinate which clears all floats on left and/or right. - * - * \param fl first float in float list - * \param clear type of clear - * \return y coordinate relative to ancestor box for floats - */ -static int layout_clear(struct box *fl, enum css_clear_e clear) -{ - int y = 0; - for (; fl; fl = fl->next_float) { - if ((clear == CSS_CLEAR_LEFT || clear == CSS_CLEAR_BOTH) && - fl->type == BOX_FLOAT_LEFT) - if (y < fl->y + fl->height) - y = fl->y + fl->height; - if ((clear == CSS_CLEAR_RIGHT || clear == CSS_CLEAR_BOTH) && - fl->type == BOX_FLOAT_RIGHT) - if (y < fl->y + fl->height) - y = fl->y + fl->height; - } - return y; -} - - -/** - * Find left and right edges in a vertical range. - * - * \param fl first float in float list - * \param y0 start of y range to search - * \param y1 end of y range to search - * \param x0 start left edge, updated to available left edge - * \param x1 start right edge, updated to available right edge - * \param left returns float on left if present - * \param right returns float on right if present - */ -static void -find_sides(struct box *fl, - int y0, int y1, - int *x0, int *x1, - struct box **left, - struct box **right) -{ - int fy0, fy1, fx0, fx1; - - NSLOG(layout, DEBUG, "y0 %i, y1 %i, x0 %i, x1 %i", y0, y1, *x0, *x1); - - *left = *right = 0; - for (; fl; fl = fl->next_float) { - fy1 = fl->y + fl->height; - if (fy1 < y0) { - /* Floats are sorted in order of decreasing bottom pos. - * Past here, all floats will be too high to concern us. - */ - return; - } - fy0 = fl->y; - if (y0 < fy1 && fy0 <= y1) { - if (fl->type == BOX_FLOAT_LEFT) { - fx1 = fl->x + fl->width; - if (*x0 < fx1) { - *x0 = fx1; - *left = fl; - } - } else { - fx0 = fl->x; - if (fx0 < *x1) { - *x1 = fx0; - *right = fl; - } - } - } - } - - NSLOG(layout, DEBUG, "x0 %i, x1 %i, left %p, right %p", *x0, *x1, - *left, *right); -} - - - - -/** - * Solve the width constraint as given in CSS 2.1 section 10.3.3. - * - * \param box Box to solve constraint for - * \param available_width Max width available in pixels - * \param width Current box width - * \param lm Min left margin required to avoid floats in px. - * zero if not applicable - * \param rm Min right margin required to avoid floats in px. - * zero if not applicable - * \param max_width Box max-width ( -ve means no max-width to apply) - * \param min_width Box min-width ( <=0 means no min-width to apply) - * \return New box width - * - * \post \a box's left/right margins will be updated. - */ -static int -layout_solve_width(struct box *box, - int available_width, - int width, - int lm, - int rm, - int max_width, - int min_width) -{ - bool auto_width = false; - - /* Increase specified left/right margins */ - if (box->margin[LEFT] != AUTO && box->margin[LEFT] < lm && - box->margin[LEFT] >= 0) - box->margin[LEFT] = lm; - if (box->margin[RIGHT] != AUTO && box->margin[RIGHT] < rm && - box->margin[RIGHT] >= 0) - box->margin[RIGHT] = rm; - - /* Find width */ - if (width == AUTO) { - int margin_left = box->margin[LEFT]; - int margin_right = box->margin[RIGHT]; - - if (margin_left == AUTO) { - margin_left = lm; - } - if (margin_right == AUTO) { - margin_right = rm; - } - - width = available_width - - (margin_left + box->border[LEFT].width + - box->padding[LEFT] + box->padding[RIGHT] + - box->border[RIGHT].width + margin_right); - width = width < 0 ? 0 : width; - auto_width = true; - } - - if (max_width >= 0 && width > max_width) { - /* max-width is admissable and width exceeds max-width */ - width = max_width; - auto_width = false; - } - - if (min_width > 0 && width < min_width) { - /* min-width is admissable and width is less than max-width */ - width = min_width; - auto_width = false; - } - - /* Width was auto, and unconstrained by min/max width, so we're done */ - if (auto_width) { - /* any other 'auto' become 0 or the minimum required values */ - if (box->margin[LEFT] == AUTO) { - box->margin[LEFT] = lm; - } - if (box->margin[RIGHT] == AUTO) { - box->margin[RIGHT] = rm; - } - return width; - } - - /* Width was not auto, or was constrained by min/max width - * Need to compute left/right margins */ - - /* HTML alignment (only applies to over-constrained boxes) */ - if (box->margin[LEFT] != AUTO && box->margin[RIGHT] != AUTO && - box->parent != NULL && box->parent->style != NULL) { - switch (css_computed_text_align(box->parent->style)) { - case CSS_TEXT_ALIGN_LIBCSS_RIGHT: - box->margin[LEFT] = AUTO; - box->margin[RIGHT] = 0; - break; - case CSS_TEXT_ALIGN_LIBCSS_CENTER: - box->margin[LEFT] = box->margin[RIGHT] = AUTO; - break; - case CSS_TEXT_ALIGN_LIBCSS_LEFT: - box->margin[LEFT] = 0; - box->margin[RIGHT] = AUTO; - break; - default: - /* Leave it alone; no HTML alignment */ - break; - } - } - - if (box->margin[LEFT] == AUTO && box->margin[RIGHT] == AUTO) { - /* make the margins equal, centering the element */ - box->margin[LEFT] = box->margin[RIGHT] = - (available_width - lm - rm - - (box->border[LEFT].width + box->padding[LEFT] + - width + box->padding[RIGHT] + - box->border[RIGHT].width)) / 2; - - if (box->margin[LEFT] < 0) { - box->margin[RIGHT] += box->margin[LEFT]; - box->margin[LEFT] = 0; - } - - box->margin[LEFT] += lm; - - } else if (box->margin[LEFT] == AUTO) { - box->margin[LEFT] = available_width - lm - - (box->border[LEFT].width + box->padding[LEFT] + - width + box->padding[RIGHT] + - box->border[RIGHT].width + box->margin[RIGHT]); - box->margin[LEFT] = box->margin[LEFT] < lm - ? lm : box->margin[LEFT]; - } else { - /* margin-right auto or "over-constrained" */ - box->margin[RIGHT] = available_width - rm - - (box->margin[LEFT] + box->border[LEFT].width + - box->padding[LEFT] + width + - box->padding[RIGHT] + - box->border[RIGHT].width); - } - - return width; -} - - -/** - * Compute dimensions of box, margins, paddings, and borders for a block-level - * element. - * - * \param len_ctx Length conversion context - * \param available_width Max width available in pixels - * \param viewport_height Height of viewport in pixels or -ve if unknown - * \param lm min left margin required to avoid floats in px. - * zero if not applicable - * \param rm min right margin required to avoid floats in px. - * zero if not applicable - * \param box box to find dimensions of. updated with new width, - * height, margins, borders and paddings - * - * See CSS 2.1 10.3.3, 10.3.4, 10.6.2, and 10.6.3. - */ -static void -layout_block_find_dimensions(const nscss_len_ctx *len_ctx, - int available_width, - int viewport_height, - int lm, - int rm, - struct box *box) -{ - int width, max_width, min_width; - int height, max_height, min_height; - int *margin = box->margin; - int *padding = box->padding; - struct box_border *border = box->border; - const css_computed_style *style = box->style; - - layout_find_dimensions(len_ctx, available_width, viewport_height, box, - style, &width, &height, &max_width, &min_width, - &max_height, &min_height, margin, padding, border); - - if (box->object && !(box->flags & REPLACE_DIM) && - content_get_type(box->object) != CONTENT_HTML) { - /* block-level replaced element, see 10.3.4 and 10.6.2 */ - layout_get_object_dimensions(box, &width, &height, - min_width, max_width, min_height, max_height); - } - - box->width = layout_solve_width(box, available_width, width, lm, rm, - max_width, min_width); - box->height = height; - - if (margin[TOP] == AUTO) - margin[TOP] = 0; - if (margin[BOTTOM] == AUTO) - margin[BOTTOM] = 0; -} - - -/** - * Manipulate a block's [RB]padding/height/width to accommodate scrollbars - * - * \param box Box to apply scrollbar space too. Must be BOX_BLOCK. - * \param which Which scrollbar to make space for. Must be RIGHT or BOTTOM. - */ -static void layout_block_add_scrollbar(struct box *box, int which) -{ - enum css_overflow_e overflow_x, overflow_y; - - assert(box->type == BOX_BLOCK && (which == RIGHT || which == BOTTOM)); - - if (box->style == NULL) - return; - - overflow_x = css_computed_overflow_x(box->style); - overflow_y = css_computed_overflow_y(box->style); - - if (which == BOTTOM && - (overflow_x == CSS_OVERFLOW_SCROLL || - overflow_x == CSS_OVERFLOW_AUTO || - (box->object && - content_get_type(box->object) == CONTENT_HTML))) { - /* make space for scrollbar, unless height is AUTO */ - if (box->height != AUTO && - (overflow_x == CSS_OVERFLOW_SCROLL || - box_hscrollbar_present(box))) { - box->padding[BOTTOM] += SCROLLBAR_WIDTH; - } - - } else if (which == RIGHT && - (overflow_y == CSS_OVERFLOW_SCROLL || - overflow_y == CSS_OVERFLOW_AUTO || - (box->object && - content_get_type(box->object) == CONTENT_HTML))) { - /* make space for scrollbars, unless width is AUTO */ - enum css_height_e htype; - css_fixed height = 0; - css_unit hunit = CSS_UNIT_PX; - htype = css_computed_height(box->style, &height, &hunit); - - if (which == RIGHT && box->width != AUTO && - htype == CSS_HEIGHT_SET && - (overflow_y == CSS_OVERFLOW_SCROLL || - box_vscrollbar_present(box))) { - box->width -= SCROLLBAR_WIDTH; - box->padding[RIGHT] += SCROLLBAR_WIDTH; - } - } -} - - -/** - * Moves the children of a box by a specified amount - * - * \param box top of tree of boxes - * \param x the amount to move children by horizontally - * \param y the amount to move children by vertically - */ -static void layout_move_children(struct box *box, int x, int y) -{ - assert(box); - - for (box = box->children; box; box = box->next) { - box->x += x; - box->y += y; - } -} - - -/** - * Layout a table. - * - * \param table table to layout - * \param available_width width of containing block - * \param content memory pool for any new boxes - * \return true on success, false on memory exhaustion - */ -static bool layout_table(struct box *table, int available_width, - html_content *content) -{ - unsigned int columns = table->columns; /* total columns */ - unsigned int i; - unsigned int *row_span; - int *excess_y; - int table_width, min_width = 0, max_width = 0; - int required_width = 0; - int x, remainder = 0, count = 0; - int table_height = 0; - int min_height = 0; - int *xs; /* array of column x positions */ - int auto_width; - int spare_width; - int relative_sum = 0; - int border_spacing_h = 0, border_spacing_v = 0; - int spare_height; - int positioned_columns = 0; - struct box *containing_block = NULL; - struct box *c; - struct box *row; - struct box *row_group; - struct box **row_span_cell; - struct column *col; - const css_computed_style *style = table->style; - enum css_width_e wtype; - enum css_height_e htype; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - assert(table->type == BOX_TABLE); - assert(style); - assert(table->children && table->children->children); - assert(columns); - - /* allocate working buffers */ - col = malloc(columns * sizeof col[0]); - excess_y = malloc(columns * sizeof excess_y[0]); - row_span = malloc(columns * sizeof row_span[0]); - row_span_cell = malloc(columns * sizeof row_span_cell[0]); - xs = malloc((columns + 1) * sizeof xs[0]); - if (!col || !xs || !row_span || !excess_y || !row_span_cell) { - free(col); - free(excess_y); - free(row_span); - free(row_span_cell); - free(xs); - return false; - } - - memcpy(col, table->col, sizeof(col[0]) * columns); - - /* find margins, paddings, and borders for table and cells */ - layout_find_dimensions(&content->len_ctx, available_width, -1, table, - style, 0, 0, 0, 0, 0, 0, table->margin, table->padding, - table->border); - for (row_group = table->children; row_group; - row_group = row_group->next) { - for (row = row_group->children; row; row = row->next) { - for (c = row->children; c; c = c->next) { - enum css_overflow_e overflow_x; - enum css_overflow_e overflow_y; - - assert(c->style); - table_used_border_for_cell( - &content->len_ctx, c); - layout_find_dimensions(&content->len_ctx, - available_width, -1, c, - c->style, 0, 0, 0, 0, 0, 0, - 0, c->padding, c->border); - - overflow_x = css_computed_overflow_x(c->style); - overflow_y = css_computed_overflow_y(c->style); - - if (overflow_x == CSS_OVERFLOW_SCROLL || - overflow_x == - CSS_OVERFLOW_AUTO) { - c->padding[BOTTOM] += SCROLLBAR_WIDTH; - } - if (overflow_y == CSS_OVERFLOW_SCROLL || - overflow_y == - CSS_OVERFLOW_AUTO) { - c->padding[RIGHT] += SCROLLBAR_WIDTH; - } - } - } - } - - /* border-spacing is used in the separated borders model */ - if (css_computed_border_collapse(style) == - CSS_BORDER_COLLAPSE_SEPARATE) { - css_fixed h = 0, v = 0; - css_unit hu = CSS_UNIT_PX, vu = CSS_UNIT_PX; - - css_computed_border_spacing(style, &h, &hu, &v, &vu); - - border_spacing_h = FIXTOINT(nscss_len2px(&content->len_ctx, - h, hu, style)); - border_spacing_v = FIXTOINT(nscss_len2px(&content->len_ctx, - v, vu, style)); - } - - /* find specified table width, or available width if auto-width */ - wtype = css_computed_width(style, &value, &unit); - if (wtype == CSS_WIDTH_SET) { - if (unit == CSS_UNIT_PCT) { - table_width = FPCT_OF_INT_TOINT(value, available_width); - } else { - table_width = - FIXTOINT(nscss_len2px(&content->len_ctx, - value, unit, style)); - } - - /* specified width includes border */ - table_width -= table->border[LEFT].width + - table->border[RIGHT].width; - table_width = table_width < 0 ? 0 : table_width; - - auto_width = table_width; - } else { - table_width = AUTO; - auto_width = available_width - - ((table->margin[LEFT] == AUTO ? 0 : - table->margin[LEFT]) + - table->border[LEFT].width + - table->padding[LEFT] + - table->padding[RIGHT] + - table->border[RIGHT].width + - (table->margin[RIGHT] == AUTO ? 0 : - table->margin[RIGHT])); - } - - /* Find any table height specified within CSS/HTML */ - htype = css_computed_height(style, &value, &unit); - if (htype == CSS_HEIGHT_SET) { - if (unit == CSS_UNIT_PCT) { - /* This is the minimum height for the table - * (see 17.5.3) */ - if (css_computed_position(table->style) == - CSS_POSITION_ABSOLUTE) { - /* Table is absolutely positioned */ - assert(table->float_container); - containing_block = table->float_container; - } else if (table->float_container && - css_computed_position(table->style) != - CSS_POSITION_ABSOLUTE && - (css_computed_float(table->style) == - CSS_FLOAT_LEFT || - css_computed_float(table->style) == - CSS_FLOAT_RIGHT)) { - /* Table is a float */ - assert(table->parent && table->parent->parent && - table->parent->parent->parent); - containing_block = - table->parent->parent->parent; - } else if (table->parent && table->parent->type != - BOX_INLINE_CONTAINER) { - /* Table is a block level element */ - containing_block = table->parent; - } else if (table->parent && table->parent->type == - BOX_INLINE_CONTAINER) { - /* Table is an inline block */ - assert(table->parent->parent); - containing_block = table->parent->parent; - } - - if (containing_block) { - css_fixed ignored = 0; - - htype = css_computed_height( - containing_block->style, - &ignored, &unit); - } - - if (containing_block && - containing_block->height != AUTO && - (css_computed_position(table->style) == - CSS_POSITION_ABSOLUTE || - htype == CSS_HEIGHT_SET)) { - /* Table is absolutely positioned or its - * containing block has a valid specified - * height. (CSS 2.1 Section 10.5) */ - min_height = FPCT_OF_INT_TOINT(value, - containing_block->height); - } - } else { - /* This is the minimum height for the table - * (see 17.5.3) */ - min_height = FIXTOINT(nscss_len2px(&content->len_ctx, - value, unit, style)); - } - } - - /* calculate width required by cells */ - for (i = 0; i != columns; i++) { - - NSLOG(layout, DEBUG, - "table %p, column %u: type %s, width %i, min %i, max %i", - table, - i, - ((const char *[]){ - "UNKNOWN", - "FIXED", - "AUTO", - "PERCENT", - "RELATIVE", - })[col[i].type], - col[i].width, - col[i].min, - col[i].max); - - - if (col[i].positioned) { - positioned_columns++; - continue; - } else if (col[i].type == COLUMN_WIDTH_FIXED) { - if (col[i].width < col[i].min) - col[i].width = col[i].max = col[i].min; - else - col[i].min = col[i].max = col[i].width; - required_width += col[i].width; - } else if (col[i].type == COLUMN_WIDTH_PERCENT) { - int width = col[i].width * auto_width / 100; - required_width += col[i].min < width ? width : - col[i].min; - } else - required_width += col[i].min; - - NSLOG(layout, DEBUG, "required_width %i", required_width); - } - required_width += (columns + 1 - positioned_columns) * - border_spacing_h; - - NSLOG(layout, DEBUG, - "width %i, min %i, max %i, auto %i, required %i", table_width, - table->min_width, table->max_width, auto_width, required_width); - - if (auto_width < required_width) { - /* table narrower than required width for columns: - * treat percentage widths as maximums */ - for (i = 0; i != columns; i++) { - if (col[i].type == COLUMN_WIDTH_RELATIVE) - continue; - if (col[i].type == COLUMN_WIDTH_PERCENT) { - col[i].max = auto_width * col[i].width / 100; - if (col[i].max < col[i].min) - col[i].max = col[i].min; - } - min_width += col[i].min; - max_width += col[i].max; - } - } else { - /* take percentages exactly */ - for (i = 0; i != columns; i++) { - if (col[i].type == COLUMN_WIDTH_RELATIVE) - continue; - if (col[i].type == COLUMN_WIDTH_PERCENT) { - int width = auto_width * col[i].width / 100; - if (width < col[i].min) - width = col[i].min; - col[i].min = col[i].width = col[i].max = width; - col[i].type = COLUMN_WIDTH_FIXED; - } - min_width += col[i].min; - max_width += col[i].max; - } - } - - /* allocate relative widths */ - spare_width = auto_width; - for (i = 0; i != columns; i++) { - if (col[i].type == COLUMN_WIDTH_RELATIVE) - relative_sum += col[i].width; - else if (col[i].type == COLUMN_WIDTH_FIXED) - spare_width -= col[i].width; - else - spare_width -= col[i].min; - } - spare_width -= (columns + 1) * border_spacing_h; - if (relative_sum != 0) { - if (spare_width < 0) - spare_width = 0; - for (i = 0; i != columns; i++) { - if (col[i].type == COLUMN_WIDTH_RELATIVE) { - col[i].min = ceil(col[i].max = - (float) spare_width - * (float) col[i].width - / relative_sum); - min_width += col[i].min; - max_width += col[i].max; - } - } - } - min_width += (columns + 1) * border_spacing_h; - max_width += (columns + 1) * border_spacing_h; - - if (auto_width <= min_width) { - /* not enough space: minimise column widths */ - for (i = 0; i < columns; i++) { - col[i].width = col[i].min; - } - table_width = min_width; - } else if (max_width <= auto_width) { - /* more space than maximum width */ - if (table_width == AUTO) { - /* for auto-width tables, make columns max width */ - for (i = 0; i < columns; i++) { - col[i].width = col[i].max; - } - table_width = max_width; - } else { - /* for fixed-width tables, distribute the extra space - * too */ - unsigned int flexible_columns = 0; - for (i = 0; i != columns; i++) - if (col[i].type != COLUMN_WIDTH_FIXED) - flexible_columns++; - if (flexible_columns == 0) { - int extra = (table_width - max_width) / columns; - remainder = (table_width - max_width) - - (extra * columns); - for (i = 0; i != columns; i++) { - col[i].width = col[i].max + extra; - count -= remainder; - if (count < 0) { - col[i].width++; - count += columns; - } - } - - } else { - int extra = (table_width - max_width) / - flexible_columns; - remainder = (table_width - max_width) - - (extra * flexible_columns); - for (i = 0; i != columns; i++) - if (col[i].type != COLUMN_WIDTH_FIXED) { - col[i].width = col[i].max + - extra; - count -= remainder; - if (count < 0) { - col[i].width++; - count += flexible_columns; - } - } - } - } - } else { - /* space between min and max: fill it exactly */ - float scale = (float) (auto_width - min_width) / - (float) (max_width - min_width); - /* fprintf(stderr, "filling, scale %f\n", scale); */ - for (i = 0; i < columns; i++) { - col[i].width = col[i].min + (int) (0.5 + - (col[i].max - col[i].min) * scale); - } - table_width = auto_width; - } - - xs[0] = x = border_spacing_h; - for (i = 0; i != columns; i++) { - if (!col[i].positioned) - x += col[i].width + border_spacing_h; - xs[i + 1] = x; - row_span[i] = 0; - excess_y[i] = 0; - row_span_cell[i] = 0; - } - - /* position cells */ - table_height = border_spacing_v; - for (row_group = table->children; row_group; - row_group = row_group->next) { - int row_group_height = 0; - for (row = row_group->children; row; row = row->next) { - int row_height = 0; - - htype = css_computed_height(row->style, &value, &unit); - if (htype == CSS_HEIGHT_SET && unit != CSS_UNIT_PCT) { - row_height = FIXTOINT(nscss_len2px( - &content->len_ctx, - value, unit, row->style)); - } - for (c = row->children; c; c = c->next) { - assert(c->style); - c->width = xs[c->start_column + c->columns] - - xs[c->start_column] - - border_spacing_h - - c->border[LEFT].width - - c->padding[LEFT] - - c->padding[RIGHT] - - c->border[RIGHT].width; - c->float_children = 0; - c->cached_place_below_level = 0; - - c->height = AUTO; - if (!layout_block_context(c, -1, content)) { - free(col); - free(excess_y); - free(row_span); - free(row_span_cell); - free(xs); - return false; - } - /* warning: c->descendant_y0 and - * c->descendant_y1 used as temporary storage - * until after vertical alignment is complete */ - c->descendant_y0 = c->height; - c->descendant_y1 = c->padding[BOTTOM]; - - htype = css_computed_height(c->style, - &value, &unit); - - if (htype == CSS_HEIGHT_SET && - unit != CSS_UNIT_PCT) { - /* some sites use height="1" or similar - * to attempt to make cells as small as - * possible, so treat it as a minimum */ - int h = FIXTOINT(nscss_len2px( - &content->len_ctx, - value, unit, c->style)); - if (c->height < h) - c->height = h; - } - /* specified row height is treated as a minimum - */ - if (c->height < row_height) - c->height = row_height; - c->x = xs[c->start_column] + - c->border[LEFT].width; - c->y = c->border[TOP].width; - for (i = 0; i != c->columns; i++) { - row_span[c->start_column + i] = c->rows; - excess_y[c->start_column + i] = - c->border[TOP].width + - c->padding[TOP] + - c->height + - c->padding[BOTTOM] + - c->border[BOTTOM].width; - row_span_cell[c->start_column + i] = 0; - } - row_span_cell[c->start_column] = c; - c->padding[BOTTOM] = -border_spacing_v - - c->border[TOP].width - - c->padding[TOP] - - c->height - - c->border[BOTTOM].width; - } - for (i = 0; i != columns; i++) - if (row_span[i] != 0) - row_span[i]--; - else - row_span_cell[i] = 0; - if (row->next || row_group->next) { - /* row height is greatest excess of a cell - * which ends in this row */ - for (i = 0; i != columns; i++) - if (row_span[i] == 0 && row_height < - excess_y[i]) - row_height = excess_y[i]; - } else { - /* except in the last row */ - for (i = 0; i != columns; i++) - if (row_height < excess_y[i]) - row_height = excess_y[i]; - } - for (i = 0; i != columns; i++) { - if (row_height < excess_y[i]) - excess_y[i] -= row_height; - else - excess_y[i] = 0; - if (row_span_cell[i] != 0) - row_span_cell[i]->padding[BOTTOM] += - row_height + - border_spacing_v; - } - - row->x = 0; - row->y = row_group_height; - row->width = table_width; - row->height = row_height; - row_group_height += row_height + border_spacing_v; - } - row_group->x = 0; - row_group->y = table_height; - row_group->width = table_width; - row_group->height = row_group_height; - table_height += row_group_height; - } - /* Table height is either the height of the contents, or specified - * height if greater */ - table_height = max(table_height, min_height); - /** \todo distribute spare height over the row groups / rows / cells */ - - /* perform vertical alignment */ - for (row_group = table->children; row_group; - row_group = row_group->next) { - for (row = row_group->children; row; row = row->next) { - for (c = row->children; c; c = c->next) { - enum css_vertical_align_e vertical_align; - - /* unextended bottom padding is in - * c->descendant_y1, and unextended - * cell height is in c->descendant_y0 */ - spare_height = (c->padding[BOTTOM] - - c->descendant_y1) + - (c->height - c->descendant_y0); - - vertical_align = css_computed_vertical_align( - c->style, &value, &unit); - - switch (vertical_align) { - case CSS_VERTICAL_ALIGN_SUB: - case CSS_VERTICAL_ALIGN_SUPER: - case CSS_VERTICAL_ALIGN_TEXT_TOP: - case CSS_VERTICAL_ALIGN_TEXT_BOTTOM: - case CSS_VERTICAL_ALIGN_SET: - case CSS_VERTICAL_ALIGN_BASELINE: - /* todo: baseline alignment, for now - * just use ALIGN_TOP */ - case CSS_VERTICAL_ALIGN_TOP: - break; - case CSS_VERTICAL_ALIGN_MIDDLE: - c->padding[TOP] += spare_height / 2; - c->padding[BOTTOM] -= spare_height / 2; - layout_move_children(c, 0, - spare_height / 2); - break; - case CSS_VERTICAL_ALIGN_BOTTOM: - c->padding[TOP] += spare_height; - c->padding[BOTTOM] -= spare_height; - layout_move_children(c, 0, - spare_height); - break; - case CSS_VERTICAL_ALIGN_INHERIT: - assert(0); - break; - } - } - } - } - - /* Top and bottom margins of 'auto' are set to 0. CSS2.1 10.6.3 */ - if (table->margin[TOP] == AUTO) - table->margin[TOP] = 0; - if (table->margin[BOTTOM] == AUTO) - table->margin[BOTTOM] = 0; - - free(col); - free(excess_y); - free(row_span); - free(row_span_cell); - free(xs); - - table->width = table_width; - table->height = table_height; - - return true; -} - - -/** - * Manimpulate box height according to CSS min-height and max-height properties - * - * \param len_ctx CSS length conversion context for document. - * \param box block to modify with any min-height or max-height - * \param container containing block for absolutely positioned elements, or - * NULL for non absolutely positioned elements. - * \return whether the height has been changed - */ -static bool layout_apply_minmax_height( - const nscss_len_ctx *len_ctx, - struct box *box, - struct box *container) -{ - int h; - struct box *containing_block = NULL; - bool updated = false; - - /* Find containing block for percentage heights */ - if (box->style != NULL && css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE) { - /* Box is absolutely positioned */ - assert(container); - containing_block = container; - } else if (box->float_container && box->style != NULL && - (css_computed_float(box->style) == CSS_FLOAT_LEFT || - css_computed_float(box->style) == CSS_FLOAT_RIGHT)) { - /* Box is a float */ - assert(box->parent && box->parent->parent && - box->parent->parent->parent); - containing_block = box->parent->parent->parent; - } else if (box->parent && box->parent->type != BOX_INLINE_CONTAINER) { - /* Box is a block level element */ - containing_block = box->parent; - } else if (box->parent && box->parent->type == BOX_INLINE_CONTAINER) { - /* Box is an inline block */ - assert(box->parent->parent); - containing_block = box->parent->parent; - } - - if (box->style) { - enum css_height_e htype = CSS_HEIGHT_AUTO; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - if (containing_block) { - htype = css_computed_height(containing_block->style, - &value, &unit); - } - - /* max-height */ - if (css_computed_max_height(box->style, &value, &unit) == - CSS_MAX_HEIGHT_SET) { - if (unit == CSS_UNIT_PCT) { - if (containing_block && - containing_block->height != AUTO && - (css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE || - htype == CSS_HEIGHT_SET)) { - /* Box is absolutely positioned or its - * containing block has a valid - * specified height. (CSS 2.1 - * Section 10.5) */ - h = FPCT_OF_INT_TOINT(value, - containing_block->height); - if (h < box->height) { - box->height = h; - updated = true; - } - } - } else { - h = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); - if (h < box->height) { - box->height = h; - updated = true; - } - } - } - - /* min-height */ - if (ns_computed_min_height(box->style, &value, &unit) == - CSS_MIN_HEIGHT_SET) { - if (unit == CSS_UNIT_PCT) { - if (containing_block && - containing_block->height != AUTO && - (css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE || - htype == CSS_HEIGHT_SET)) { - /* Box is absolutely positioned or its - * containing block has a valid - * specified height. (CSS 2.1 - * Section 10.5) */ - h = FPCT_OF_INT_TOINT(value, - containing_block->height); - if (h > box->height) { - box->height = h; - updated = true; - } - } - } else { - h = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); - if (h > box->height) { - box->height = h; - updated = true; - } - } - } - } - return updated; -} - - -/** - * Layout a block which contains an object. - * - * \param block box of type BLOCK, INLINE_BLOCK, TABLE, or TABLE_CELL - * \return true on success, false on memory exhaustion - */ -static bool layout_block_object(struct box *block) -{ - assert(block); - assert(block->type == BOX_BLOCK || - block->type == BOX_INLINE_BLOCK || - block->type == BOX_TABLE || - block->type == BOX_TABLE_CELL); - assert(block->object); - - NSLOG(layout, DEBUG, "block %p, object %p, width %i", block, - hlcache_handle_get_url(block->object), block->width); - - if (content_get_type(block->object) == CONTENT_HTML) { - content_reformat(block->object, false, block->width, 1); - } else { - /* Non-HTML objects */ - /* this case handled already in - * layout_block_find_dimensions() */ - } - - return true; -} - - -/** - * Insert a float into a container. - * - * \param cont block formatting context block, used to contain float - * \param b box to add to float - * - * This sorts floats in order of descending bottom edges. - */ -static void add_float_to_container(struct box *cont, struct box *b) -{ - struct box *box = cont->float_children; - int b_bottom = b->y + b->height; - - assert(b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT); - - if (box == NULL) { - /* No other float children */ - b->next_float = NULL; - cont->float_children = b; - return; - } else if (b_bottom >= box->y + box->height) { - /* Goes at start of list */ - b->next_float = cont->float_children; - cont->float_children = b; - } else { - struct box *prev = NULL; - while (box != NULL && b_bottom < box->y + box->height) { - prev = box; - box = box->next_float; - } - if (prev != NULL) { - b->next_float = prev->next_float; - prev->next_float = b; - } - } -} - - -/** - * Split a text box. - * - * \param content memory pool for any new boxes - * \param fstyle style for text in text box - * \param split_box box with text to split - * \param new_length new length for text in split_box, after splitting - * \param new_width new width for text in split_box, after splitting - * \return true on success, false on memory exhaustion - * - * A new box is created and inserted into the box tree after split_box, - * containing the text after new_length excluding the initial space character. - */ -static bool -layout_text_box_split(html_content *content, - plot_font_style_t *fstyle, - struct box *split_box, - size_t new_length, - int new_width) -{ - int space_width = split_box->space; - struct box *c2; - const struct gui_layout_table *font_func = content->font_func; - bool space = (split_box->text[new_length] == ' '); - int used_length = new_length + (space ? 1 : 0); - - if ((space && space_width == 0) || space_width == UNKNOWN_WIDTH) { - /* We're need to add a space, and we don't know how big - * it's to be, OR we have a space of unknown width anyway; - * Calculate space width */ - font_func->width(fstyle, " ", 1, &space_width); - } - - if (split_box->space == UNKNOWN_WIDTH) - split_box->space = space_width; - if (!space) - space_width = 0; - - /* Create clone of split_box, c2 */ - c2 = talloc_memdup(content->bctx, split_box, sizeof *c2); - if (!c2) - return false; - c2->flags |= CLONE; - - /* Set remaining text in c2 */ - c2->text += used_length; - - /* Set c2 according to the remaining text */ - c2->width -= new_width + space_width; - c2->flags &= ~MEASURED; /* width has been estimated */ - c2->length = split_box->length - used_length; - - /* Update split_box for its reduced text */ - split_box->width = new_width; - split_box->flags |= MEASURED; - split_box->length = new_length; - split_box->space = space_width; - - /* Insert c2 into box list */ - c2->next = split_box->next; - split_box->next = c2; - c2->prev = split_box; - if (c2->next) - c2->next->prev = c2; - else - c2->parent->last = c2; - - NSLOG(layout, DEBUG, - "split_box %p len: %" PRIsizet " \"%.*s\"", - split_box, - split_box->length, - (int)split_box->length, - split_box->text); - NSLOG(layout, DEBUG, - " new_box %p len: %" PRIsizet " \"%.*s\"", - c2, - c2->length, - (int)c2->length, - c2->text); - - return true; -} - - -/** - * Compute dimensions of box, margins, paddings, and borders for a floating - * element using shrink-to-fit. Also used for inline-blocks. - * - * \param len_ctx CSS length conversion context for document. - * \param available_width Max width available in pixels - * \param style Box's style - * \param box Box for which to find dimensions - * Box margins, borders, paddings, width and - * height are updated. - */ -static void -layout_float_find_dimensions( - const nscss_len_ctx *len_ctx, - int available_width, - const css_computed_style *style, - struct box *box) -{ - int width, height, max_width, min_width, max_height, min_height; - int *margin = box->margin; - int *padding = box->padding; - struct box_border *border = box->border; - enum css_overflow_e overflow_x = css_computed_overflow_x(style); - enum css_overflow_e overflow_y = css_computed_overflow_y(style); - int scrollbar_width_x = - (overflow_x == CSS_OVERFLOW_SCROLL || - overflow_x == CSS_OVERFLOW_AUTO) ? - SCROLLBAR_WIDTH : 0; - int scrollbar_width_y = - (overflow_y == CSS_OVERFLOW_SCROLL || - overflow_y == CSS_OVERFLOW_AUTO) ? - SCROLLBAR_WIDTH : 0; - - layout_find_dimensions(len_ctx, available_width, -1, box, style, - &width, &height, &max_width, &min_width, - &max_height, &min_height, margin, padding, border); - - if (margin[LEFT] == AUTO) - margin[LEFT] = 0; - if (margin[RIGHT] == AUTO) - margin[RIGHT] = 0; - - if (box->gadget == NULL) { - padding[RIGHT] += scrollbar_width_y; - padding[BOTTOM] += scrollbar_width_x; - } - - if (box->object && !(box->flags & REPLACE_DIM) && - content_get_type(box->object) != CONTENT_HTML) { - /* Floating replaced element, with intrinsic width or height. - * See 10.3.6 and 10.6.2 */ - layout_get_object_dimensions(box, &width, &height, - min_width, max_width, min_height, max_height); - } else if (box->gadget && (box->gadget->type == GADGET_TEXTBOX || - box->gadget->type == GADGET_PASSWORD || - box->gadget->type == GADGET_FILE || - box->gadget->type == GADGET_TEXTAREA)) { - css_fixed size = 0; - css_unit unit = CSS_UNIT_EM; - - /* Give sensible dimensions to gadgets, with auto width/height, - * that don't shrink to fit contained text. */ - assert(box->style); - - if (box->gadget->type == GADGET_TEXTBOX || - box->gadget->type == GADGET_PASSWORD || - box->gadget->type == GADGET_FILE) { - if (width == AUTO) { - size = INTTOFIX(10); - width = FIXTOINT(nscss_len2px(len_ctx, - size, unit, box->style)); - } - if (box->gadget->type == GADGET_FILE && - height == AUTO) { - size = FLTTOFIX(1.5); - height = FIXTOINT(nscss_len2px(len_ctx, - size, unit, box->style)); - } - } - if (box->gadget->type == GADGET_TEXTAREA) { - if (width == AUTO) { - size = INTTOFIX(10); - width = FIXTOINT(nscss_len2px(len_ctx, - size, unit, box->style)); - } - if (height == AUTO) { - size = INTTOFIX(4); - height = FIXTOINT(nscss_len2px(len_ctx, - size, unit, box->style)); - } - } - } else if (width == AUTO) { - /* CSS 2.1 section 10.3.5 */ - width = min(max(box->min_width, available_width), - box->max_width); - - /* width includes margin, borders and padding */ - if (width == available_width) { - width -= box->margin[LEFT] + box->border[LEFT].width + - box->padding[LEFT] + - box->padding[RIGHT] + - box->border[RIGHT].width + - box->margin[RIGHT]; - } else { - /* width was obtained from a min_width or max_width - * value, so need to use the same method for calculating - * mbp as was used in layout_minmax_block() */ - int fixed = 0; - float frac = 0; - calculate_mbp_width(len_ctx, box->style, LEFT, - true, true, true, &fixed, &frac); - calculate_mbp_width(len_ctx, box->style, RIGHT, - true, true, true, &fixed, &frac); - if (fixed < 0) - fixed = 0; - - width -= fixed; - } - - if (max_width >= 0 && width > max_width) width = max_width; - if (min_width > 0 && width < min_width) width = min_width; - - } else { - if (max_width >= 0 && width > max_width) width = max_width; - if (min_width > 0 && width < min_width) width = min_width; - width -= scrollbar_width_y; - } - - box->width = width; - box->height = height; - - if (margin[TOP] == AUTO) - margin[TOP] = 0; - if (margin[BOTTOM] == AUTO) - margin[BOTTOM] = 0; -} - - -/** - * Layout the contents of a float or inline block. - * - * \param b float or inline block box - * \param width available width - * \param content memory pool for any new boxes - * \return true on success, false on memory exhaustion - */ -static bool layout_float(struct box *b, int width, html_content *content) -{ - assert(b->type == BOX_TABLE || b->type == BOX_BLOCK || - b->type == BOX_INLINE_BLOCK); - layout_float_find_dimensions(&content->len_ctx, width, b->style, b); - if (b->type == BOX_TABLE) { - if (!layout_table(b, width, content)) - return false; - if (b->margin[LEFT] == AUTO) - b->margin[LEFT] = 0; - if (b->margin[RIGHT] == AUTO) - b->margin[RIGHT] = 0; - if (b->margin[TOP] == AUTO) - b->margin[TOP] = 0; - if (b->margin[BOTTOM] == AUTO) - b->margin[BOTTOM] = 0; - } else - return layout_block_context(b, -1, content); - return true; -} - - -/** - * Position a float in the first available space. - * - * \param c float box to position - * \param width available width - * \param cx x coordinate relative to cont to place float right of - * \param y y coordinate relative to cont to place float below - * \param cont ancestor box which defines horizontal space, for floats - */ -static void -place_float_below(struct box *c, int width, int cx, int y, struct box *cont) -{ - int x0, x1, yy; - struct box *left; - struct box *right; - - yy = y > cont->cached_place_below_level ? - y : cont->cached_place_below_level; - - NSLOG(layout, DEBUG, - "c %p, width %i, cx %i, y %i, cont %p", c, - width, cx, y, cont); - - do { - y = yy; - x0 = cx; - x1 = cx + width; - find_sides(cont->float_children, y, y + c->height, &x0, &x1, - &left, &right); - if (left != 0 && right != 0) { - yy = (left->y + left->height < - right->y + right->height ? - left->y + left->height : - right->y + right->height); - } else if (left == 0 && right != 0) { - yy = right->y + right->height; - } else if (left != 0 && right == 0) { - yy = left->y + left->height; - } - } while ((left != 0 || right != 0) && (c->width > x1 - x0)); - - if (c->type == BOX_FLOAT_LEFT) { - c->x = x0; - } else { - c->x = x1 - c->width; - } - c->y = y; - cont->cached_place_below_level = y; -} - - -/** - * Calculate line height from a style. - */ -static int line_height( - const nscss_len_ctx *len_ctx, - const css_computed_style *style) -{ - enum css_line_height_e lhtype; - css_fixed lhvalue = 0; - css_unit lhunit = CSS_UNIT_PX; - css_fixed line_height; - - assert(style); - - lhtype = css_computed_line_height(style, &lhvalue, &lhunit); - if (lhtype == CSS_LINE_HEIGHT_NORMAL) { - /* Normal => use a constant of 1.3 * font-size */ - lhvalue = FLTTOFIX(1.3); - lhtype = CSS_LINE_HEIGHT_NUMBER; - } - - if (lhtype == CSS_LINE_HEIGHT_NUMBER || - lhunit == CSS_UNIT_PCT) { - line_height = nscss_len2px(len_ctx, - lhvalue, CSS_UNIT_EM, style); - - if (lhtype != CSS_LINE_HEIGHT_NUMBER) - line_height = FDIV(line_height, F_100); - } else { - assert(lhunit != CSS_UNIT_PCT); - - line_height = nscss_len2px(len_ctx, - lhvalue, lhunit, style); - } - - return FIXTOINT(line_height); -} - - -/** - * Position a line of boxes in inline formatting context. - * - * \param first box at start of line - * \param width available width on input, updated with actual width on output - * (may be incorrect if the line gets split?) - * \param y coordinate of top of line, updated on exit to bottom - * \param cx coordinate of left of line relative to cont - * \param cy coordinate of top of line relative to cont - * \param cont ancestor box which defines horizontal space, for floats - * \param indent apply any first-line indent - * \param has_text_children at least one TEXT in the inline_container - * \param next_box updated to first box for next line, or 0 at end - * \param content memory pool for any new boxes - * \return true on success, false on memory exhaustion - */ -static bool -layout_line(struct box *first, - int *width, - int *y, - int cx, - int cy, - struct box *cont, - bool indent, - bool has_text_children, - html_content *content, - struct box **next_box) -{ - int height, used_height; - int x0 = 0; - int x1 = *width; - int x, h, x_previous; - int fy = cy; - struct box *left; - struct box *right; - struct box *b; - struct box *split_box = 0; - struct box *d; - struct box *br_box = 0; - bool move_y = false; - bool place_below = false; - int space_before = 0, space_after = 0; - unsigned int inline_count = 0; - unsigned int i; - const struct gui_layout_table *font_func = content->font_func; - plot_font_style_t fstyle; - - NSLOG(layout, DEBUG, - "first %p, first->text '%.*s', width %i, y %i, cx %i, cy %i", - first, - (int)first->length, - first->text, - *width, - *y, - cx, - cy); - - /* find sides at top of line */ - x0 += cx; - x1 += cx; - find_sides(cont->float_children, cy, cy, &x0, &x1, &left, &right); - x0 -= cx; - x1 -= cx; - - if (indent) - x0 += layout_text_indent(&content->len_ctx, - first->parent->parent->style, *width); - - if (x1 < x0) - x1 = x0; - - /* get minimum line height from containing block. - * this is the line-height if there are text children and also in the - * case of an initially empty text input */ - if (has_text_children || first->parent->parent->gadget) - used_height = height = line_height(&content->len_ctx, - first->parent->parent->style); - else - /* inline containers with no text are usually for layout and - * look better with no minimum line-height */ - used_height = height = 0; - - /* pass 1: find height of line assuming sides at top of line: loop - * body executed at least once - * keep in sync with the loop in layout_minmax_line() */ - - NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0); - - - for (x = 0, b = first; x <= x1 - x0 && b != 0; b = b->next) { - int min_width, max_width, min_height, max_height; - - assert(b->type == BOX_INLINE || b->type == BOX_INLINE_BLOCK || - b->type == BOX_FLOAT_LEFT || - b->type == BOX_FLOAT_RIGHT || - b->type == BOX_BR || b->type == BOX_TEXT || - b->type == BOX_INLINE_END); - - - NSLOG(layout, DEBUG, "pass 1: b %p, x %i", b, x); - - - if (b->type == BOX_BR) - break; - - if (b->type == BOX_FLOAT_LEFT || b->type == BOX_FLOAT_RIGHT) - continue; - if (b->type == BOX_INLINE_BLOCK && - (css_computed_position(b->style) == - CSS_POSITION_ABSOLUTE || - css_computed_position(b->style) == - CSS_POSITION_FIXED)) - continue; - - assert(b->style != NULL); - font_plot_style_from_css(&content->len_ctx, b->style, &fstyle); - - x += space_after; - - if (b->type == BOX_INLINE_BLOCK) { - if (b->max_width != UNKNOWN_WIDTH) - if (!layout_float(b, *width, content)) - return false; - h = b->border[TOP].width + b->padding[TOP] + b->height + - b->padding[BOTTOM] + - b->border[BOTTOM].width; - if (height < h) - height = h; - x += b->margin[LEFT] + b->border[LEFT].width + - b->padding[LEFT] + b->width + - b->padding[RIGHT] + - b->border[RIGHT].width + - b->margin[RIGHT]; - space_after = 0; - continue; - } - - if (b->type == BOX_INLINE) { - /* calculate borders, margins, and padding */ - layout_find_dimensions(&content->len_ctx, - *width, -1, b, b->style, 0, 0, 0, 0, - 0, 0, b->margin, b->padding, b->border); - for (i = 0; i != 4; i++) - if (b->margin[i] == AUTO) - b->margin[i] = 0; - x += b->margin[LEFT] + b->border[LEFT].width + - b->padding[LEFT]; - if (b->inline_end) { - b->inline_end->margin[RIGHT] = b->margin[RIGHT]; - b->inline_end->padding[RIGHT] = - b->padding[RIGHT]; - b->inline_end->border[RIGHT] = - b->border[RIGHT]; - } else { - x += b->padding[RIGHT] + - b->border[RIGHT].width + - b->margin[RIGHT]; - } - } else if (b->type == BOX_INLINE_END) { - b->width = 0; - if (b->space == UNKNOWN_WIDTH) { - font_func->width(&fstyle, " ", 1, &b->space); - /** \todo handle errors */ - } - space_after = b->space; - - x += b->padding[RIGHT] + b->border[RIGHT].width + - b->margin[RIGHT]; - continue; - } - - if (!b->object && !(b->flags & IFRAME) && !b->gadget && - !(b->flags & REPLACE_DIM)) { - /* inline non-replaced, 10.3.1 and 10.6.1 */ - b->height = line_height(&content->len_ctx, - b->style ? b->style : - b->parent->parent->style); - if (height < b->height) - height = b->height; - - if (!b->text) { - b->width = 0; - space_after = 0; - continue; - } - - if (b->width == UNKNOWN_WIDTH) { - /** \todo handle errors */ - - /* If it's a select element, we must use the - * width of the widest option text */ - if (b->parent->parent->gadget && - b->parent->parent->gadget->type - == GADGET_SELECT) { - int opt_maxwidth = 0; - struct form_option *o; - - for (o = b->parent->parent->gadget-> - data.select.items; o; - o = o->next) { - int opt_width; - font_func->width(&fstyle, - o->text, - strlen(o->text), - &opt_width); - - if (opt_maxwidth < opt_width) - opt_maxwidth =opt_width; - } - b->width = opt_maxwidth; - if (nsoption_bool(core_select_menu)) - b->width += SCROLLBAR_WIDTH; - } else { - font_func->width(&fstyle, b->text, - b->length, &b->width); - b->flags |= MEASURED; - } - } - - /* If the current text has not been measured (i.e. its - * width was estimated after splitting), and it fits on - * the line, measure it properly, so next box is placed - * correctly. */ - if (b->text && (x + b->width < x1 - x0) && - !(b->flags & MEASURED) && - b->next) { - font_func->width(&fstyle, b->text, - b->length, &b->width); - b->flags |= MEASURED; - } - - x += b->width; - if (b->space == UNKNOWN_WIDTH) { - font_func->width(&fstyle, " ", 1, &b->space); - /** \todo handle errors */ - } - space_after = b->space; - continue; - } - - space_after = 0; - - /* inline replaced, 10.3.2 and 10.6.2 */ - assert(b->style); - - layout_find_dimensions(&content->len_ctx, - *width, -1, b, b->style, - &b->width, &b->height, - &max_width, &min_width, - &max_height, &min_height, - NULL, NULL, NULL); - - if (b->object && !(b->flags & REPLACE_DIM)) { - layout_get_object_dimensions(b, &b->width, &b->height, - min_width, max_width, - min_height, max_height); - } else if (b->flags & IFRAME) { - /* TODO: should we look at the content dimensions? */ - if (b->width == AUTO) - b->width = 400; - if (b->height == AUTO) - b->height = 300; - - /* We reformat the iframe browser window to new - * dimensions in pass 2 */ - } else { - /* form control with no object */ - if (b->width == AUTO) - b->width = FIXTOINT(nscss_len2px( - &content->len_ctx, INTTOFIX(1), - CSS_UNIT_EM, b->style)); - if (b->height == AUTO) - b->height = FIXTOINT(nscss_len2px( - &content->len_ctx, INTTOFIX(1), - CSS_UNIT_EM, b->style)); - } - - /* Reformat object to new box size */ - if (b->object && content_get_type(b->object) == CONTENT_HTML && - b->width != - content_get_available_width(b->object)) { - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - enum css_height_e htype = css_computed_height(b->style, - &value, &unit); - - content_reformat(b->object, false, b->width, b->height); - - if (htype == CSS_HEIGHT_AUTO) - b->height = content_get_height(b->object); - } - - if (height < b->height) - height = b->height; - - x += b->width; - } - - /* find new sides using this height */ - x0 = cx; - x1 = cx + *width; - find_sides(cont->float_children, cy, cy + height, &x0, &x1, - &left, &right); - x0 -= cx; - x1 -= cx; - - if (indent) - x0 += layout_text_indent(&content->len_ctx, - first->parent->parent->style, *width); - - if (x1 < x0) - x1 = x0; - - space_after = space_before = 0; - - /* pass 2: place boxes in line: loop body executed at least once */ - - NSLOG(layout, DEBUG, "x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0); - - for (x = x_previous = 0, b = first; x <= x1 - x0 && b; b = b->next) { - - NSLOG(layout, DEBUG, "pass 2: b %p, x %i", b, x); - - if (b->type == BOX_INLINE_BLOCK && - (css_computed_position(b->style) == - CSS_POSITION_ABSOLUTE || - css_computed_position(b->style) == - CSS_POSITION_FIXED)) { - b->x = x + space_after; - - } else if (b->type == BOX_INLINE || - b->type == BOX_INLINE_BLOCK || - b->type == BOX_TEXT || - b->type == BOX_INLINE_END) { - assert(b->width != UNKNOWN_WIDTH); - - x_previous = x; - x += space_after; - b->x = x; - - if ((b->type == BOX_INLINE && !b->inline_end) || - b->type == BOX_INLINE_BLOCK) { - b->x += b->margin[LEFT] + b->border[LEFT].width; - x = b->x + b->padding[LEFT] + b->width + - b->padding[RIGHT] + - b->border[RIGHT].width + - b->margin[RIGHT]; - } else if (b->type == BOX_INLINE) { - b->x += b->margin[LEFT] + b->border[LEFT].width; - x = b->x + b->padding[LEFT] + b->width; - } else if (b->type == BOX_INLINE_END) { - b->height = b->inline_end->height; - x += b->padding[RIGHT] + - b->border[RIGHT].width + - b->margin[RIGHT]; - } else { - x += b->width; - } - - space_before = space_after; - if (b->object || b->flags & REPLACE_DIM || - b->flags & IFRAME) - space_after = 0; - else if (b->text || b->type == BOX_INLINE_END) { - if (b->space == UNKNOWN_WIDTH) { - font_plot_style_from_css( - &content->len_ctx, - b->style, &fstyle); - /** \todo handle errors */ - font_func->width(&fstyle, " ", 1, - &b->space); - } - space_after = b->space; - } else { - space_after = 0; - } - split_box = b; - move_y = true; - inline_count++; - } else if (b->type == BOX_BR) { - b->x = x; - b->width = 0; - br_box = b; - b = b->next; - split_box = 0; - move_y = true; - break; - - } else { - /* float */ - NSLOG(layout, DEBUG, "float %p", b); - - d = b->children; - d->float_children = 0; - d->cached_place_below_level = 0; - b->float_container = d->float_container = cont; - - if (!layout_float(d, *width, content)) - return false; - - NSLOG(layout, DEBUG, - "%p : %d %d", - d, - d->margin[TOP], - d->border[TOP].width); - - d->x = d->margin[LEFT] + d->border[LEFT].width; - d->y = d->margin[TOP] + d->border[TOP].width; - b->width = d->margin[LEFT] + d->border[LEFT].width + - d->padding[LEFT] + d->width + - d->padding[RIGHT] + - d->border[RIGHT].width + - d->margin[RIGHT]; - b->height = d->margin[TOP] + d->border[TOP].width + - d->padding[TOP] + d->height + - d->padding[BOTTOM] + - d->border[BOTTOM].width + - d->margin[BOTTOM]; - - if (b->width > (x1 - x0) - x) - place_below = true; - if (d->style && (css_computed_clear(d->style) == - CSS_CLEAR_NONE || - (css_computed_clear(d->style) == - CSS_CLEAR_LEFT && left == 0) || - (css_computed_clear(d->style) == - CSS_CLEAR_RIGHT && - right == 0) || - (css_computed_clear(d->style) == - CSS_CLEAR_BOTH && - left == 0 && right == 0)) && - (!place_below || - (left == 0 && right == 0 && x == 0)) && - cy >= cont->clear_level && - cy >= cont->cached_place_below_level) { - /* + not cleared or, - * cleared and there are no floats to clear - * + fits without needing to be placed below or, - * this line is empty with no floats - * + current y, cy, is below the clear level - * - * Float affects current line */ - if (b->type == BOX_FLOAT_LEFT) { - b->x = cx + x0; - if (b->width > 0) - x0 += b->width; - left = b; - } else { - b->x = cx + x1 - b->width; - if (b->width > 0) - x1 -= b->width; - right = b; - } - b->y = cy; - } else { - /* cleared or doesn't fit on line */ - /* place below into next available space */ - int fcy = (cy > cont->clear_level) ? cy : - cont->clear_level; - fcy = (fcy > cont->cached_place_below_level) ? - fcy : - cont->cached_place_below_level; - fy = (fy > fcy) ? fy : fcy; - fy = (fy == cy) ? fy + height : fy; - - place_float_below(b, *width, cx, fy, cont); - fy = b->y; - if (d->style && ( - (css_computed_clear(d->style) == - CSS_CLEAR_LEFT && left != 0) || - (css_computed_clear(d->style) == - CSS_CLEAR_RIGHT && - right != 0) || - (css_computed_clear(d->style) == - CSS_CLEAR_BOTH && - (left != 0 || right != 0)))) { - /* to be cleared below existing - * floats */ - if (b->type == BOX_FLOAT_LEFT) - b->x = cx; - else - b->x = cx + *width - b->width; - - fcy = layout_clear(cont->float_children, - css_computed_clear(d->style)); - if (fcy > cont->clear_level) - cont->clear_level = fcy; - if (b->y < fcy) - b->y = fcy; - } - if (b->type == BOX_FLOAT_LEFT) - left = b; - else - right = b; - } - add_float_to_container(cont, b); - - split_box = 0; - } - } - - if (x1 - x0 < x && split_box) { - /* the last box went over the end */ - size_t split = 0; - int w; - bool no_wrap = css_computed_white_space( - split_box->style) == CSS_WHITE_SPACE_NOWRAP || - css_computed_white_space( - split_box->style) == CSS_WHITE_SPACE_PRE; - - x = x_previous; - - if (!no_wrap && - (split_box->type == BOX_INLINE || - split_box->type == BOX_TEXT) && - !split_box->object && - !(split_box->flags & REPLACE_DIM) && - !(split_box->flags & IFRAME) && - !split_box->gadget && split_box->text) { - - font_plot_style_from_css(&content->len_ctx, - split_box->style, &fstyle); - /** \todo handle errors */ - font_func->split(&fstyle, - split_box->text, - split_box->length, - x1 - x0 - x - space_before, - &split, - &w); - } - - /* split == 0 implies that text can't be split */ - - if (split == 0) - w = split_box->width; - - - NSLOG(layout, DEBUG, - "splitting: split_box %p \"%.*s\", spilt %zu, w %i, " - "left %p, right %p, inline_count %u", - split_box, - (int)split_box->length, - split_box->text, - split, - w, - left, - right, - inline_count); - - if ((split == 0 || x1 - x0 <= x + space_before + w) && - !left && !right && inline_count == 1) { - /* first word of box doesn't fit, but no floats and - * first box on line so force in */ - if (split == 0 || split == split_box->length) { - /* only one word in this box, or not text - * or white-space:nowrap */ - b = split_box->next; - } else { - /* cut off first word for this line */ - if (!layout_text_box_split(content, &fstyle, - split_box, split, w)) - return false; - b = split_box->next; - } - x += space_before + w; - - NSLOG(layout, DEBUG, "forcing"); - - } else if ((split == 0 || x1 - x0 <= x + space_before + w) && - inline_count == 1) { - /* first word of first box doesn't fit, but a float is - * taking some of the width so move below it */ - assert(left || right); - used_height = 0; - if (left) { - - NSLOG(layout, DEBUG, - "cy %i, left->y %i, left->height %i", - cy, - left->y, - left->height); - - used_height = left->y + left->height - cy + 1; - - NSLOG(layout, DEBUG, "used_height %i", - used_height); - - } - if (right && used_height < - right->y + right->height - cy + 1) - used_height = right->y + right->height - cy + 1; - - if (used_height < 0) - used_height = 0; - - b = split_box; - - NSLOG(layout, DEBUG, "moving below float"); - - } else if (split == 0 || x1 - x0 <= x + space_before + w) { - /* first word of box doesn't fit so leave box for next - * line */ - b = split_box; - - NSLOG(layout, DEBUG, "leaving for next line"); - - } else { - /* fit as many words as possible */ - assert(split != 0); - - NSLOG(layout, DEBUG, "'%.*s' %i %zu %i", - (int)split_box->length, split_box->text, - x1 - x0, split, w); - - if (split != split_box->length) { - if (!layout_text_box_split(content, &fstyle, - split_box, split, w)) - return false; - b = split_box->next; - } - x += space_before + w; - - NSLOG(layout, DEBUG, "fitting words"); - - } - move_y = true; - } - - /* set positions */ - switch (css_computed_text_align(first->parent->parent->style)) { - case CSS_TEXT_ALIGN_RIGHT: - case CSS_TEXT_ALIGN_LIBCSS_RIGHT: - x0 = x1 - x; - break; - case CSS_TEXT_ALIGN_CENTER: - case CSS_TEXT_ALIGN_LIBCSS_CENTER: - x0 = (x0 + (x1 - x)) / 2; - break; - case CSS_TEXT_ALIGN_LEFT: - case CSS_TEXT_ALIGN_LIBCSS_LEFT: - case CSS_TEXT_ALIGN_JUSTIFY: - /* leave on left */ - break; - case CSS_TEXT_ALIGN_DEFAULT: - /* None; consider text direction */ - switch (css_computed_direction(first->parent->parent->style)) { - case CSS_DIRECTION_LTR: - /* leave on left */ - break; - case CSS_DIRECTION_RTL: - x0 = x1 - x; - break; - } - break; - } - - for (d = first; d != b; d = d->next) { - d->flags &= ~NEW_LINE; - - if (d->type == BOX_INLINE_BLOCK && - (css_computed_position(d->style) == - CSS_POSITION_ABSOLUTE || - css_computed_position(d->style) == - CSS_POSITION_FIXED)) { - /* positioned inline-blocks: - * set static position (x,y) only, rest of positioning - * is handled later */ - d->x += x0; - d->y = *y; - continue; - } else if ((d->type == BOX_INLINE && - ((d->object || d->gadget) == false) && - !(d->flags & IFRAME) && - !(d->flags & REPLACE_DIM)) || - d->type == BOX_BR || - d->type == BOX_TEXT || - d->type == BOX_INLINE_END) { - /* regular (non-replaced) inlines */ - d->x += x0; - d->y = *y - d->padding[TOP]; - - if (d->type == BOX_TEXT && d->height > used_height) { - /* text */ - used_height = d->height; - } - } else if ((d->type == BOX_INLINE) || - d->type == BOX_INLINE_BLOCK) { - /* replaced inlines and inline-blocks */ - d->x += x0; - d->y = *y + d->border[TOP].width + d->margin[TOP]; - h = d->margin[TOP] + d->border[TOP].width + - d->padding[TOP] + d->height + - d->padding[BOTTOM] + - d->border[BOTTOM].width + - d->margin[BOTTOM]; - if (used_height < h) - used_height = h; - } - } - - first->flags |= NEW_LINE; - - assert(b != first || (move_y && 0 < used_height && (left || right))); - - /* handle vertical-align by adjusting box y values */ - /** \todo proper vertical alignment handling */ - for (d = first; d != b; d = d->next) { - if ((d->type == BOX_INLINE && d->inline_end) || - d->type == BOX_BR || - d->type == BOX_TEXT || - d->type == BOX_INLINE_END) { - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - switch (css_computed_vertical_align(d->style, &value, - &unit)) { - case CSS_VERTICAL_ALIGN_SUPER: - case CSS_VERTICAL_ALIGN_TOP: - case CSS_VERTICAL_ALIGN_TEXT_TOP: - /* already at top */ - break; - case CSS_VERTICAL_ALIGN_SUB: - case CSS_VERTICAL_ALIGN_BOTTOM: - case CSS_VERTICAL_ALIGN_TEXT_BOTTOM: - d->y += used_height - d->height; - break; - default: - case CSS_VERTICAL_ALIGN_BASELINE: - d->y += 0.75 * (used_height - d->height); - break; - } - } - } - - /* handle clearance for br */ - if (br_box && css_computed_clear(br_box->style) != CSS_CLEAR_NONE) { - int clear_y = layout_clear(cont->float_children, - css_computed_clear(br_box->style)); - if (used_height < clear_y - cy) - used_height = clear_y - cy; - } - - if (move_y) - *y += used_height; - *next_box = b; - *width = x; /* return actual width */ - return true; -} - - -/** - * Layout lines of text or inline boxes with floats. - * - * \param box inline container box - * \param width horizontal space available - * \param cont ancestor box which defines horizontal space, for floats - * \param cx box position relative to cont - * \param cy box position relative to cont - * \param content memory pool for any new boxes - * \return true on success, false on memory exhaustion - */ -static bool layout_inline_container(struct box *inline_container, int width, - struct box *cont, int cx, int cy, html_content *content) -{ - bool first_line = true; - bool has_text_children; - struct box *c, *next; - int y = 0; - int curwidth,maxwidth = width; - - assert(inline_container->type == BOX_INLINE_CONTAINER); - - NSLOG(layout, DEBUG, - "inline_container %p, width %i, cont %p, cx %i, cy %i", - inline_container, - width, - cont, - cx, - cy); - - - has_text_children = false; - for (c = inline_container->children; c; c = c->next) { - bool is_pre = false; - - if (c->style) { - enum css_white_space_e whitespace; - - whitespace = css_computed_white_space(c->style); - - is_pre = (whitespace == CSS_WHITE_SPACE_PRE || - whitespace == CSS_WHITE_SPACE_PRE_LINE || - whitespace == CSS_WHITE_SPACE_PRE_WRAP); - } - - if ((!c->object && !(c->flags & REPLACE_DIM) && - !(c->flags & IFRAME) && - c->text && (c->length || is_pre)) || - c->type == BOX_BR) - has_text_children = true; - } - - /** \todo fix wrapping so that a box with horizontal scrollbar will - * shrink back to 'width' if no word is wider than 'width' (Or just set - * curwidth = width and have the multiword lines wrap to the min width) - */ - for (c = inline_container->children; c; ) { - - NSLOG(layout, DEBUG, "c %p", c); - - curwidth = inline_container->width; - if (!layout_line(c, &curwidth, &y, cx, cy + y, cont, first_line, - has_text_children, content, &next)) - return false; - maxwidth = max(maxwidth,curwidth); - c = next; - first_line = false; - } - - inline_container->width = maxwidth; - inline_container->height = y; - - return true; -} - - -/** - * Layout a block formatting context. - * - * \param block BLOCK, INLINE_BLOCK, or TABLE_CELL to layout - * \param viewport_height Height of viewport in pixels or -ve if unknown - * \param content Memory pool for any new boxes - * \return true on success, false on memory exhaustion - * - * This function carries out layout of a block and its children, as described - * in CSS 2.1 9.4.1. - */ -static bool -layout_block_context(struct box *block, - int viewport_height, - html_content *content) -{ - struct box *box; - int cx, cy; /**< current coordinates */ - int max_pos_margin = 0; - int max_neg_margin = 0; - int y = 0; - int lm, rm; - struct box *margin_collapse = NULL; - bool in_margin = false; - css_fixed gadget_size; - css_unit gadget_unit; /* Checkbox / radio buttons */ - - assert(block->type == BOX_BLOCK || - block->type == BOX_INLINE_BLOCK || - block->type == BOX_TABLE_CELL); - assert(block->width != UNKNOWN_WIDTH); - assert(block->width != AUTO); - - block->float_children = NULL; - block->cached_place_below_level = 0; - block->clear_level = 0; - - /* special case if the block contains an object */ - if (block->object) { - int temp_width = block->width; - if (!layout_block_object(block)) - return false; - layout_get_object_dimensions(block, &temp_width, - &block->height, INT_MIN, INT_MAX, - INT_MIN, INT_MAX); - return true; - } else if (block->flags & REPLACE_DIM) { - return true; - } - - /* special case if the block contains an radio button or checkbox */ - if (block->gadget && (block->gadget->type == GADGET_RADIO || - block->gadget->type == GADGET_CHECKBOX)) { - /* form checkbox or radio button - * if width or height is AUTO, set it to 1em */ - gadget_unit = CSS_UNIT_EM; - gadget_size = INTTOFIX(1); - if (block->height == AUTO) - block->height = FIXTOINT(nscss_len2px( - &content->len_ctx, gadget_size, - gadget_unit, block->style)); - } - - box = block->children; - /* set current coordinates to top-left of the block */ - cx = 0; - y = cy = block->padding[TOP]; - if (box) - box->y = block->padding[TOP]; - - /* Step through the descendants of the block in depth-first order, but - * not into the children of boxes which aren't blocks. For example, if - * the tree passed to this function looks like this (box->type shown): - * - * block -> BOX_BLOCK - * BOX_BLOCK * (1) - * BOX_INLINE_CONTAINER * (2) - * BOX_INLINE - * BOX_TEXT - * ... - * BOX_BLOCK * (3) - * BOX_TABLE * (4) - * BOX_TABLE_ROW - * BOX_TABLE_CELL - * ... - * BOX_TABLE_CELL - * ... - * BOX_BLOCK * (5) - * BOX_INLINE_CONTAINER * (6) - * BOX_TEXT - * ... - * then the while loop will visit each box marked with *, setting box - * to each in the order shown. */ - while (box) { - enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; - enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; - - assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || - box->type == BOX_INLINE_CONTAINER); - - /* Tables are laid out before being positioned, because the - * position depends on the width which is calculated in - * table layout. Blocks and inline containers are positioned - * before being laid out, because width is not dependent on - * content, and the position is required during layout for - * correct handling of floats. - */ - - if (box->style && - (css_computed_position(box->style) == - CSS_POSITION_ABSOLUTE || - css_computed_position(box->style) == - CSS_POSITION_FIXED)) { - box->x = box->parent->padding[LEFT]; - /* absolute positioned; this element will establish - * its own block context when it gets laid out later, - * so no need to look at its children now. */ - goto advance_to_next_box; - } - - /* If we don't know which box the current margin collapses - * through to, find out. Update the pos/neg margin values. */ - if (margin_collapse == NULL) { - margin_collapse = layout_next_margin_block( - &content->len_ctx, box, block, - viewport_height, - &max_pos_margin, &max_neg_margin); - /* We have a margin that has not yet been applied. */ - in_margin = true; - } - - /* Clearance. */ - y = 0; - if (box->style && css_computed_clear(box->style) != - CSS_CLEAR_NONE) - y = layout_clear(block->float_children, - css_computed_clear(box->style)); - - /* Find box's overflow properties */ - if (box->style) { - overflow_x = css_computed_overflow_x(box->style); - overflow_y = css_computed_overflow_y(box->style); - } - - /* Blocks establishing a block formatting context get minimum - * left and right margins to avoid any floats. */ - lm = rm = 0; - - if (box->type == BOX_BLOCK || box->flags & IFRAME) { - if (!box->object && !(box->flags & IFRAME) && - !(box->flags & REPLACE_DIM) && - box->style && - (overflow_x != CSS_OVERFLOW_VISIBLE || - overflow_y != CSS_OVERFLOW_VISIBLE)) { - /* box establishes new block formatting context - * so available width may be diminished due to - * floats. */ - int x0, x1, top; - struct box *left, *right; - top = cy + max_pos_margin - max_neg_margin; - top = (top > y) ? top : y; - x0 = cx; - x1 = cx + box->parent->width - - box->parent->padding[LEFT] - - box->parent->padding[RIGHT]; - find_sides(block->float_children, top, top, - &x0, &x1, &left, &right); - /* calculate min required left & right margins - * needed to avoid floats */ - lm = x0 - cx; - rm = cx + box->parent->width - - box->parent->padding[LEFT] - - box->parent->padding[RIGHT] - - x1; - } - layout_block_find_dimensions(&content->len_ctx, - box->parent->width, - viewport_height, lm, rm, box); - if (box->type == BOX_BLOCK && !(box->flags & IFRAME)) { - layout_block_add_scrollbar(box, RIGHT); - layout_block_add_scrollbar(box, BOTTOM); - } - } else if (box->type == BOX_TABLE) { - if (box->style != NULL) { - enum css_width_e wtype; - css_fixed width = 0; - css_unit unit = CSS_UNIT_PX; - - wtype = css_computed_width(box->style, &width, - &unit); - - if (wtype == CSS_WIDTH_AUTO) { - /* max available width may be - * diminished due to floats. */ - int x0, x1, top; - struct box *left, *right; - top = cy + max_pos_margin - - max_neg_margin; - top = (top > y) ? top : y; - x0 = cx; - x1 = cx + box->parent->width - - box->parent->padding[LEFT] - - box->parent->padding[RIGHT]; - find_sides(block->float_children, - top, top, &x0, &x1, - &left, &right); - /* calculate min required left & right - * margins needed to avoid floats */ - lm = x0 - cx; - rm = cx + box->parent->width - - box->parent->padding[LEFT] - - box->parent->padding[RIGHT] - - x1; - } - } - if (!layout_table(box, box->parent->width - lm - rm, - content)) - return false; - layout_solve_width(box, box->parent->width, box->width, - lm, rm, -1, -1); - } - - /* Position box: horizontal. */ - box->x = box->parent->padding[LEFT] + box->margin[LEFT] + - box->border[LEFT].width; - cx += box->x; - - /* Position box: vertical. */ - if (box->border[TOP].width) { - box->y += box->border[TOP].width; - cy += box->border[TOP].width; - } - - /* Vertical margin */ - if (((box->type == BOX_BLOCK && - (box->flags & HAS_HEIGHT)) || - box->type == BOX_TABLE || - (box->type == BOX_INLINE_CONTAINER && - box != box->parent->children) || - margin_collapse == box) && - in_margin == true) { - /* Margin goes above this box. */ - cy += max_pos_margin - max_neg_margin; - box->y += max_pos_margin - max_neg_margin; - - /* Current margin has been applied. */ - in_margin = false; - max_pos_margin = max_neg_margin = 0; - } - - /* Handle clearance */ - if (box->type != BOX_INLINE_CONTAINER && - (y > 0) && (cy < y)) { - /* box clears something*/ - box->y += y - cy; - cy = y; - } - - /* Unless the box has an overflow style of visible, the box - * establishes a new block context. */ - if (box->type == BOX_BLOCK && box->style && - (overflow_x != CSS_OVERFLOW_VISIBLE || - overflow_y != CSS_OVERFLOW_VISIBLE)) { - - layout_block_context(box, viewport_height, content); - - cy += box->padding[TOP]; - - if (box->height == AUTO) { - box->height = 0; - layout_block_add_scrollbar(box, BOTTOM); - } - - cx -= box->x; - cy += box->height + box->padding[BOTTOM] + - box->border[BOTTOM].width; - y = box->y + box->padding[TOP] + box->height + - box->padding[BOTTOM] + - box->border[BOTTOM].width; - - /* Skip children, because they are done in the new - * block context */ - goto advance_to_next_box; - } - - NSLOG(layout, DEBUG, "box %p, cx %i, cy %i", box, cx, cy); - - /* Layout (except tables). */ - if (box->object) { - if (!layout_block_object(box)) - return false; - - } else if (box->type == BOX_INLINE_CONTAINER) { - box->width = box->parent->width; - if (!layout_inline_container(box, box->width, block, - cx, cy, content)) - return false; - - } else if (box->type == BOX_TABLE) { - /* Move down to avoid floats if necessary. */ - int x0, x1; - struct box *left, *right; - y = cy; - while (1) { - enum css_width_e wtype; - css_fixed width = 0; - css_unit unit = CSS_UNIT_PX; - - wtype = css_computed_width(box->style, - &width, &unit); - - x0 = cx; - x1 = cx + box->parent->width; - find_sides(block->float_children, y, - y + box->height, - &x0, &x1, &left, &right); - if (wtype == CSS_WIDTH_AUTO) - break; - if (box->width <= x1 - x0) - break; - if (!left && !right) - break; - else if (!left) - y = right->y + right->height + 1; - else if (!right) - y = left->y + left->height + 1; - else if (left->y + left->height < - right->y + right->height) - y = left->y + left->height + 1; - else - y = right->y + right->height + 1; - } - box->x += x0 - cx; - cx = x0; - box->y += y - cy; - cy = y; - } - - /* Advance to next box. */ - if (box->type == BOX_BLOCK && !box->object && !(box->iframe) && - box->children) { - /* Down into children. */ - - if (box == margin_collapse) { - /* Current margin collapsed though to this box. - * Unset margin_collapse. */ - margin_collapse = NULL; - } - - y = box->padding[TOP]; - box = box->children; - box->y = y; - cy += y; - continue; - } else if (box->type == BOX_BLOCK || box->object || - box->flags & IFRAME) - cy += box->padding[TOP]; - - if (box->type == BOX_BLOCK && box->height == AUTO) { - box->height = 0; - layout_block_add_scrollbar(box, BOTTOM); - } - - cy += box->height + box->padding[BOTTOM] + - box->border[BOTTOM].width; - cx -= box->x; - y = box->y + box->padding[TOP] + box->height + - box->padding[BOTTOM] + - box->border[BOTTOM].width; - - advance_to_next_box: - if (!box->next) { - /* No more siblings: - * up to first ancestor with a sibling. */ - - do { - if (box == margin_collapse) { - /* Current margin collapsed though to - * this box. Unset margin_collapse. */ - margin_collapse = NULL; - } - - /* Apply bottom margin */ - if (max_pos_margin < box->margin[BOTTOM]) - max_pos_margin = box->margin[BOTTOM]; - else if (max_neg_margin < -box->margin[BOTTOM]) - max_neg_margin = -box->margin[BOTTOM]; - - box = box->parent; - if (box == block) - break; - - /* Margin is invalidated if this is a box - * margins can't collapse through. */ - if (box->type == BOX_BLOCK && - box->flags & MAKE_HEIGHT) { - margin_collapse = NULL; - in_margin = false; - max_pos_margin = max_neg_margin = 0; - } - - if (box->height == AUTO) { - box->height = y - box->padding[TOP]; - - if (box->type == BOX_BLOCK) - layout_block_add_scrollbar(box, - BOTTOM); - } else - cy += box->height - - (y - box->padding[TOP]); - - /* Apply any min-height and max-height to - * boxes in normal flow */ - if (box->style && - css_computed_position(box->style) != - CSS_POSITION_ABSOLUTE && - layout_apply_minmax_height( - &content->len_ctx, - box, NULL)) { - /* Height altered */ - /* Set current cy */ - cy += box->height - - (y - box->padding[TOP]); - } - - cy += box->padding[BOTTOM] + - box->border[BOTTOM].width; - cx -= box->x; - y = box->y + box->padding[TOP] + box->height + - box->padding[BOTTOM] + - box->border[BOTTOM].width; - - } while (box->next == NULL); - if (box == block) - break; - } - - /* To next sibling. */ - - if (box == margin_collapse) { - /* Current margin collapsed though to this box. - * Unset margin_collapse. */ - margin_collapse = NULL; - } - - if (max_pos_margin < box->margin[BOTTOM]) - max_pos_margin = box->margin[BOTTOM]; - else if (max_neg_margin < -box->margin[BOTTOM]) - max_neg_margin = -box->margin[BOTTOM]; - - box = box->next; - box->y = y; - } - - /* Account for bottom margin of last contained block */ - cy += max_pos_margin - max_neg_margin; - - /* Increase height to contain any floats inside (CSS 2.1 10.6.7). */ - for (box = block->float_children; box; box = box->next_float) { - y = box->y + box->height + box->padding[BOTTOM] + - box->border[BOTTOM].width + box->margin[BOTTOM]; - if (cy < y) - cy = y; - } - - if (block->height == AUTO) { - block->height = cy - block->padding[TOP]; - if (block->type == BOX_BLOCK) - layout_block_add_scrollbar(block, BOTTOM); - } - - if (block->style && css_computed_position(block->style) != - CSS_POSITION_ABSOLUTE) { - /* Block is in normal flow */ - layout_apply_minmax_height(&content->len_ctx, block, NULL); - } - - if (block->gadget && - (block->gadget->type == GADGET_TEXTAREA || - block->gadget->type == GADGET_PASSWORD || - block->gadget->type == GADGET_TEXTBOX)) { - plot_font_style_t fstyle; - int ta_width = block->padding[LEFT] + block->width + - block->padding[RIGHT]; - int ta_height = block->padding[TOP] + block->height + - block->padding[BOTTOM]; - font_plot_style_from_css(&content->len_ctx, - block->style, &fstyle); - fstyle.background = NS_TRANSPARENT; - textarea_set_layout(block->gadget->data.text.ta, - &fstyle, ta_width, ta_height, - block->padding[TOP], block->padding[RIGHT], - block->padding[BOTTOM], block->padding[LEFT]); - } - - return true; -} - - -/** - * Layout list markers. - */ -static void -layout_lists(struct box *box, - const struct gui_layout_table *font_func, - const nscss_len_ctx *len_ctx) -{ - struct box *child; - struct box *marker; - plot_font_style_t fstyle; - - for (child = box->children; child; child = child->next) { - if (child->list_marker) { - marker = child->list_marker; - if (marker->object) { - marker->width = - content_get_width(marker->object); - marker->x = -marker->width; - marker->height = - content_get_height(marker->object); - marker->y = (line_height(len_ctx, - marker->style) - - marker->height) / 2; - } else if (marker->text) { - if (marker->width == UNKNOWN_WIDTH) { - font_plot_style_from_css(len_ctx, - marker->style, &fstyle); - font_func->width(&fstyle, - marker->text, - marker->length, - &marker->width); - marker->flags |= MEASURED; - } - marker->x = -marker->width; - marker->y = 0; - marker->height = line_height(len_ctx, - marker->style); - } else { - marker->x = 0; - marker->y = 0; - marker->width = 0; - marker->height = 0; - } - /* Gap between marker and content */ - marker->x -= 4; - } - layout_lists(child, font_func, len_ctx); - } -} - - -/** - * Compute box offsets for a relatively or absolutely positioned box with - * respect to a box. - * - * \param len_ctx Length conversion context - * \param box box to compute offsets for - * \param containing_block box to compute percentages with respect to - * \param top updated to top offset, or AUTO - * \param right updated to right offset, or AUTO - * \param bottom updated to bottom offset, or AUTO - * \param left updated to left offset, or AUTO - * - * See CSS 2.1 9.3.2. containing_block must have width and height. - */ -static void -layout_compute_offsets(const nscss_len_ctx *len_ctx, - struct box *box, - struct box *containing_block, - int *top, - int *right, - int *bottom, - int *left) -{ - uint32_t type; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - assert(containing_block->width != UNKNOWN_WIDTH && - containing_block->width != AUTO && - containing_block->height != AUTO); - - /* left */ - type = css_computed_left(box->style, &value, &unit); - if (type == CSS_LEFT_SET) { - if (unit == CSS_UNIT_PCT) { - *left = FPCT_OF_INT_TOINT(value, - containing_block->width); - } else { - *left = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); - } - } else { - *left = AUTO; - } - - /* right */ - type = css_computed_right(box->style, &value, &unit); - if (type == CSS_RIGHT_SET) { - if (unit == CSS_UNIT_PCT) { - *right = FPCT_OF_INT_TOINT(value, - containing_block->width); - } else { - *right = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); - } - } else { - *right = AUTO; - } - - /* top */ - type = css_computed_top(box->style, &value, &unit); - if (type == CSS_TOP_SET) { - if (unit == CSS_UNIT_PCT) { - *top = FPCT_OF_INT_TOINT(value, - containing_block->height); - } else { - *top = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); - } - } else { - *top = AUTO; - } - - /* bottom */ - type = css_computed_bottom(box->style, &value, &unit); - if (type == CSS_BOTTOM_SET) { - if (unit == CSS_UNIT_PCT) { - *bottom = FPCT_OF_INT_TOINT(value, - containing_block->height); - } else { - *bottom = FIXTOINT(nscss_len2px(len_ctx, - value, unit, box->style)); - } - } else { - *bottom = AUTO; - } -} - - -/** - * Layout and position an absolutely positioned box. - * - * \param box absolute box to layout and position - * \param containing_block containing block - * \param cx position of box relative to containing_block - * \param cy position of box relative to containing_block - * \param content memory pool for any new boxes - * \return true on success, false on memory exhaustion - */ -static bool -layout_absolute(struct box *box, - struct box *containing_block, - int cx, int cy, - html_content *content) -{ - int static_left, static_top; /* static position */ - int top, right, bottom, left; - int width, height, max_width, min_width; - int *margin = box->margin; - int *padding = box->padding; - struct box_border *border = box->border; - int available_width = containing_block->width; - int space; - - assert(box->type == BOX_BLOCK || box->type == BOX_TABLE || - box->type == BOX_INLINE_BLOCK); - - /* The static position is where the box would be if it was not - * absolutely positioned. The x and y are filled in by - * layout_block_context(). */ - static_left = cx + box->x; - static_top = cy + box->y; - - if (containing_block->type == BOX_BLOCK || - containing_block->type == BOX_INLINE_BLOCK || - containing_block->type == BOX_TABLE_CELL) { - /* Block level container => temporarily increase containing - * block dimensions to include padding (we restore this - * again at the end) */ - containing_block->width += containing_block->padding[LEFT] + - containing_block->padding[RIGHT]; - containing_block->height += containing_block->padding[TOP] + - containing_block->padding[BOTTOM]; - } else { - /** \todo inline containers */ - } - - layout_compute_offsets(&content->len_ctx, box, containing_block, - &top, &right, &bottom, &left); - - /* Pass containing block into layout_find_dimensions via the float - * containing block box member. This is unused for absolutely positioned - * boxes because a box can't be floated and absolutely positioned. */ - box->float_container = containing_block; - layout_find_dimensions(&content->len_ctx, available_width, -1, - box, box->style, &width, &height, - &max_width, &min_width, 0, 0, - margin, padding, border); - box->float_container = NULL; - - /* 10.3.7 */ - NSLOG(layout, DEBUG, - "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", - left, margin[LEFT], border[LEFT].width, padding[LEFT], width, - padding[RIGHT], border[RIGHT].width, margin[RIGHT], right, - containing_block->width); - - - if (left == AUTO && width == AUTO && right == AUTO) { - if (margin[LEFT] == AUTO) - margin[LEFT] = 0; - if (margin[RIGHT] == AUTO) - margin[RIGHT] = 0; - left = static_left; - - width = min(max(box->min_width, available_width), - box->max_width); - width -= box->margin[LEFT] + box->border[LEFT].width + - box->padding[LEFT] + box->padding[RIGHT] + - box->border[RIGHT].width + box->margin[RIGHT]; - - /* Adjust for {min|max}-width */ - if (max_width >= 0 && width > max_width) width = max_width; - if (width < min_width) width = min_width; - - right = containing_block->width - - left - - margin[LEFT] - border[LEFT].width - padding[LEFT] - - width - - padding[RIGHT] - border[RIGHT].width - margin[RIGHT]; - } else if (left != AUTO && width != AUTO && right != AUTO) { - - /* Adjust for {min|max}-width */ - if (max_width >= 0 && width > max_width) width = max_width; - if (min_width > 0 && width < min_width) width = min_width; - - if (margin[LEFT] == AUTO && margin[RIGHT] == AUTO) { - space = containing_block->width - - left - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - right; - if (space < 0) { - margin[LEFT] = 0; - margin[RIGHT] = space; - } else { - margin[LEFT] = margin[RIGHT] = space / 2; - } - } else if (margin[LEFT] == AUTO) { - margin[LEFT] = containing_block->width - - left - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - margin[RIGHT] - - right; - } else if (margin[RIGHT] == AUTO) { - margin[RIGHT] = containing_block->width - - left - margin[LEFT] - - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - right; - } else { - right = containing_block->width - - left - margin[LEFT] - - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - margin[RIGHT]; - } - } else { - if (margin[LEFT] == AUTO) - margin[LEFT] = 0; - if (margin[RIGHT] == AUTO) - margin[RIGHT] = 0; - - if (left == AUTO && width == AUTO && right != AUTO) { - available_width -= right; - - width = min(max(box->min_width, available_width), - box->max_width); - width -= box->margin[LEFT] + box->border[LEFT].width + - box->padding[LEFT] + box->padding[RIGHT] + - box->border[RIGHT].width + box->margin[RIGHT]; - - /* Adjust for {min|max}-width */ - if (max_width >= 0 && width > max_width) - width = max_width; - if (width < min_width) - width = min_width; - - left = containing_block->width - - margin[LEFT] - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - margin[RIGHT] - - right; - } else if (left == AUTO && width != AUTO && right == AUTO) { - - /* Adjust for {min|max}-width */ - if (max_width >= 0 && width > max_width) - width = max_width; - if (min_width > 0 && width < min_width) - width = min_width; - - left = static_left; - right = containing_block->width - - left - margin[LEFT] - - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - margin[RIGHT]; - } else if (left != AUTO && width == AUTO && right == AUTO) { - available_width -= left; - - width = min(max(box->min_width, available_width), - box->max_width); - width -= box->margin[LEFT] + box->border[LEFT].width + - box->padding[LEFT] + box->padding[RIGHT] + - box->border[RIGHT].width + box->margin[RIGHT]; - - /* Adjust for {min|max}-width */ - if (max_width >= 0 && width > max_width) - width = max_width; - if (width < min_width) - width = min_width; - - right = containing_block->width - - left - margin[LEFT] - - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - margin[RIGHT]; - } else if (left == AUTO && width != AUTO && right != AUTO) { - - /* Adjust for {min|max}-width */ - if (max_width >= 0 && width > max_width) - width = max_width; - if (width < min_width) - width = min_width; - - left = containing_block->width - - margin[LEFT] - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - margin[RIGHT] - - right; - } else if (left != AUTO && width == AUTO && right != AUTO) { - width = containing_block->width - - left - margin[LEFT] - - border[LEFT].width - - padding[LEFT] - padding[RIGHT] - - border[RIGHT].width - margin[RIGHT] - - right; - - /* Adjust for {min|max}-width */ - if (max_width >= 0 && width > max_width) - width = max_width; - if (width < min_width) - width = min_width; - - } else if (left != AUTO && width != AUTO && right == AUTO) { - - /* Adjust for {min|max}-width */ - if (max_width >= 0 && width > max_width) - width = max_width; - if (width < min_width) - width = min_width; - - right = containing_block->width - - left - margin[LEFT] - - border[LEFT].width - - padding[LEFT] - width - padding[RIGHT] - - border[RIGHT].width - margin[RIGHT]; - } - } - - NSLOG(layout, DEBUG, - "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", - left, margin[LEFT], border[LEFT].width, padding[LEFT], width, - padding[RIGHT], border[RIGHT].width, margin[RIGHT], right, - containing_block->width); - - box->x = left + margin[LEFT] + border[LEFT].width - cx; - if (containing_block->type == BOX_BLOCK || - containing_block->type == BOX_INLINE_BLOCK || - containing_block->type == BOX_TABLE_CELL) { - /* Block-level ancestor => reset container's width */ - containing_block->width -= containing_block->padding[LEFT] + - containing_block->padding[RIGHT]; - } else { - /** \todo inline ancestors */ - } - box->width = width; - box->height = height; - - if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || - box->object || box->flags & IFRAME) { - if (!layout_block_context(box, -1, content)) - return false; - } else if (box->type == BOX_TABLE) { - /* layout_table also expects the containing block to be - * stored in the float_container field */ - box->float_container = containing_block; - /* \todo layout_table considers margins etc. again */ - if (!layout_table(box, width, content)) - return false; - box->float_container = NULL; - layout_solve_width(box, box->parent->width, box->width, 0, 0, - -1, -1); - } - - /* 10.6.4 */ - NSLOG(layout, DEBUG, - "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", - top, margin[TOP], border[TOP].width, padding[TOP], height, - padding[BOTTOM], border[BOTTOM].width, margin[BOTTOM], bottom, - containing_block->height); - - if (top == AUTO && height == AUTO && bottom == AUTO) { - top = static_top; - height = box->height; - if (margin[TOP] == AUTO) - margin[TOP] = 0; - if (margin[BOTTOM] == AUTO) - margin[BOTTOM] = 0; - bottom = containing_block->height - - top - margin[TOP] - border[TOP].width - - padding[TOP] - height - padding[BOTTOM] - - border[BOTTOM].width - margin[BOTTOM]; - } else if (top != AUTO && height != AUTO && bottom != AUTO) { - if (margin[TOP] == AUTO && margin[BOTTOM] == AUTO) { - space = containing_block->height - - top - border[TOP].width - padding[TOP] - - height - padding[BOTTOM] - - border[BOTTOM].width - bottom; - margin[TOP] = margin[BOTTOM] = space / 2; - } else if (margin[TOP] == AUTO) { - margin[TOP] = containing_block->height - - top - border[TOP].width - padding[TOP] - - height - padding[BOTTOM] - - border[BOTTOM].width - margin[BOTTOM] - - bottom; - } else if (margin[BOTTOM] == AUTO) { - margin[BOTTOM] = containing_block->height - - top - margin[TOP] - border[TOP].width - - padding[TOP] - height - - padding[BOTTOM] - border[BOTTOM].width - - bottom; - } else { - bottom = containing_block->height - - top - margin[TOP] - border[TOP].width - - padding[TOP] - height - - padding[BOTTOM] - border[BOTTOM].width - - margin[BOTTOM]; - } - } else { - if (margin[TOP] == AUTO) - margin[TOP] = 0; - if (margin[BOTTOM] == AUTO) - margin[BOTTOM] = 0; - if (top == AUTO && height == AUTO && bottom != AUTO) { - height = box->height; - top = containing_block->height - - margin[TOP] - border[TOP].width - - padding[TOP] - height - - padding[BOTTOM] - border[BOTTOM].width - - margin[BOTTOM] - bottom; - } else if (top == AUTO && height != AUTO && bottom == AUTO) { - top = static_top; - bottom = containing_block->height - - top - margin[TOP] - border[TOP].width - - padding[TOP] - height - - padding[BOTTOM] - border[BOTTOM].width - - margin[BOTTOM]; - } else if (top != AUTO && height == AUTO && bottom == AUTO) { - height = box->height; - bottom = containing_block->height - - top - margin[TOP] - border[TOP].width - - padding[TOP] - height - - padding[BOTTOM] - border[BOTTOM].width - - margin[BOTTOM]; - } else if (top == AUTO && height != AUTO && bottom != AUTO) { - top = containing_block->height - - margin[TOP] - border[TOP].width - - padding[TOP] - height - - padding[BOTTOM] - border[BOTTOM].width - - margin[BOTTOM] - bottom; - } else if (top != AUTO && height == AUTO && bottom != AUTO) { - height = containing_block->height - - top - margin[TOP] - border[TOP].width - - padding[TOP] - padding[BOTTOM] - - border[BOTTOM].width - margin[BOTTOM] - - bottom; - } else if (top != AUTO && height != AUTO && bottom == AUTO) { - bottom = containing_block->height - - top - margin[TOP] - border[TOP].width - - padding[TOP] - height - - padding[BOTTOM] - border[BOTTOM].width - - margin[BOTTOM]; - } - } - - NSLOG(layout, DEBUG, - "%i + %i + %i + %i + %i + %i + %i + %i + %i = %i", - top, margin[TOP], border[TOP].width, padding[TOP], height, - padding[BOTTOM], border[BOTTOM].width, margin[BOTTOM], bottom, - containing_block->height); - - box->y = top + margin[TOP] + border[TOP].width - cy; - if (containing_block->type == BOX_BLOCK || - containing_block->type == BOX_INLINE_BLOCK || - containing_block->type == BOX_TABLE_CELL) { - /* Block-level ancestor => reset container's height */ - containing_block->height -= containing_block->padding[TOP] + - containing_block->padding[BOTTOM]; - } else { - /** \todo Inline ancestors */ - } - box->height = height; - layout_apply_minmax_height(&content->len_ctx, box, containing_block); - - return true; -} - - -/** - * Recursively layout and position absolutely positioned boxes. - * - * \param box tree of boxes to layout - * \param containing_block current containing block - * \param cx position of box relative to containing_block - * \param cy position of box relative to containing_block - * \param content memory pool for any new boxes - * \return true on success, false on memory exhaustion - */ -static bool -layout_position_absolute(struct box *box, - struct box *containing_block, - int cx, int cy, - html_content *content) -{ - struct box *c; - - for (c = box->children; c; c = c->next) { - if ((c->type == BOX_BLOCK || c->type == BOX_TABLE || - c->type == BOX_INLINE_BLOCK) && - (css_computed_position(c->style) == - CSS_POSITION_ABSOLUTE || - css_computed_position(c->style) == - CSS_POSITION_FIXED)) { - if (!layout_absolute(c, containing_block, - cx, cy, content)) - return false; - if (!layout_position_absolute(c, c, 0, 0, content)) - return false; - } else if (c->style && css_computed_position(c->style) == - CSS_POSITION_RELATIVE) { - if (!layout_position_absolute(c, c, 0, 0, content)) - return false; - } else { - int px, py; - if (c->style && (css_computed_float(c->style) == - CSS_FLOAT_LEFT || - css_computed_float(c->style) == - CSS_FLOAT_RIGHT)) { - /* Float x/y coords are relative to nearest - * ansestor with float_children, rather than - * relative to parent. Need to get x/y relative - * to parent */ - struct box *p; - px = c->x; - py = c->y; - for (p = box->parent; p && !p->float_children; - p = p->parent) { - px -= p->x; - py -= p->y; - } - } else { - /* Not a float, so box x/y coords are relative - * to parent */ - px = c->x; - py = c->y; - } - if (!layout_position_absolute(c, containing_block, - cx + px, cy + py, content)) - return false; - } - } - - return true; -} - - -/** - * Compute a box's relative offset as per CSS 2.1 9.4.3 - * - * \param len_ctx Length conversion context - * \param box Box to compute relative offsets for. - * \param x Receives relative offset in x. - * \param y Receives relative offset in y. - */ -static void layout_compute_relative_offset( - const nscss_len_ctx *len_ctx, - struct box *box, - int *x, - int *y) -{ - int left, right, top, bottom; - struct box *containing_block; - - assert(box && box->parent && box->style && - css_computed_position(box->style) == - CSS_POSITION_RELATIVE); - - if (box->float_container && - (css_computed_float(box->style) == CSS_FLOAT_LEFT || - css_computed_float(box->style) == CSS_FLOAT_RIGHT)) { - containing_block = box->float_container; - } else { - containing_block = box->parent; - } - - layout_compute_offsets(len_ctx, box, containing_block, - &top, &right, &bottom, &left); - - if (left == AUTO && right == AUTO) - left = right = 0; - else if (left == AUTO) - /* left is auto => computed = -right */ - left = -right; - else if (right == AUTO) - /* right is auto => computed = -left */ - right = -left; - else { - /* over constrained => examine direction property - * of containing block */ - if (containing_block->style && - css_computed_direction( - containing_block->style) == - CSS_DIRECTION_RTL) { - /* right wins */ - left = -right; - } else { - /* assume LTR in all other cases */ - right = -left; - } - } - - assert(left == -right); - - if (top == AUTO && bottom == AUTO) { - top = bottom = 0; - } else if (top == AUTO) { - top = -bottom; - } else { - /* bottom is AUTO, or neither are AUTO */ - bottom = -top; - } - - NSLOG(layout, DEBUG, "left %i, right %i, top %i, bottom %i", left, - right, top, bottom); - - *x = left; - *y = top; -} - - -/** - * Adjust positions of relatively positioned boxes. - * - * \param len_ctx Length conversion context - * \param root box to adjust the position of - * \param fp box which forms the block formatting context for children of - * "root" which are floats - * \param fx x offset due to intervening relatively positioned boxes - * between current box, "root", and the block formatting context - * box, "fp", for float children of "root" - * \param fy y offset due to intervening relatively positioned boxes - * between current box, "root", and the block formatting context - * box, "fp", for float children of "root" - */ -static void -layout_position_relative( - const nscss_len_ctx *len_ctx, - struct box *root, - struct box *fp, - int fx, - int fy) -{ - struct box *box; /* for children of "root" */ - struct box *fn; /* for block formatting context box for children of - * "box" */ - struct box *fc; /* for float children of the block formatting context, - * "fp" */ - int x, y; /* for the offsets resulting from any relative - * positioning on the current block */ - int fnx, fny; /* for affsets which apply to flat children of "box" */ - - /**\todo ensure containing box is large enough after moving boxes */ - - assert(root); - - /* Normal children */ - for (box = root->children; box; box = box->next) { - - if (box->type == BOX_TEXT) - continue; - - /* If relatively positioned, get offsets */ - if (box->style && css_computed_position(box->style) == - CSS_POSITION_RELATIVE) - layout_compute_relative_offset( - len_ctx, box, &x, &y); - else - x = y = 0; - - /* Adjust float coordinates. - * (note float x and y are relative to their block formatting - * context box and not their parent) */ - if (box->style && (css_computed_float(box->style) == - CSS_FLOAT_LEFT || - css_computed_float(box->style) == - CSS_FLOAT_RIGHT) && - (fx != 0 || fy != 0)) { - /* box is a float and there is a float offset to - * apply */ - for (fc = fp->float_children; fc; fc = fc->next_float) { - if (box == fc->children) { - /* Box is floated in the block - * formatting context block, fp. - * Apply float offsets. */ - box->x += fx; - box->y += fy; - fx = fy = 0; - } - } - } - - if (box->float_children) { - fn = box; - fnx = fny = 0; - } else { - fn = fp; - fnx = fx + x; - fny = fy + y; - } - - /* recurse first */ - layout_position_relative(len_ctx, box, fn, fnx, fny); - - /* Ignore things we're not interested in. */ - if (!box->style || (box->style && - css_computed_position(box->style) != - CSS_POSITION_RELATIVE)) - continue; - - box->x += x; - box->y += y; - - /* Handle INLINEs - their "children" are in fact - * the sibling boxes between the INLINE and - * INLINE_END boxes */ - if (box->type == BOX_INLINE && box->inline_end) { - struct box *b; - for (b = box->next; b && b != box->inline_end; - b = b->next) { - b->x += x; - b->y += y; - } - } - } -} - - -/** - * Find a box's bounding box relative to itself, i.e. the box's border edge box - * - * \param len_ctx Length conversion context - * \param box box find bounding box of - * \param desc_x0 updated to left of box's bbox - * \param desc_y0 updated to top of box's bbox - * \param desc_x1 updated to right of box's bbox - * \param desc_y1 updated to bottom of box's bbox - */ -static void -layout_get_box_bbox( - const nscss_len_ctx *len_ctx, - struct box *box, - int *desc_x0, int *desc_y0, - int *desc_x1, int *desc_y1) -{ - *desc_x0 = -box->border[LEFT].width; - *desc_y0 = -box->border[TOP].width; - *desc_x1 = box->padding[LEFT] + box->width + box->padding[RIGHT] + - box->border[RIGHT].width; - *desc_y1 = box->padding[TOP] + box->height + box->padding[BOTTOM] + - box->border[BOTTOM].width; - - /* To stop the top of text getting clipped when css line-height is - * reduced, we increase the top of the descendant bbox. */ - if (box->type == BOX_BLOCK && box->style != NULL && - css_computed_overflow_y(box->style) == - CSS_OVERFLOW_VISIBLE && - box->object == NULL) { - css_fixed font_size = 0; - css_unit font_unit = CSS_UNIT_PT; - int text_height; - - css_computed_font_size(box->style, &font_size, &font_unit); - text_height = nscss_len2px(len_ctx, font_size, font_unit, - box->style); - text_height = FIXTOINT(text_height * 3 / 4); - *desc_y0 = (*desc_y0 < -text_height) ? *desc_y0 : -text_height; - } -} - - -/** - * Apply changes to box descendant_[xy][01] values due to given child. - * - * \param len_ctx Length conversion context - * \param box box to update - * \param child a box, which may affect box's descendant bbox - * \param off_x offset to apply to child->x coord to treat as child of box - * \param off_y offset to apply to child->y coord to treat as child of box - */ -static void -layout_update_descendant_bbox( - const nscss_len_ctx *len_ctx, - struct box *box, - struct box *child, - int off_x, - int off_y) -{ - int child_desc_x0, child_desc_y0, child_desc_x1, child_desc_y1; - - /* get coordinates of child relative to box */ - int child_x = child->x - off_x; - int child_y = child->y - off_y; - - bool html_object = (child->object && - content_get_type(child->object) == CONTENT_HTML); - - enum css_overflow_e overflow_x = CSS_OVERFLOW_VISIBLE; - enum css_overflow_e overflow_y = CSS_OVERFLOW_VISIBLE; - - if (child->style != NULL) { - overflow_x = css_computed_overflow_x(child->style); - overflow_y = css_computed_overflow_y(child->style); - } - - /* Get child's border edge */ - layout_get_box_bbox(len_ctx, child, - &child_desc_x0, &child_desc_y0, - &child_desc_x1, &child_desc_y1); - - if (overflow_x == CSS_OVERFLOW_VISIBLE && - html_object == false) { - /* get child's descendant bbox relative to box */ - child_desc_x0 = child->descendant_x0; - child_desc_x1 = child->descendant_x1; - } - if (overflow_y == CSS_OVERFLOW_VISIBLE && - html_object == false) { - /* get child's descendant bbox relative to box */ - child_desc_y0 = child->descendant_y0; - child_desc_y1 = child->descendant_y1; - } - - child_desc_x0 += child_x; - child_desc_y0 += child_y; - child_desc_x1 += child_x; - child_desc_y1 += child_y; - - /* increase box's descendant bbox to contain descendants */ - if (child_desc_x0 < box->descendant_x0) - box->descendant_x0 = child_desc_x0; - if (child_desc_y0 < box->descendant_y0) - box->descendant_y0 = child_desc_y0; - if (box->descendant_x1 < child_desc_x1) - box->descendant_x1 = child_desc_x1; - if (box->descendant_y1 < child_desc_y1) - box->descendant_y1 = child_desc_y1; -} - - -/** - * Recursively calculate the descendant_[xy][01] values for a laid-out box tree - * and inform iframe browser windows of their size and position. - * - * \param len_ctx Length conversion context - * \param box tree of boxes to update - */ -static void layout_calculate_descendant_bboxes( - const nscss_len_ctx *len_ctx, - struct box *box) -{ - struct box *child; - - assert(box->width != UNKNOWN_WIDTH); - assert(box->height != AUTO); - /* assert((box->width >= 0) && (box->height >= 0)); */ - - /* Initialise box's descendant box to border edge box */ - layout_get_box_bbox(len_ctx, box, - &box->descendant_x0, &box->descendant_y0, - &box->descendant_x1, &box->descendant_y1); - - /* Extend it to contain HTML contents if box is replaced */ - if (box->object && content_get_type(box->object) == CONTENT_HTML) { - if (box->descendant_x1 < content_get_width(box->object)) - box->descendant_x1 = content_get_width(box->object); - if (box->descendant_y1 < content_get_height(box->object)) - box->descendant_y1 = content_get_height(box->object); - } - - if (box->iframe != NULL) { - int x, y; - box_coords(box, &x, &y); - - browser_window_set_position(box->iframe, x, y); - browser_window_set_dimensions(box->iframe, - box->width, box->height); - browser_window_reformat(box->iframe, true, - box->width, box->height); - } - - if (box->type == BOX_INLINE || box->type == BOX_TEXT) - return; - - if (box->type == BOX_INLINE_END) { - box = box->inline_end; - for (child = box->next; child; - child = child->next) { - if (child->type == BOX_FLOAT_LEFT || - child->type == BOX_FLOAT_RIGHT) - continue; - - layout_update_descendant_bbox(len_ctx, box, child, - box->x, box->y); - - if (child == box->inline_end) - break; - } - return; - } - - if (box->flags & REPLACE_DIM) - /* Box's children aren't displayed if the box is replaced */ - return; - - for (child = box->children; child; child = child->next) { - if (child->type == BOX_FLOAT_LEFT || - child->type == BOX_FLOAT_RIGHT) - continue; - - layout_calculate_descendant_bboxes(len_ctx, child); - - if (box->style && css_computed_overflow_x(box->style) == - CSS_OVERFLOW_HIDDEN && - css_computed_overflow_y(box->style) == - CSS_OVERFLOW_HIDDEN) - continue; - - layout_update_descendant_bbox(len_ctx, box, child, 0, 0); - } - - for (child = box->float_children; child; child = child->next_float) { - assert(child->type == BOX_FLOAT_LEFT || - child->type == BOX_FLOAT_RIGHT); - - layout_calculate_descendant_bboxes(len_ctx, child); - - layout_update_descendant_bbox(len_ctx, box, child, 0, 0); - } - - if (box->list_marker) { - child = box->list_marker; - layout_calculate_descendant_bboxes(len_ctx, child); - - layout_update_descendant_bbox(len_ctx, box, child, 0, 0); - } -} - - -/* exported function documented in render/layout.h */ -bool layout_document(html_content *content, int width, int height) -{ - bool ret; - struct box *doc = content->layout; - const struct gui_layout_table *font_func = content->font_func; - - layout_minmax_block(doc, font_func, content); - - layout_block_find_dimensions(&content->len_ctx, - width, height, 0, 0, doc); - doc->x = doc->margin[LEFT] + doc->border[LEFT].width; - doc->y = doc->margin[TOP] + doc->border[TOP].width; - width -= doc->margin[LEFT] + doc->border[LEFT].width + - doc->padding[LEFT] + doc->padding[RIGHT] + - doc->border[RIGHT].width + doc->margin[RIGHT]; - if (width < 0) { - width = 0; - } - doc->width = width; - - ret = layout_block_context(doc, height, content); - - /* make and fill available height */ - if (doc->y + doc->padding[TOP] + doc->height + doc->padding[BOTTOM] + - doc->border[BOTTOM].width + doc->margin[BOTTOM] < - height) { - doc->height = height - (doc->y + doc->padding[TOP] + - doc->padding[BOTTOM] + - doc->border[BOTTOM].width + - doc->margin[BOTTOM]); - if (doc->children) - doc->children->height = doc->height - - (doc->children->margin[TOP] + - doc->children->border[TOP].width + - doc->children->padding[TOP] + - doc->children->padding[BOTTOM] + - doc->children->border[BOTTOM].width + - doc->children->margin[BOTTOM]); - } - - layout_lists(doc, font_func, &content->len_ctx); - layout_position_absolute(doc, doc, 0, 0, content); - layout_position_relative(&content->len_ctx, doc, doc, 0, 0); - - layout_calculate_descendant_bboxes(&content->len_ctx, doc); - - return ret; -} diff --git a/render/layout.h b/render/layout.h deleted file mode 100644 index cd5ddd77f..000000000 --- a/render/layout.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2003 James Bursa - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * HTML layout (interface). - * - * The main interface to the layout code is layout_document(), which takes a - * normalized box tree and assigns coordinates and dimensions to the boxes, and - * also adds boxes to the tree (eg. when formatting lines of text). - */ - -#ifndef _NETSURF_RENDER_LAYOUT_H_ -#define _NETSURF_RENDER_LAYOUT_H_ - -struct box; -struct html_content; -struct gui_layout_table; - -/** - * Calculate positions of boxes in a document. - * - * \param content content of type CONTENT_HTML - * \param width available width - * \param height available height - * \return true on success, false on memory exhaustion - */ -bool layout_document(struct html_content *content, int width, int height); - -#endif diff --git a/render/search.c b/render/search.c deleted file mode 100644 index ca9520165..000000000 --- a/render/search.c +++ /dev/null @@ -1,656 +0,0 @@ -/* - * Copyright 2004 John M Bell - * Copyright 2005 Adrian Lees - * Copyright 2009 Mark Benjamin - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * Free text search (core) - */ - -#include -#include -#include - -#include "utils/config.h" -#include "utils/log.h" -#include "utils/messages.h" -#include "utils/utils.h" -#include "content/content.h" -#include "content/hlcache.h" -#include "desktop/selection.h" -#include "netsurf/search.h" -#include "netsurf/misc.h" -#include "desktop/gui_internal.h" - -#include "render/box.h" -#include "render/html.h" -#include "render/html_internal.h" -#include "render/search.h" -#include "render/textplain.h" - -#ifndef NOF_ELEMENTS -#define NOF_ELEMENTS(array) (sizeof(array)/sizeof(*(array))) -#endif - - -struct list_entry { - unsigned start_idx; /* start position of match */ - unsigned end_idx; /* end of match */ - - struct box *start_box; /* used only for html contents */ - struct box *end_box; - - struct selection *sel; - - struct list_entry *prev; - struct list_entry *next; -}; - -struct search_context { - void *gui_p; - struct content *c; - struct list_entry *found; - struct list_entry *current; /* first for select all */ - char *string; - bool prev_case_sens; - bool newsearch; - bool is_html; -}; - - -/* Exported function documented in search.h */ -struct search_context * search_create_context(struct content *c, - content_type type, void *gui_data) -{ - struct search_context *context; - struct list_entry *search_head; - - if (type != CONTENT_HTML && type != CONTENT_TEXTPLAIN) { - return NULL; - } - - context = malloc(sizeof(struct search_context)); - if (context == NULL) { - guit->misc->warning("NoMemory", 0); - return NULL; - } - - search_head = malloc(sizeof(struct list_entry)); - if (search_head == NULL) { - guit->misc->warning("NoMemory", 0); - free(context); - return NULL; - } - - search_head->start_idx = 0; - search_head->end_idx = 0; - search_head->start_box = NULL; - search_head->end_box = NULL; - search_head->sel = NULL; - search_head->prev = NULL; - search_head->next = NULL; - - context->found = search_head; - context->current = NULL; - context->string = NULL; - context->prev_case_sens = false; - context->newsearch = true; - context->c = c; - context->is_html = (type == CONTENT_HTML) ? true : false; - context->gui_p = gui_data; - - return context; -} - - -/** - * Release the memory used by the list of matches, - * deleting selection objects too - */ - -static void free_matches(struct search_context *context) -{ - struct list_entry *a; - struct list_entry *b; - - a = context->found->next; - - /* empty the list before clearing and deleting the - * selections because the the clearing updates the - * screen immediately, causing nested accesses to the list */ - - context->found->prev = NULL; - context->found->next = NULL; - - for (; a; a = b) { - b = a->next; - if (a->sel) { - selection_clear(a->sel, true); - selection_destroy(a->sel); - } - free(a); - } -} - - -/** - * Find the first occurrence of 'match' in 'string' and return its index - * - * \param string the string to be searched (unterminated) - * \param s_len length of the string to be searched - * \param pattern the pattern for which we are searching (unterminated) - * \param p_len length of pattern - * \param case_sens true iff case sensitive match required - * \param m_len accepts length of match in bytes - * \return pointer to first match, NULL if none - */ - -static const char *find_pattern(const char *string, int s_len, - const char *pattern, int p_len, bool case_sens, - unsigned int *m_len) -{ - struct { const char *ss, *s, *p; bool first; } context[16]; - const char *ep = pattern + p_len; - const char *es = string + s_len; - const char *p = pattern - 1; /* a virtual '*' before the pattern */ - const char *ss = string; - const char *s = string; - bool first = true; - int top = 0; - - while (p < ep) { - bool matches; - if (p < pattern || *p == '*') { - char ch; - - /* skip any further asterisks; one is the same as many - */ - do p++; while (p < ep && *p == '*'); - - /* if we're at the end of the pattern, yes, it matches - */ - if (p >= ep) break; - - /* anything matches a # so continue matching from - here, and stack a context that will try to match - the wildcard against the next character */ - - ch = *p; - if (ch != '#') { - /* scan forwards until we find a match for - this char */ - if (!case_sens) ch = toupper(ch); - while (s < es) { - if (case_sens) { - if (*s == ch) break; - } else if (toupper(*s) == ch) - break; - s++; - } - } - - if (s < es) { - /* remember where we are in case the match - fails; we may then resume */ - if (top < (int)NOF_ELEMENTS(context)) { - context[top].ss = ss; - context[top].s = s + 1; - context[top].p = p - 1; - /* ptr to last asterisk */ - context[top].first = first; - top++; - } - - if (first) { - ss = s; - /* remember first non-'*' char */ - first = false; - } - - matches = true; - } else { - matches = false; - } - - } else if (s < es) { - char ch = *p; - if (ch == '#') - matches = true; - else { - if (case_sens) - matches = (*s == ch); - else - matches = (toupper(*s) == toupper(ch)); - } - if (matches && first) { - ss = s; /* remember first non-'*' char */ - first = false; - } - } else { - matches = false; - } - - if (matches) { - p++; s++; - } else { - /* doesn't match, - * resume with stacked context if we have one */ - if (--top < 0) - return NULL; /* no match, give up */ - - ss = context[top].ss; - s = context[top].s; - p = context[top].p; - first = context[top].first; - } - } - - /* end of pattern reached */ - *m_len = max(s - ss, 1); - return ss; -} - - -/** - * Add a new entry to the list of matches - * - * \param start_idx Offset of match start within textual representation - * \param end_idx Offset of match end - * \param context The search context to add the entry to. - * \return Pointer to added entry, NULL iff failed. - */ - -static struct list_entry *add_entry(unsigned start_idx, unsigned end_idx, - struct search_context *context) -{ - struct list_entry *entry; - - /* found string in box => add to list */ - entry = calloc(1, sizeof(*entry)); - if (!entry) { - guit->misc->warning("NoMemory", 0); - return NULL; - } - - entry->start_idx = start_idx; - entry->end_idx = end_idx; - entry->sel = NULL; - - entry->next = 0; - entry->prev = context->found->prev; - - if (context->found->prev == NULL) - context->found->next = entry; - else - context->found->prev->next = entry; - - context->found->prev = entry; - - return entry; -} - - -/** - * Finds all occurrences of a given string in the html box tree - * - * \param pattern the string pattern to search for - * \param p_len pattern length - * \param cur pointer to the current box - * \param case_sens whether to perform a case sensitive search - * \param context The search context to add the entry to. - * \return true on success, false on memory allocation failure - */ -static bool find_occurrences_html(const char *pattern, int p_len, - struct box *cur, bool case_sens, - struct search_context *context) -{ - struct box *a; - - /* ignore this box, if there's no visible text */ - if (!cur->object && cur->text) { - const char *text = cur->text; - unsigned length = cur->length; - - while (length > 0) { - struct list_entry *entry; - unsigned match_length; - unsigned match_offset; - const char *new_text; - const char *pos = find_pattern(text, length, - pattern, p_len, case_sens, - &match_length); - if (!pos) - break; - - /* found string in box => add to list */ - match_offset = pos - cur->text; - - entry = add_entry(cur->byte_offset + match_offset, - cur->byte_offset + - match_offset + - match_length, context); - if (!entry) - return false; - - entry->start_box = cur; - entry->end_box = cur; - - new_text = pos + match_length; - length -= (new_text - text); - text = new_text; - } - } - - /* and recurse */ - for (a = cur->children; a; a = a->next) { - if (!find_occurrences_html(pattern, p_len, a, case_sens, - context)) - return false; - } - - return true; -} - - -/** - * Finds all occurrences of a given string in a textplain content - * - * \param pattern the string pattern to search for - * \param p_len pattern length - * \param c the content to be searched - * \param case_sens whether to perform a case sensitive search - * \param context The search context to add the entry to. - * \return true on success, false on memory allocation failure - */ - -static bool find_occurrences_text(const char *pattern, int p_len, - struct content *c, bool case_sens, - struct search_context *context) -{ - int nlines = textplain_line_count(c); - int line; - - for(line = 0; line < nlines; line++) { - size_t offset, length; - const char *text = textplain_get_line(c, line, - &offset, &length); - if (text) { - while (length > 0) { - struct list_entry *entry; - unsigned match_length; - size_t start_idx; - const char *new_text; - const char *pos = find_pattern(text, length, - pattern, p_len, case_sens, - &match_length); - if (!pos) - break; - - /* found string in line => add to list */ - start_idx = offset + (pos - text); - entry = add_entry(start_idx, start_idx + - match_length, context); - if (!entry) - return false; - - new_text = pos + match_length; - offset += (new_text - text); - length -= (new_text - text); - text = new_text; - } - } - } - - return true; -} - - -/** - * Search for a string in the box tree - * - * \param string the string to search for - * \param string_len length of search string - * \param context The search context to add the entry to. - * \param flags flags to control the search. - */ -static void search_text(const char *string, int string_len, - struct search_context *context, search_flags_t flags) -{ - struct rect bounds; - struct box *box = NULL; - union content_msg_data msg_data; - bool case_sensitive, forwards, showall; - - case_sensitive = ((flags & SEARCH_FLAG_CASE_SENSITIVE) != 0) ? - true : false; - forwards = ((flags & SEARCH_FLAG_FORWARDS) != 0) ? true : false; - showall = ((flags & SEARCH_FLAG_SHOWALL) != 0) ? true : false; - - if (context->c == NULL) - return; - - if (context->is_html == true) { - html_content *html = (html_content *)context->c; - - box = html->layout; - - if (!box) - return; - } - - - /* check if we need to start a new search or continue an old one */ - if (context->newsearch) { - bool res; - - if (context->string != NULL) - free(context->string); - - context->current = NULL; - free_matches(context); - - context->string = malloc(string_len + 1); - if (context->string != NULL) { - memcpy(context->string, string, string_len); - context->string[string_len] = '\0'; - } - - guit->search->hourglass(true, context->gui_p); - - if (context->is_html == true) { - res = find_occurrences_html(string, string_len, - box, case_sensitive, context); - } else { - res = find_occurrences_text(string, string_len, - context->c, case_sensitive, context); - } - - if (!res) { - free_matches(context); - guit->search->hourglass(false, context->gui_p); - return; - } - guit->search->hourglass(false, context->gui_p); - - context->prev_case_sens = case_sensitive; - - /* new search, beginning at the top of the page */ - context->current = context->found->next; - context->newsearch = false; - - } else if (context->current != NULL) { - /* continued search in the direction specified */ - if (forwards) { - if (context->current->next) - context->current = context->current->next; - } else { - if (context->current->prev) - context->current = context->current->prev; - } - } - - guit->search->status((context->current != NULL), context->gui_p); - - search_show_all(showall, context); - - guit->search->back_state((context->current != NULL) && - (context->current->prev != NULL), - context->gui_p); - guit->search->forward_state((context->current != NULL) && - (context->current->next != NULL), - context->gui_p); - - if (context->current == NULL) - return; - - if (context->is_html == true) { - /* get box position and jump to it */ - box_coords(context->current->start_box, &bounds.x0, &bounds.y0); - /* \todo: move x0 in by correct idx */ - box_coords(context->current->end_box, &bounds.x1, &bounds.y1); - /* \todo: move x1 in by correct idx */ - bounds.x1 += context->current->end_box->width; - bounds.y1 += context->current->end_box->height; - } else { - textplain_coords_from_range(context->c, - context->current->start_idx, - context->current->end_idx, &bounds); - } - - msg_data.scroll.area = true; - msg_data.scroll.x0 = bounds.x0; - msg_data.scroll.y0 = bounds.y0; - msg_data.scroll.x1 = bounds.x1; - msg_data.scroll.y1 = bounds.y1; - content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); -} - - -/* Exported function documented in search.h */ -void search_step(struct search_context *context, search_flags_t flags, - const char *string) -{ - int string_len; - int i = 0; - - if (context == NULL) { - guit->misc->warning("SearchError", 0); - return; - } - - guit->search->add_recent(string, context->gui_p); - - string_len = strlen(string); - for (i = 0; i < string_len; i++) - if (string[i] != '#' && string[i] != '*') - break; - if (i >= string_len) { - union content_msg_data msg_data; - free_matches(context); - - guit->search->status(true, context->gui_p); - guit->search->back_state(false, context->gui_p); - guit->search->forward_state(false, context->gui_p); - - msg_data.scroll.area = false; - msg_data.scroll.x0 = 0; - msg_data.scroll.y0 = 0; - content_broadcast(context->c, CONTENT_MSG_SCROLL, &msg_data); - return; - } - search_text(string, string_len, context, flags); -} - - -/* Exported function documented in search.h */ -bool search_term_highlighted(struct content *c, - unsigned start_offset, unsigned end_offset, - unsigned *start_idx, unsigned *end_idx, - struct search_context *context) -{ - if (c == context->c) { - struct list_entry *a; - for (a = context->found->next; a; a = a->next) - if (a->sel && selection_defined(a->sel) && - selection_highlighted(a->sel, - start_offset, end_offset, - start_idx, end_idx)) - return true; - } - - return false; -} - - -/* Exported function documented in search.h */ -void search_show_all(bool all, struct search_context *context) -{ - struct list_entry *a; - - for (a = context->found->next; a; a = a->next) { - bool add = true; - if (!all && a != context->current) { - add = false; - if (a->sel) { - selection_clear(a->sel, true); - selection_destroy(a->sel); - a->sel = NULL; - } - } - if (add && !a->sel) { - - if (context->is_html == true) { - html_content *html = (html_content *)context->c; - a->sel = selection_create(context->c, true); - if (!a->sel) - continue; - - selection_init(a->sel, html->layout, - &html->len_ctx); - } else { - a->sel = selection_create(context->c, false); - if (!a->sel) - continue; - - selection_init(a->sel, NULL, NULL); - } - - selection_set_start(a->sel, a->start_idx); - selection_set_end(a->sel, a->end_idx); - } - } -} - - -/* Exported function documented in search.h */ -void search_destroy_context(struct search_context *context) -{ - assert(context != NULL); - - if (context->string != NULL) { - guit->search->add_recent(context->string, context->gui_p); - free(context->string); - } - - guit->search->forward_state(true, context->gui_p); - guit->search->back_state(true, context->gui_p); - - free_matches(context); - free(context); -} diff --git a/render/search.h b/render/search.h deleted file mode 100644 index 79d1ee3d3..000000000 --- a/render/search.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2009 Mark Benjamin - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef _NETSURF_RENDER_SEARCH_H_ -#define _NETSURF_RENDER_SEARCH_H_ - -#include -#include - -#include "desktop/search.h" - -struct search_context; - -/** - * create a search_context - * - * \param c The content the search_context is connected to - * \param type The content type of c - * \param context A context pointer passed to the provider routines. - * \return A new search context or NULL on error. - */ -struct search_context *search_create_context(struct content *c, - content_type type, void *context); - -/** - * Ends the search process, invalidating all state - * freeing the list of found boxes - */ -void search_destroy_context(struct search_context *context); - -/** - * Begins/continues the search process - * - * \note that this may be called many times for a single search. - * - * \param context The search context in use. - * \param flags The flags forward/back etc - * \param string The string to match - */ -void search_step(struct search_context *context, search_flags_t flags, - const char * string); - -/** - * Specifies whether all matches or just the current match should - * be highlighted in the search text. - */ -void search_show_all(bool all, struct search_context *context); - -/** - * Determines whether any portion of the given text box should be - * selected because it matches the current search string. - * - * \param c The content to hilight within. - * \param start_offset byte offset within text of string to be checked - * \param end_offset byte offset within text - * \param start_idx byte offset within string of highlight start - * \param end_idx byte offset of highlight end - * \param context The search context to hilight entries from. - * \return true iff part of the box should be highlighted - */ -bool search_term_highlighted(struct content *c, - unsigned start_offset, unsigned end_offset, - unsigned *start_idx, unsigned *end_idx, - struct search_context *context); - -#endif diff --git a/render/table.c b/render/table.c deleted file mode 100644 index 08a2e805c..000000000 --- a/render/table.c +++ /dev/null @@ -1,1080 +0,0 @@ -/* - * Copyright 2005 James Bursa - * Copyright 2005 Richard Wilson - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Table processing and layout (implementation). - */ - -#include -#include - -#include "utils/log.h" -#include "utils/talloc.h" -#include "css/utils.h" - -#include "render/box.h" -#include "render/table.h" - -/* Define to enable verbose table debug */ -#undef TABLE_DEBUG - -/** - * Container for border values during table border calculations - */ -struct border { - enum css_border_style_e style; /**< border-style */ - enum css_border_color_e color; /**< border-color type */ - css_color c; /**< border-color value */ - css_fixed width; /**< border-width length */ - css_unit unit; /**< border-width units */ -}; - -static void table_used_left_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell); -static void table_used_top_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell); -static void table_used_right_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell); -static void table_used_bottom_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell); -static bool table_border_is_more_eyecatching( - const nscss_len_ctx *len_ctx, - const struct border *a, - box_type a_src, - const struct border *b, - box_type b_src); -static void table_cell_top_process_table( - const nscss_len_ctx *len_ctx, - struct box *table, - struct border *a, - box_type *a_src); -static bool table_cell_top_process_group( - const nscss_len_ctx *len_ctx, - struct box *cell, - struct box *group, - struct border *a, - box_type *a_src); -static bool table_cell_top_process_row( - const nscss_len_ctx *len_ctx, - struct box *cell, - struct box *row, - struct border *a, - box_type *a_src); - - -/** - * Determine the column width types for a table. - * - * \param len_ctx Length conversion context - * \param table box of type BOX_TABLE - * \return true on success, false on memory exhaustion - * - * The table->col array is allocated and type and width are filled in for each - * column. - */ - -bool table_calculate_column_types( - const nscss_len_ctx *len_ctx, - struct box *table) -{ - unsigned int i, j; - struct column *col; - struct box *row_group, *row, *cell; - - if (table->col) - /* table->col already constructed, for example frameset table */ - return true; - - table->col = col = talloc_array(table, struct column, table->columns); - if (!col) - return false; - - for (i = 0; i != table->columns; i++) { - col[i].type = COLUMN_WIDTH_UNKNOWN; - col[i].width = 0; - col[i].positioned = true; - } - - /* 1st pass: cells with colspan 1 only */ - for (row_group = table->children; row_group; row_group =row_group->next) - for (row = row_group->children; row; row = row->next) - for (cell = row->children; cell; cell = cell->next) { - enum css_width_e type; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - assert(cell->type == BOX_TABLE_CELL); - assert(cell->style); - - if (cell->columns != 1) - continue; - i = cell->start_column; - - if (css_computed_position(cell->style) != - CSS_POSITION_ABSOLUTE && - css_computed_position(cell->style) != - CSS_POSITION_FIXED) { - col[i].positioned = false; - } - - type = css_computed_width(cell->style, &value, &unit); - - /* fixed width takes priority over any other width type */ - if (col[i].type != COLUMN_WIDTH_FIXED && - type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) { - col[i].type = COLUMN_WIDTH_FIXED; - col[i].width = FIXTOINT(nscss_len2px(len_ctx, - value, unit, cell->style)); - if (col[i].width < 0) - col[i].width = 0; - continue; - } - - if (col[i].type != COLUMN_WIDTH_UNKNOWN) - continue; - - if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT) { - col[i].type = COLUMN_WIDTH_PERCENT; - col[i].width = FIXTOINT(value); - if (col[i].width < 0) - col[i].width = 0; - } else if (type == CSS_WIDTH_AUTO) { - col[i].type = COLUMN_WIDTH_AUTO; - } - } - - /* 2nd pass: cells which span multiple columns */ - for (row_group = table->children; row_group; row_group =row_group->next) - for (row = row_group->children; row; row = row->next) - for (cell = row->children; cell; cell = cell->next) { - unsigned int fixed_columns = 0, percent_columns = 0, - auto_columns = 0, unknown_columns = 0; - int fixed_width = 0, percent_width = 0; - enum css_width_e type; - css_fixed value = 0; - css_unit unit = CSS_UNIT_PX; - - if (cell->columns == 1) - continue; - i = cell->start_column; - - for (j = i; j < i + cell->columns; j++) { - col[j].positioned = false; - } - - /* count column types in spanned cells */ - for (j = 0; j != cell->columns; j++) { - if (col[i + j].type == COLUMN_WIDTH_FIXED) { - fixed_width += col[i + j].width; - fixed_columns++; - } else if (col[i + j].type == COLUMN_WIDTH_PERCENT) { - percent_width += col[i + j].width; - percent_columns++; - } else if (col[i + j].type == COLUMN_WIDTH_AUTO) { - auto_columns++; - } else { - unknown_columns++; - } - } - - if (!unknown_columns) - continue; - - type = css_computed_width(cell->style, &value, &unit); - - /* if cell is fixed width, and all spanned columns are fixed - * or unknown width, split extra width among unknown columns */ - if (type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT && - fixed_columns + unknown_columns == - cell->columns) { - int width = (FIXTOFLT(nscss_len2px(len_ctx, value, unit, - cell->style)) - fixed_width) / - unknown_columns; - if (width < 0) - width = 0; - for (j = 0; j != cell->columns; j++) { - if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) { - col[i + j].type = COLUMN_WIDTH_FIXED; - col[i + j].width = width; - } - } - } - - /* as above for percentage width */ - if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT && - percent_columns + unknown_columns == - cell->columns) { - int width = (FIXTOFLT(value) - - percent_width) / unknown_columns; - if (width < 0) - width = 0; - for (j = 0; j != cell->columns; j++) { - if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) { - col[i + j].type = COLUMN_WIDTH_PERCENT; - col[i + j].width = width; - } - } - } - } - - /* use AUTO if no width type was specified */ - for (i = 0; i != table->columns; i++) { - if (col[i].type == COLUMN_WIDTH_UNKNOWN) - col[i].type = COLUMN_WIDTH_AUTO; - } - -#ifdef TABLE_DEBUG - for (i = 0; i != table->columns; i++) - NSLOG(netsurf, INFO, - "table %p, column %u: type %s, width %i", table, i, ((const char *[]){ - "UNKNOWN", - "FIXED", - "AUTO", - "PERCENT", - "RELATIVE", - })[col[i].type], col[i].width); -#endif - - return true; -} - -/** - * Calculate used values of border-{trbl}-{style,color,width} for table cells. - * - * \param len_ctx Length conversion context - * \param cell Table cell to consider - * - * \post \a cell's border array is populated - */ -void table_used_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell) -{ - int side; - - assert(cell->type == BOX_TABLE_CELL); - - if (css_computed_border_collapse(cell->style) == - CSS_BORDER_COLLAPSE_SEPARATE) { - css_fixed width = 0; - css_unit unit = CSS_UNIT_PX; - - /* Left border */ - cell->border[LEFT].style = - css_computed_border_left_style(cell->style); - css_computed_border_left_color(cell->style, - &cell->border[LEFT].c); - css_computed_border_left_width(cell->style, &width, &unit); - cell->border[LEFT].width = - FIXTOINT(nscss_len2px(len_ctx, - width, unit, cell->style)); - - /* Top border */ - cell->border[TOP].style = - css_computed_border_top_style(cell->style); - css_computed_border_top_color(cell->style, - &cell->border[TOP].c); - css_computed_border_top_width(cell->style, &width, &unit); - cell->border[TOP].width = - FIXTOINT(nscss_len2px(len_ctx, - width, unit, cell->style)); - - /* Right border */ - cell->border[RIGHT].style = - css_computed_border_right_style(cell->style); - css_computed_border_right_color(cell->style, - &cell->border[RIGHT].c); - css_computed_border_right_width(cell->style, &width, &unit); - cell->border[RIGHT].width = - FIXTOINT(nscss_len2px(len_ctx, - width, unit, cell->style)); - - /* Bottom border */ - cell->border[BOTTOM].style = - css_computed_border_bottom_style(cell->style); - css_computed_border_bottom_color(cell->style, - &cell->border[BOTTOM].c); - css_computed_border_bottom_width(cell->style, &width, &unit); - cell->border[BOTTOM].width = - FIXTOINT(nscss_len2px(len_ctx, - width, unit, cell->style)); - } else { - /* Left border */ - table_used_left_border_for_cell(len_ctx, cell); - - /* Top border */ - table_used_top_border_for_cell(len_ctx, cell); - - /* Right border */ - table_used_right_border_for_cell(len_ctx, cell); - - /* Bottom border */ - table_used_bottom_border_for_cell(len_ctx, cell); - } - - /* Finally, ensure that any borders configured as - * hidden or none have zero width. (c.f. layout_find_dimensions) */ - for (side = 0; side != 4; side++) { - if (cell->border[side].style == CSS_BORDER_STYLE_HIDDEN || - cell->border[side].style == - CSS_BORDER_STYLE_NONE) - cell->border[side].width = 0; - } -} - -/****************************************************************************** - * Helpers for used border calculations * - ******************************************************************************/ - -/** - * Calculate used values of border-left-{style,color,width} - * - * \param len_ctx Length conversion context - * \param cell Table cell to consider - */ -void table_used_left_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell) -{ - struct border a, b; - box_type a_src, b_src; - - /** \todo Need column and column_group, too */ - - /* Initialise to computed left border for cell */ - a.style = css_computed_border_left_style(cell->style); - a.color = css_computed_border_left_color(cell->style, &a.c); - css_computed_border_left_width(cell->style, &a.width, &a.unit); - a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); - a.unit = CSS_UNIT_PX; - a_src = BOX_TABLE_CELL; - - if (cell->prev != NULL || cell->start_column != 0) { - /* Cell to the left -- consider its right border */ - struct box *prev = NULL; - - if (cell->prev == NULL) { - struct box *row; - - /* Spanned from a previous row in current row group */ - for (row = cell->parent; row != NULL; row = row->prev) { - for (prev = row->children; prev != NULL; - prev = prev->next) { - if (prev->start_column + - prev->columns == - cell->start_column) - break; - } - - if (prev != NULL) - break; - } - - assert(prev != NULL); - } else { - prev = cell->prev; - } - - b.style = css_computed_border_right_style(prev->style); - b.color = css_computed_border_right_color(prev->style, &b.c); - css_computed_border_right_width(prev->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, prev->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_CELL; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - } else { - /* First cell in row, so consider rows and row group */ - struct box *row = cell->parent; - struct box *group = row->parent; - struct box *table = group->parent; - unsigned int rows = cell->rows; - - while (rows-- > 0 && row != NULL) { - /* Spanned rows -- consider their left border */ - b.style = css_computed_border_left_style(row->style); - b.color = css_computed_border_left_color( - row->style, &b.c); - css_computed_border_left_width( - row->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, - b.width, b.unit, row->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - - row = row->next; - } - - /** \todo can cells span row groups? */ - - /* Row group -- consider its left border */ - b.style = css_computed_border_left_style(group->style); - b.color = css_computed_border_left_color(group->style, &b.c); - css_computed_border_left_width(group->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW_GROUP; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - - /* The table itself -- consider its left border */ - b.style = css_computed_border_left_style(table->style); - b.color = css_computed_border_left_color(table->style, &b.c); - css_computed_border_left_width(table->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - } - - /* a now contains the used left border for the cell */ - cell->border[LEFT].style = a.style; - cell->border[LEFT].c = a.c; - cell->border[LEFT].width = FIXTOINT(nscss_len2px(len_ctx, - a.width, a.unit, cell->style)); -} - -/** - * Calculate used values of border-top-{style,color,width} - * - * \param len_ctx Length conversion context - * \param cell Table cell to consider - */ -void table_used_top_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell) -{ - struct border a, b; - box_type a_src, b_src; - struct box *row = cell->parent; - bool process_group = false; - - /* Initialise to computed top border for cell */ - a.style = css_computed_border_top_style(cell->style); - css_computed_border_top_color(cell->style, &a.c); - css_computed_border_top_width(cell->style, &a.width, &a.unit); - a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); - a.unit = CSS_UNIT_PX; - a_src = BOX_TABLE_CELL; - - /* Top border of row */ - b.style = css_computed_border_top_style(row->style); - css_computed_border_top_color(row->style, &b.c); - css_computed_border_top_width(row->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW; - - if (table_border_is_more_eyecatching(len_ctx, &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - - if (row->prev != NULL) { - /* Consider row(s) above */ - while (table_cell_top_process_row(len_ctx, cell, row->prev, - &a, &a_src) == false) { - if (row->prev->prev == NULL) { - /* Consider row group */ - process_group = true; - break; - } else { - row = row->prev; - } - } - } else { - process_group = true; - } - - if (process_group) { - struct box *group = row->parent; - - /* Top border of row group */ - b.style = css_computed_border_top_style(group->style); - b.color = css_computed_border_top_color(group->style, &b.c); - css_computed_border_top_width(group->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW_GROUP; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - - if (group->prev == NULL) { - /* Top border of table */ - table_cell_top_process_table(len_ctx, - group->parent, &a, &a_src); - } else { - /* Process previous group(s) */ - while (table_cell_top_process_group(len_ctx, - cell, group->prev, - &a, &a_src) == false) { - if (group->prev->prev == NULL) { - /* Top border of table */ - table_cell_top_process_table(len_ctx, - group->parent, - &a, &a_src); - break; - } else { - group = group->prev; - } - } - } - } - - /* a now contains the used top border for the cell */ - cell->border[TOP].style = a.style; - cell->border[TOP].c = a.c; - cell->border[TOP].width = FIXTOINT(nscss_len2px(len_ctx, - a.width, a.unit, cell->style)); -} - -/** - * Calculate used values of border-right-{style,color,width} - * - * \param len_ctx Length conversion context - * \param cell Table cell to consider - */ -void table_used_right_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell) -{ - struct border a, b; - box_type a_src, b_src; - - /** \todo Need column and column_group, too */ - - /* Initialise to computed right border for cell */ - a.style = css_computed_border_right_style(cell->style); - css_computed_border_right_color(cell->style, &a.c); - css_computed_border_right_width(cell->style, &a.width, &a.unit); - a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); - a.unit = CSS_UNIT_PX; - a_src = BOX_TABLE_CELL; - - if (cell->next != NULL || cell->start_column + cell->columns != - cell->parent->parent->parent->columns) { - /* Cell is not at right edge of table -- no right border */ - a.style = CSS_BORDER_STYLE_NONE; - a.width = 0; - a.unit = CSS_UNIT_PX; - } else { - /* Last cell in row, so consider rows and row group */ - struct box *row = cell->parent; - struct box *group = row->parent; - struct box *table = group->parent; - unsigned int rows = cell->rows; - - while (rows-- > 0 && row != NULL) { - /* Spanned rows -- consider their right border */ - b.style = css_computed_border_right_style(row->style); - b.color = css_computed_border_right_color( - row->style, &b.c); - css_computed_border_right_width( - row->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, - b.width, b.unit, row->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - - row = row->next; - } - - /** \todo can cells span row groups? */ - - /* Row group -- consider its right border */ - b.style = css_computed_border_right_style(group->style); - b.color = css_computed_border_right_color(group->style, &b.c); - css_computed_border_right_width(group->style, - &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW_GROUP; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - - /* The table itself -- consider its right border */ - b.style = css_computed_border_right_style(table->style); - b.color = css_computed_border_right_color(table->style, &b.c); - css_computed_border_right_width(table->style, - &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - } - - /* a now contains the used right border for the cell */ - cell->border[RIGHT].style = a.style; - cell->border[RIGHT].c = a.c; - cell->border[RIGHT].width = FIXTOINT(nscss_len2px(len_ctx, - a.width, a.unit, cell->style)); -} - -/** - * Calculate used values of border-bottom-{style,color,width} - * - * \param len_ctx Length conversion context - * \param cell Table cell to consider - */ -void table_used_bottom_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell) -{ - struct border a, b; - box_type a_src, b_src; - struct box *row = cell->parent; - unsigned int rows = cell->rows; - - /* Initialise to computed bottom border for cell */ - a.style = css_computed_border_bottom_style(cell->style); - css_computed_border_bottom_color(cell->style, &a.c); - css_computed_border_bottom_width(cell->style, &a.width, &a.unit); - a.width = nscss_len2px(len_ctx, a.width, a.unit, cell->style); - a.unit = CSS_UNIT_PX; - a_src = BOX_TABLE_CELL; - - while (rows-- > 0 && row != NULL) - row = row->next; - - /** \todo Can cells span row groups? */ - - if (row != NULL) { - /* Cell is not at bottom edge of table -- no bottom border */ - a.style = CSS_BORDER_STYLE_NONE; - a.width = 0; - a.unit = CSS_UNIT_PX; - } else { - /* Cell at bottom of table, so consider row and row group */ - struct box *row = cell->parent; - struct box *group = row->parent; - struct box *table = group->parent; - - /* Bottom border of row */ - b.style = css_computed_border_bottom_style(row->style); - b.color = css_computed_border_bottom_color(row->style, &b.c); - css_computed_border_bottom_width(row->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - - /* Row group -- consider its bottom border */ - b.style = css_computed_border_bottom_style(group->style); - b.color = css_computed_border_bottom_color(group->style, &b.c); - css_computed_border_bottom_width(group->style, - &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW_GROUP; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - a_src = b_src; - } - - /* The table itself -- consider its bottom border */ - b.style = css_computed_border_bottom_style(table->style); - b.color = css_computed_border_bottom_color(table->style, &b.c); - css_computed_border_bottom_width(table->style, - &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE; - - if (table_border_is_more_eyecatching(len_ctx, - &a, a_src, &b, b_src)) { - a = b; - } - } - - /* a now contains the used bottom border for the cell */ - cell->border[BOTTOM].style = a.style; - cell->border[BOTTOM].c = a.c; - cell->border[BOTTOM].width = FIXTOINT(nscss_len2px(len_ctx, - a.width, a.unit, cell->style)); -} - -/** - * Determine if a border style is more eyecatching than another - * - * \param len_ctx Length conversion context - * \param a Reference border style - * \param a_src Source of \a a - * \param b Candidate border style - * \param b_src Source of \a b - * \return True if \a b is more eyecatching than \a a - */ -bool table_border_is_more_eyecatching( - const nscss_len_ctx *len_ctx, - const struct border *a, - box_type a_src, - const struct border *b, - box_type b_src) -{ - css_fixed awidth, bwidth; - int impact = 0; - - /* See CSS 2.1 $17.6.2.1 */ - - /* 1 + 2 -- hidden beats everything, none beats nothing */ - if (a->style == CSS_BORDER_STYLE_HIDDEN || - b->style == CSS_BORDER_STYLE_NONE) - return false; - - if (b->style == CSS_BORDER_STYLE_HIDDEN || - a->style == CSS_BORDER_STYLE_NONE) - return true; - - /* 3a -- wider borders beat narrow ones */ - /* The widths must be absolute, which will be the case - * if they've come from a computed style. */ - assert(a->unit != CSS_UNIT_EM && a->unit != CSS_UNIT_EX); - assert(b->unit != CSS_UNIT_EM && b->unit != CSS_UNIT_EX); - awidth = nscss_len2px(len_ctx, a->width, a->unit, NULL); - bwidth = nscss_len2px(len_ctx, b->width, b->unit, NULL); - - if (awidth < bwidth) - return true; - else if (bwidth < awidth) - return false; - - /* 3b -- sort by style */ - switch (a->style) { - case CSS_BORDER_STYLE_DOUBLE: impact++; /* Fall through */ - case CSS_BORDER_STYLE_SOLID: impact++; /* Fall through */ - case CSS_BORDER_STYLE_DASHED: impact++; /* Fall through */ - case CSS_BORDER_STYLE_DOTTED: impact++; /* Fall through */ - case CSS_BORDER_STYLE_RIDGE: impact++; /* Fall through */ - case CSS_BORDER_STYLE_OUTSET: impact++; /* Fall through */ - case CSS_BORDER_STYLE_GROOVE: impact++; /* Fall through */ - case CSS_BORDER_STYLE_INSET: impact++; /* Fall through */ - default: - break; - } - - switch (b->style) { - case CSS_BORDER_STYLE_DOUBLE: impact--; /* Fall through */ - case CSS_BORDER_STYLE_SOLID: impact--; /* Fall through */ - case CSS_BORDER_STYLE_DASHED: impact--; /* Fall through */ - case CSS_BORDER_STYLE_DOTTED: impact--; /* Fall through */ - case CSS_BORDER_STYLE_RIDGE: impact--; /* Fall through */ - case CSS_BORDER_STYLE_OUTSET: impact--; /* Fall through */ - case CSS_BORDER_STYLE_GROOVE: impact--; /* Fall through */ - case CSS_BORDER_STYLE_INSET: impact--; /* Fall through */ - default: - break; - } - - if (impact < 0) - return true; - else if (impact > 0) - return false; - - /* 4a -- sort by origin */ - impact = 0; - - /** \todo COL/COL_GROUP */ - switch (a_src) { - case BOX_TABLE_CELL: impact++; /* Fall through */ - case BOX_TABLE_ROW: impact++; /* Fall through */ - case BOX_TABLE_ROW_GROUP: impact++; /* Fall through */ - case BOX_TABLE: impact++; /* Fall through */ - default: - break; - } - - /** \todo COL/COL_GROUP */ - switch (b_src) { - case BOX_TABLE_CELL: impact--; /* Fall through */ - case BOX_TABLE_ROW: impact--; /* Fall through */ - case BOX_TABLE_ROW_GROUP: impact--; /* Fall through */ - case BOX_TABLE: impact--; /* Fall through */ - default: - break; - } - - if (impact < 0) - return true; - else if (impact > 0) - return false; - - /* 4b -- furthest left (if direction: ltr) and towards top wins */ - /** \todo Currently assumes b satisifies this */ - return true; -} - -/****************************************************************************** - * Helpers for top border collapsing * - ******************************************************************************/ - -/** - * Process a table - * - * \param len_ctx Length conversion context - * \param table Table to process - * \param a Current border style for cell - * \param a_src Source of \a a - * - * \post \a a will be updated with most eyecatching style - * \post \a a_src will be updated also - */ -void table_cell_top_process_table( - const nscss_len_ctx *len_ctx, - struct box *table, - struct border *a, - box_type *a_src) -{ - struct border b; - box_type b_src; - - /* Top border of table */ - b.style = css_computed_border_top_style(table->style); - b.color = css_computed_border_top_color(table->style, &b.c); - css_computed_border_top_width(table->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, table->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE; - - if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } -} - -/** - * Process a group - * - * \param len_ctx Length conversion context - * \param cell Cell being considered - * \param group Group to process - * \param a Current border style for cell - * \param a_src Source of \a a - * \return true if group has non-empty rows, false otherwise - * - * \post \a a will be updated with most eyecatching style - * \post \a a_src will be updated also - */ -bool table_cell_top_process_group( - const nscss_len_ctx *len_ctx, - struct box *cell, - struct box *group, - struct border *a, - box_type *a_src) -{ - struct border b; - box_type b_src; - - /* Bottom border of group */ - b.style = css_computed_border_bottom_style(group->style); - b.color = css_computed_border_bottom_color(group->style, &b.c); - css_computed_border_bottom_width(group->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW_GROUP; - - if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } - - if (group->last != NULL) { - /* Process rows in group, starting with last */ - struct box *row = group->last; - - while (table_cell_top_process_row(len_ctx, cell, row, - a, a_src) == false) { - if (row->prev == NULL) { - return false; - } else { - row = row->prev; - } - } - } else { - /* Group is empty, so consider its top border */ - b.style = css_computed_border_top_style(group->style); - b.color = css_computed_border_top_color(group->style, &b.c); - css_computed_border_top_width(group->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, group->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW_GROUP; - - if (table_border_is_more_eyecatching(len_ctx, - a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } - - return false; - } - - return true; -} - -/** - * Process a row - * - * \param len_ctx Length conversion context - * \param cell Cell being considered - * \param row Row to process - * \param a Current border style for cell - * \param a_src Source of \a a - * \return true if row has cells, false otherwise - * - * \post \a a will be updated with most eyecatching style - * \post \a a_src will be updated also - */ -bool table_cell_top_process_row( - const nscss_len_ctx *len_ctx, - struct box *cell, - struct box *row, - struct border *a, - box_type *a_src) -{ - struct border b; - box_type b_src; - - /* Bottom border of row */ - b.style = css_computed_border_bottom_style(row->style); - b.color = css_computed_border_bottom_color(row->style, &b.c); - css_computed_border_bottom_width(row->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW; - - if (table_border_is_more_eyecatching(len_ctx, a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } - - if (row->children == NULL) { - /* Row is empty, so consider its top border */ - b.style = css_computed_border_top_style(row->style); - b.color = css_computed_border_top_color(row->style, &b.c); - css_computed_border_top_width(row->style, &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, b.width, b.unit, row->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_ROW; - - if (table_border_is_more_eyecatching(len_ctx, - a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } - - return false; - } else { - /* Process cells that are directly above the cell being - * considered. They may not be in this row, but in one of the - * rows above it in the case where rowspan > 1. */ - struct box *c; - bool processed = false; - - while (processed == false) { - for (c = row->children; c != NULL; c = c->next) { - /* Ignore cells to the left */ - if (c->start_column + c->columns - 1 < - cell->start_column) - continue; - /* Ignore cells to the right */ - if (c->start_column > cell->start_column + - cell->columns - 1) - continue; - - /* Flag that we've processed a cell */ - processed = true; - - /* Consider bottom border */ - b.style = css_computed_border_bottom_style( - c->style); - b.color = css_computed_border_bottom_color( - c->style, &b.c); - css_computed_border_bottom_width(c->style, - &b.width, &b.unit); - b.width = nscss_len2px(len_ctx, - b.width, b.unit, c->style); - b.unit = CSS_UNIT_PX; - b_src = BOX_TABLE_CELL; - - if (table_border_is_more_eyecatching(len_ctx, - a, *a_src, &b, b_src)) { - *a = b; - *a_src = b_src; - } - } - - if (processed == false) { - /* There must be a preceding row */ - assert(row->prev != NULL); - - row = row->prev; - } - } - } - - return true; -} - diff --git a/render/table.h b/render/table.h deleted file mode 100644 index 2eeffe699..000000000 --- a/render/table.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2005 James Bursa - * Copyright 2005 Richard Wilson - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** \file - * Table processing and layout (interface). - */ - -#ifndef _NETSURF_RENDER_TABLE_H_ -#define _NETSURF_RENDER_TABLE_H_ - -#include - -struct box; - -bool table_calculate_column_types( - const nscss_len_ctx *len_ctx, - struct box *table); -void table_used_border_for_cell( - const nscss_len_ctx *len_ctx, - struct box *cell); - -#endif diff --git a/render/textplain.c b/render/textplain.c deleted file mode 100644 index 0036eb5c0..000000000 --- a/render/textplain.c +++ /dev/null @@ -1,1351 +0,0 @@ -/* - * Copyright 2006 James Bursa - * Copyright 2006 Adrian Lees - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * - * plain text content handling implementation. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "utils/corestrings.h" -#include "utils/http.h" -#include "utils/log.h" -#include "utils/messages.h" -#include "utils/utils.h" -#include "utils/utf8.h" -#include "netsurf/content.h" -#include "netsurf/keypress.h" -#include "netsurf/browser_window.h" -#include "netsurf/plotters.h" -#include "netsurf/layout.h" -#include "content/content_protected.h" -#include "content/hlcache.h" -#include "css/utils.h" -#include "utils/nsoption.h" -#include "desktop/search.h" -#include "desktop/selection.h" -#include "desktop/gui_internal.h" - -#include "render/search.h" -#include "render/textplain.h" -#include "render/html.h" -#include "render/search.h" - -struct textplain_line { - size_t start; - size_t length; -}; - -typedef struct textplain_content { - struct content base; - - lwc_string *encoding; - void *inputstream; - char *utf8_data; - size_t utf8_data_size; - size_t utf8_data_allocated; - unsigned long physical_line_count; - struct textplain_line *physical_line; - int formatted_width; - struct browser_window *bw; - - struct selection sel; /** Selection state */ - - /** Context for free text search, or NULL if none */ - struct search_context *search; - /** Current search string, or NULL if none */ - char *search_string; -} textplain_content; - - -#define CHUNK 32768 /* Must be a power of 2 */ -#define MARGIN 4 - -#define TAB_WIDTH 8 /* must be power of 2 currently */ -#define TEXT_SIZE 10 * FONT_SIZE_SCALE /* Unscaled text size in pt */ - -static plot_font_style_t textplain_style = { - .family = PLOT_FONT_FAMILY_MONOSPACE, - .size = TEXT_SIZE, - .weight = 400, - .flags = FONTF_NONE, - .background = 0xffffff, - .foreground = 0x000000, -}; - -static int textplain_tab_width = 256; /* try for a sensible default */ - -static lwc_string *textplain_default_charset; - - -/** - * Clean up after the text content handler - */ -static void textplain_fini(void) -{ - if (textplain_default_charset != NULL) { - lwc_string_unref(textplain_default_charset); - textplain_default_charset = NULL; - } -} - - -/** - * Work around feature in libparserutils - * - * if the client provides an encoding up front, but does not provide a - * charset detection callback, then libparserutils will replace the - * provided encoding with UTF-8. This breaks our input handling. - * - * Avoid this by providing a callback that does precisely nothing, - * thus preserving whatever charset information we decided on in - * textplain_create. - */ -static parserutils_error -textplain_charset_hack(const uint8_t *data, - size_t len, - uint16_t *mibenum, - uint32_t *source) -{ - return PARSERUTILS_OK; -} - - -/** - * setup plain text render. - * - * \param[in] c content object. - * \param[in] encoding the encoding of the content. - * \return NSERROR_OK else appropriate error code. - */ -static nserror -textplain_create_internal(textplain_content *c, lwc_string *encoding) -{ - char *utf8_data; - parserutils_inputstream *stream; - parserutils_error error; - - textplain_style.size = (nsoption_int(font_size) * FONT_SIZE_SCALE) / 10; - - utf8_data = malloc(CHUNK); - if (utf8_data == NULL) - goto no_memory; - - error = parserutils_inputstream_create(lwc_string_data(encoding), 0, - textplain_charset_hack, &stream); - if (error == PARSERUTILS_BADENCODING) { - /* Fall back to Windows-1252 */ - error = parserutils_inputstream_create("Windows-1252", 0, - textplain_charset_hack, &stream); - } - if (error != PARSERUTILS_OK) { - free(utf8_data); - goto no_memory; - } - - c->encoding = lwc_string_ref(encoding); - c->inputstream = stream; - c->utf8_data = utf8_data; - c->utf8_data_size = 0; - c->utf8_data_allocated = CHUNK; - c->physical_line = 0; - c->physical_line_count = 0; - c->formatted_width = 0; - c->bw = NULL; - - selection_prepare(&c->sel, (struct content *)c, false); - - return NSERROR_OK; - -no_memory: - content_broadcast_errorcode(&c->base, NSERROR_NOMEM); - - return NSERROR_NOMEM; -} - - -/** - * Create a CONTENT_TEXTPLAIN. - */ -static nserror -textplain_create(const content_handler *handler, - lwc_string *imime_type, - const http_parameter *params, - llcache_handle *llcache, - const char *fallback_charset, - bool quirks, - struct content **c) -{ - textplain_content *text; - nserror error; - lwc_string *encoding; - - text = calloc(1, sizeof(textplain_content)); - if (text == NULL) { - return NSERROR_NOMEM; - } - - error = content__init(&text->base, handler, imime_type, params, - llcache, fallback_charset, quirks); - if (error != NSERROR_OK) { - free(text); - return error; - } - - error = http_parameter_list_find_item(params, corestring_lwc_charset, - &encoding); - if (error != NSERROR_OK) { - encoding = lwc_string_ref(textplain_default_charset); - } - - error = textplain_create_internal(text, encoding); - if (error != NSERROR_OK) { - lwc_string_unref(encoding); - free(text); - return error; - } - - lwc_string_unref(encoding); - - *c = (struct content *) text; - - return NSERROR_OK; -} - - -/** - * copy utf8 encoded data - */ -static bool -textplain_copy_utf8_data(textplain_content *c, const uint8_t *buf, size_t len) -{ - if (c->utf8_data_size + len >= c->utf8_data_allocated) { - /* Compute next multiple of chunk above the required space */ - size_t allocated; - char *utf8_data; - - allocated = (c->utf8_data_size + len + CHUNK - 1) & ~(CHUNK - 1); - utf8_data = realloc(c->utf8_data, allocated); - if (utf8_data == NULL) - return false; - - c->utf8_data = utf8_data; - c->utf8_data_allocated = allocated; - } - - memcpy(c->utf8_data + c->utf8_data_size, buf, len); - c->utf8_data_size += len; - - return true; -} - - -/** - * drain input - */ -static bool -textplain_drain_input(textplain_content *c, - parserutils_inputstream *stream, - parserutils_error terminator) -{ - static const uint8_t *u_fffd = (const uint8_t *) "\xef\xbf\xfd"; - const uint8_t *ch; - size_t chlen, offset = 0; - - while (parserutils_inputstream_peek(stream, offset, &ch, &chlen) != - terminator) { - /* Replace all instances of NUL with U+FFFD */ - if (chlen == 1 && *ch == 0) { - if (offset > 0) { - /* Obtain pointer to start of input data */ - parserutils_inputstream_peek(stream, 0, - &ch, &chlen); - /* Copy from it up to the start of the NUL */ - if (textplain_copy_utf8_data(c, ch, - offset) == false) - return false; - } - - /* Emit U+FFFD */ - if (textplain_copy_utf8_data(c, u_fffd, 3) == false) - return false; - - /* Advance inputstream past the NUL we just read */ - parserutils_inputstream_advance(stream, offset + 1); - /* Reset the read offset */ - offset = 0; - } else { - /* Accumulate input */ - offset += chlen; - - if (offset > CHUNK) { - /* Obtain pointer to start of input data */ - parserutils_inputstream_peek(stream, 0, - &ch, &chlen); - - /* Emit the data we've read */ - if (textplain_copy_utf8_data(c, ch, - offset) == false) - return false; - - /* Advance the inputstream */ - parserutils_inputstream_advance(stream, offset); - /* Reset the read offset */ - offset = 0; - } - } - } - - if (offset > 0) { - /* Obtain pointer to start of input data */ - parserutils_inputstream_peek(stream, 0, &ch, &chlen); - /* Emit any data remaining */ - if (textplain_copy_utf8_data(c, ch, offset) == false) - return false; - - /* Advance the inputstream past the data we've read */ - parserutils_inputstream_advance(stream, offset); - } - - return true; -} - - -/** - * Process data for CONTENT_TEXTPLAIN. - */ -static bool -textplain_process_data(struct content *c, const char *data, unsigned int size) -{ - textplain_content *text = (textplain_content *) c; - parserutils_inputstream *stream = text->inputstream; - parserutils_error error; - - error = parserutils_inputstream_append(stream, - (const uint8_t *) data, size); - if (error != PARSERUTILS_OK) { - goto no_memory; - } - - if (textplain_drain_input(text, stream, PARSERUTILS_NEEDDATA) == false) - goto no_memory; - - return true; - -no_memory: - content_broadcast_errorcode(c, NSERROR_NOMEM); - return false; -} - - -/** - * Convert a CONTENT_TEXTPLAIN for display. - */ -static bool textplain_convert(struct content *c) -{ - textplain_content *text = (textplain_content *) c; - parserutils_inputstream *stream = text->inputstream; - parserutils_error error; - - error = parserutils_inputstream_append(stream, NULL, 0); - if (error != PARSERUTILS_OK) { - return false; - } - - if (textplain_drain_input(text, stream, PARSERUTILS_EOF) == false) - return false; - - parserutils_inputstream_destroy(stream); - text->inputstream = NULL; - - content_set_ready(c); - content_set_done(c); - content_set_status(c, messages_get("Done")); - - return true; -} - - -/** - * Calculate the line height, in pixels - * - * \return Line height, in pixels - */ -static float textplain_line_height(void) -{ - /* Size is in points, so convert to pixels. - * Then use a constant line height of 1.2 x font size. - */ - return FIXTOFLT(FDIV((FMUL(FLTTOFIX(1.2), FMUL(nscss_screen_dpi, INTTOFIX((textplain_style.size / FONT_SIZE_SCALE))))), F_72)); -} - - -/** - * Reformat a CONTENT_TEXTPLAIN to a new width. - */ -static void textplain_reformat(struct content *c, int width, int height) -{ - textplain_content *text = (textplain_content *) c; - char *utf8_data = text->utf8_data; - size_t utf8_data_size = text->utf8_data_size; - unsigned long line_count = 0; - struct textplain_line *line = text->physical_line; - struct textplain_line *line1; - size_t i, space, col; - size_t columns = 80; - int character_width; - size_t line_start; - nserror res; - - NSLOG(netsurf, INFO, "content %p w:%d h:%d", c, width, height); - - /* compute available columns (assuming monospaced font) - use 8 - * characters for better accuracy - */ - res = guit->layout->width(&textplain_style, - "ABCDEFGH", 8, - &character_width); - if (res != NSERROR_OK) { - return; - } - - columns = (width - MARGIN - MARGIN) * 8 / character_width; - textplain_tab_width = (TAB_WIDTH * character_width) / 8; - - text->formatted_width = width; - - text->physical_line_count = 0; - - if (!line) { - text->physical_line = line = - malloc(sizeof(struct textplain_line) * (1024 + 3)); - if (!line) - goto no_memory; - } - - line[line_count++].start = line_start = 0; - space = 0; - i = 0; - col = 0; - while (i < utf8_data_size) { - size_t csize; /* number of bytes in character */ - uint32_t chr; - bool term; - size_t next_col; - parserutils_error perror; - - perror = parserutils_charset_utf8_to_ucs4((const uint8_t *)utf8_data + i, utf8_data_size - i, &chr, &csize); - if (perror != PARSERUTILS_OK) { - chr = 0xfffd; - } - - term = (chr == '\n' || chr == '\r'); - - next_col = col + 1; - - if (chr == '\t') { - next_col = (next_col + TAB_WIDTH - 1) & ~(TAB_WIDTH - 1); - } - - if (term || next_col >= columns) { - if (line_count % 1024 == 0) { - line1 = realloc(line, - sizeof(struct textplain_line) * - (line_count + 1024 + 3)); - if (!line1) - goto no_memory; - text->physical_line = line = line1; - } - - if (term) { - line[line_count-1].length = i - line_start; - - /* skip second char of CR/LF or LF/CR pair */ - if (i + 1 < utf8_data_size && - utf8_data[i+1] != utf8_data[i] && - (utf8_data[i+1] == '\n' || - utf8_data[i+1] == '\r')) { - i++; - } - } else { - if (space) { - /* break at last space in line */ - i = space; - line[line_count-1].length = (i + 1) - line_start; - } else - line[line_count-1].length = i - line_start; - } - - line[line_count++].start = line_start = i + 1; - col = 0; - space = 0; - } else { - col++; - if (chr == ' ') - space = i; - } - i += csize; - } - line[line_count-1].length = i - line[line_count-1].start; - line[line_count].start = utf8_data_size; - - text->physical_line_count = line_count; - c->width = width; - c->height = line_count * textplain_line_height() + MARGIN + MARGIN; - - return; - -no_memory: - NSLOG(netsurf, INFO, "out of memory (line_count %lu)", line_count); - return; -} - - -/** - * Destroy a CONTENT_TEXTPLAIN and free all resources it owns. - */ - -static void textplain_destroy(struct content *c) -{ - textplain_content *text = (textplain_content *) c; - - lwc_string_unref(text->encoding); - - if (text->inputstream != NULL) { - parserutils_inputstream_destroy(text->inputstream); - } - - if (text->physical_line != NULL) { - free(text->physical_line); - } - - if (text->utf8_data != NULL) { - free(text->utf8_data); - } -} - - -static nserror textplain_clone(const struct content *old, struct content **newc) -{ - const textplain_content *old_text = (textplain_content *) old; - textplain_content *text; - nserror error; - const char *data; - unsigned long size; - - text = calloc(1, sizeof(textplain_content)); - if (text == NULL) - return NSERROR_NOMEM; - - error = content__clone(old, &text->base); - if (error != NSERROR_OK) { - content_destroy(&text->base); - return error; - } - - /* Simply replay create/process/convert */ - error = textplain_create_internal(text, old_text->encoding); - if (error != NSERROR_OK) { - content_destroy(&text->base); - return error; - } - - data = content__get_source_data(&text->base, &size); - if (size > 0) { - if (textplain_process_data(&text->base, data, size) == false) { - content_destroy(&text->base); - return NSERROR_NOMEM; - } - } - - if (old->status == CONTENT_STATUS_READY || - old->status == CONTENT_STATUS_DONE) { - if (textplain_convert(&text->base) == false) { - content_destroy(&text->base); - return NSERROR_CLONE_FAILED; - } - } - - return NSERROR_OK; -} - - -static content_type textplain_content_type(void) -{ - return CONTENT_TEXTPLAIN; -} - - -/** - * Handle mouse clicks and movements in a TEXTPLAIN content window. - * - * \param c content of type textplain - * \param bw browser window - * \param mouse mouse state on action - * \param x coordinate of mouse - * \param y coordinate of mouse - */ -static void -textplain_mouse_action(struct content *c, - struct browser_window *bw, - browser_mouse_state mouse, - int x, int y) -{ - textplain_content *text = (textplain_content *) c; - browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; - union content_msg_data msg_data; - const char *status = 0; - size_t idx; - int dir = 0; - - browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); - - idx = textplain_offset_from_coords(c, x, y, dir); - if (selection_click(&text->sel, mouse, idx)) { - - if (selection_dragging(&text->sel)) { - browser_window_set_drag_type(bw, - DRAGGING_SELECTION, NULL); - status = messages_get("Selecting"); - } - - } else { - if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) { - browser_window_page_drag_start(bw, x, y); - pointer = BROWSER_POINTER_MOVE; - } - } - - msg_data.explicit_status_text = status; - content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); - - msg_data.pointer = pointer; - content_broadcast(c, CONTENT_MSG_POINTER, &msg_data); -} - - -/** - * Handle mouse tracking (including drags) in a TEXTPLAIN content window. - * - * \param c content of type textplain - * \param bw browser window - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - */ -static void -textplain_mouse_track(struct content *c, - struct browser_window *bw, - browser_mouse_state mouse, - int x, int y) -{ - textplain_content *text = (textplain_content *) c; - - if (browser_window_get_drag_type(bw) == DRAGGING_SELECTION && !mouse) { - int dir = -1; - size_t idx; - - if (selection_dragging_start(&text->sel)) - dir = 1; - - idx = textplain_offset_from_coords(c, x, y, dir); - selection_track(&text->sel, mouse, idx); - - browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); - } - - switch (browser_window_get_drag_type(bw)) { - - case DRAGGING_SELECTION: { - int dir = -1; - size_t idx; - - if (selection_dragging_start(&text->sel)) dir = 1; - - idx = textplain_offset_from_coords(c, x, y, dir); - selection_track(&text->sel, mouse, idx); - } - break; - - default: - textplain_mouse_action(c, bw, mouse, x, y); - break; - } -} - - -/** - * Handle keypresses. - * - * \param c content of type CONTENT_TEXTPLAIN - * \param key The UCS4 character codepoint - * \return true if key handled, false otherwise - */ -static bool textplain_keypress(struct content *c, uint32_t key) -{ - textplain_content *text = (textplain_content *) c; - struct selection *sel = &text->sel; - - switch (key) { - case NS_KEY_COPY_SELECTION: - selection_copy_to_clipboard(sel); - return true; - - case NS_KEY_CLEAR_SELECTION: - selection_clear(sel, true); - return true; - - case NS_KEY_SELECT_ALL: - selection_select_all(sel); - return true; - - case NS_KEY_ESCAPE: - if (selection_defined(sel)) { - selection_clear(sel, true); - return true; - } - - /* if there's no selection, leave Escape for the caller */ - return false; - } - - return false; -} - - -/** - * Terminate a search. - * - * \param c content of type text - */ -static void textplain_search_clear(struct content *c) -{ - textplain_content *text = (textplain_content *) c; - - assert(c != NULL); - - free(text->search_string); - text->search_string = NULL; - - if (text->search != NULL) { - search_destroy_context(text->search); - } - text->search = NULL; -} - - -/** - * Handle search. - * - * \param c content of type text - * \param gui_data front end private data - * \param flags search flags - * \param string search string - */ -static void textplain_search(struct content *c, void *gui_data, - search_flags_t flags, const char *string) -{ - textplain_content *text = (textplain_content *) c; - - assert(c != NULL); - - if (string != NULL && text->search_string != NULL && - strcmp(string, text->search_string) == 0 && - text->search != NULL) { - /* Continue prev. search */ - search_step(text->search, flags, string); - - } else if (string != NULL) { - /* New search */ - free(text->search_string); - text->search_string = strdup(string); - if (text->search_string == NULL) - return; - - if (text->search != NULL) { - search_destroy_context(text->search); - text->search = NULL; - } - - text->search = search_create_context(c, CONTENT_TEXTPLAIN, - gui_data); - - if (text->search == NULL) - return; - - search_step(text->search, flags, string); - - } else { - /* Clear search */ - textplain_search_clear(c); - - free(text->search_string); - text->search_string = NULL; - } -} - - -/** - * Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot). - * - * x, y, clip_[xy][01] are in target coordinates. - * - * \param c content of type CONTENT_TEXTPLAIN - * \param data redraw data for this content redraw - * \param clip current clip region - * \param ctx current redraw context - * \return true if successful, false otherwise - */ -static bool -textplain_redraw(struct content *c, - struct content_redraw_data *data, - const struct rect *clip, - const struct redraw_context *ctx) -{ - textplain_content *text = (textplain_content *) c; - struct browser_window *bw = text->bw; - char *utf8_data = text->utf8_data; - long lineno; - int x = data->x; - int y = data->y; - unsigned long line_count = text->physical_line_count; - float line_height = textplain_line_height(); - float scaled_line_height = line_height * data->scale; - long line0 = (clip->y0 - y * data->scale) / scaled_line_height - 1; - long line1 = (clip->y1 - y * data->scale) / scaled_line_height + 1; - struct textplain_line *line = text->physical_line; - size_t length; - plot_style_t *plot_style_highlight; - nserror res; - - if (line0 < 0) - line0 = 0; - if (line1 < 0) - line1 = 0; - if (line_count < (unsigned long) line0) - line0 = line_count; - if (line_count < (unsigned long) line1) - line1 = line_count; - if (line1 < line0) - line1 = line0; - - res = ctx->plot->rectangle(ctx, plot_style_fill_white, clip); - if (res != NSERROR_OK) { - return false; - } - - if (!line) - return true; - - /* choose a suitable background colour for any highlighted text */ - if ((data->background_colour & 0x808080) == 0x808080) - plot_style_highlight = plot_style_fill_black; - else - plot_style_highlight = plot_style_fill_white; - - /* Set up font plot style */ - textplain_style.background = data->background_colour; - - x = (x + MARGIN) * data->scale; - y = (y + MARGIN) * data->scale; - for (lineno = line0; lineno != line1; lineno++) { - const char *text_d = utf8_data + line[lineno].start; - int tab_width = textplain_tab_width * data->scale; - size_t offset = 0; - int tx = x; - - if (!tab_width) tab_width = 1; - - length = line[lineno].length; - if (!length) - continue; - - while (offset < length) { - size_t next_offset = offset; - int width; - int ntx; - nserror res; - - while (next_offset < length && text_d[next_offset] != '\t') - next_offset = utf8_next(text_d, length, next_offset); - - if (!text_redraw(text_d + offset, next_offset - offset, - line[lineno].start + offset, 0, - &textplain_style, - tx, y + (lineno * scaled_line_height), - clip, line_height, data->scale, false, - (struct content *)text, &text->sel, - text->search, ctx)) - return false; - - if (next_offset >= length) - break; - - res = guit->layout->width(&textplain_style, - &text_d[offset], - next_offset - offset, - &width); - /* locate end of string and align to next tab position */ - if (res == NSERROR_OK) { - tx += (int)(width * data->scale); - } - - ntx = x + ((1 + (tx - x) / tab_width) * tab_width); - - /* if the tab character lies within the - * selection, if any, then we must draw it as - * a filled rectangle so that it's consistent - * with background of the selected text - */ - - if (bw) { - unsigned tab_ofst = line[lineno].start + next_offset; - struct selection *sel = &text->sel; - bool highlighted = false; - - if (selection_defined(sel)) { - unsigned start_idx, end_idx; - if (selection_highlighted(sel, - tab_ofst, - tab_ofst + 1, - &start_idx, - &end_idx)) - highlighted = true; - } - - if (!highlighted && (text->search != NULL)) { - unsigned start_idx, end_idx; - if (search_term_highlighted(c, - tab_ofst, - tab_ofst + 1, - &start_idx, - &end_idx, - text->search)) - highlighted = true; - } - - if (highlighted) { - struct rect rect; - rect.x0 = tx; - rect.y0 = y + (lineno * scaled_line_height); - rect.x1 = ntx; - rect.y1 = rect.y0 + scaled_line_height; - res = ctx->plot->rectangle(ctx, - plot_style_highlight, - &rect); - if (res != NSERROR_OK) { - return false; - } - } - } - - offset = next_offset + 1; - tx = ntx; - } - } - - return true; -} - - -/** - * Handle a window containing a CONTENT_TEXTPLAIN being opened. - */ -static void -textplain_open(struct content *c, - struct browser_window *bw, - struct content *page, - struct object_params *params) -{ - textplain_content *text = (textplain_content *) c; - - text->bw = bw; - - /* text selection */ - selection_init(&text->sel, NULL, NULL); -} - - -/** - * Handle a window containing a CONTENT_TEXTPLAIN being closed. - */ -static void textplain_close(struct content *c) -{ - textplain_content *text = (textplain_content *) c; - - if (text->search != NULL) { - search_destroy_context(text->search); - } - - text->bw = NULL; -} - - -/** - * Return an textplain content's selection context - */ -static char *textplain_get_selection(struct content *c) -{ - textplain_content *text = (textplain_content *) c; - - return selection_get_copy(&text->sel); -} - - -/** - * Convert a character offset within a line of text into the - * horizontal co-ordinate - * - * The conversion takes into account the font being used and any tabs - * in the text - * - * \param text line of text - * \param offset char offset within text - * \param length line length - * \return x ordinate - */ -static int -textplain_coord_from_offset(const char *text, size_t offset, size_t length) -{ - int x = 0; - - while (offset > 0) { - size_t next_offset = 0; - int tx; - - while (next_offset < offset && text[next_offset] != '\t') { - next_offset = utf8_next(text, length, next_offset); - } - - guit->layout->width(&textplain_style, text, next_offset, &tx); - - x += tx; - - if (next_offset >= offset) - break; - - /* align to next tab boundary */ - next_offset++; - x = (1 + (x / textplain_tab_width)) * textplain_tab_width; - offset -= next_offset; - text += next_offset; - length -= next_offset; - } - - return x; -} - - -/** - * plain text content handler table - */ -static const content_handler textplain_content_handler = { - .fini = textplain_fini, - .create = textplain_create, - .process_data = textplain_process_data, - .data_complete = textplain_convert, - .reformat = textplain_reformat, - .destroy = textplain_destroy, - .mouse_track = textplain_mouse_track, - .mouse_action = textplain_mouse_action, - .keypress = textplain_keypress, - .search = textplain_search, - .search_clear = textplain_search_clear, - .redraw = textplain_redraw, - .open = textplain_open, - .close = textplain_close, - .get_selection = textplain_get_selection, - .clone = textplain_clone, - .type = textplain_content_type, - .no_share = true, -}; - - -/* exported interface documented in render/textplain.h */ -nserror textplain_init(void) -{ - lwc_error lerror; - nserror error; - - lerror = lwc_intern_string("Windows-1252", - SLEN("Windows-1252"), - &textplain_default_charset); - if (lerror != lwc_error_ok) { - return NSERROR_NOMEM; - } - - error = content_factory_register_handler("text/plain", - &textplain_content_handler); - if (error != NSERROR_OK) { - lwc_string_unref(textplain_default_charset); - } - - return error; -} - - -/* exported interface documented in render/textplain.h */ -unsigned long textplain_line_count(struct content *c) -{ - textplain_content *text = (textplain_content *) c; - - assert(c != NULL); - - return text->physical_line_count; -} - - -/* exported interface documented in render/textplain.h */ -size_t textplain_size(struct content *c) -{ - textplain_content *text = (textplain_content *) c; - - assert(c != NULL); - - return text->utf8_data_size; -} - - -/* exported interface documented in render/textplain.h */ -size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir) -{ - textplain_content *textc = (textplain_content *) c; - float line_height = textplain_line_height(); - struct textplain_line *line; - const char *text; - unsigned nlines; - size_t length; - int idx; - - assert(c != NULL); - - y = (int)((float)(y - MARGIN) / line_height); - x -= MARGIN; - - nlines = textc->physical_line_count; - if (!nlines) - return 0; - - if (y <= 0) y = 0; - else if ((unsigned)y >= nlines) - y = nlines - 1; - - line = &textc->physical_line[y]; - text = textc->utf8_data + line->start; - length = line->length; - idx = 0; - - while (x > 0) { - size_t next_offset = 0; - int width = INT_MAX; - - while (next_offset < length && text[next_offset] != '\t') { - next_offset = utf8_next(text, length, next_offset); - } - - if (next_offset < length) { - guit->layout->width(&textplain_style, - text, - next_offset, - &width); - } - - if (x <= width) { - int pixel_offset; - size_t char_offset; - - guit->layout->position(&textplain_style, - text, next_offset, x, - &char_offset, &pixel_offset); - - idx += char_offset; - break; - } - - x -= width; - length -= next_offset; - text += next_offset; - idx += next_offset; - - /* check if it's within the tab */ - width = textplain_tab_width - (width % textplain_tab_width); - if (x <= width) break; - - x -= width; - length--; - text++; - idx++; - } - - return line->start + idx; -} - - -/* exported interface documented in render/textplain.h */ -void -textplain_coords_from_range(struct content *c, - unsigned start, - unsigned end, - struct rect *r) -{ - textplain_content *text = (textplain_content *) c; - float line_height = textplain_line_height(); - char *utf8_data; - struct textplain_line *line; - unsigned lineno = 0; - unsigned nlines; - - assert(c != NULL); - assert(start <= end); - assert(end <= text->utf8_data_size); - - utf8_data = text->utf8_data; - nlines = text->physical_line_count; - line = text->physical_line; - - /* find start */ - lineno = textplain_find_line(c, start); - - r->y0 = (int)(MARGIN + lineno * line_height); - - if (lineno + 1 <= nlines || line[lineno + 1].start >= end) { - /* \todo - it may actually be more efficient just to - * run forwards most of the time - */ - - /* find end */ - lineno = textplain_find_line(c, end); - - r->x0 = 0; - r->x1 = text->formatted_width; - } else { - /* single line */ - const char *text = utf8_data + line[lineno].start; - - r->x0 = textplain_coord_from_offset(text, - start - line[lineno].start, - line[lineno].length); - - r->x1 = textplain_coord_from_offset(text, - end - line[lineno].start, - line[lineno].length); - } - - r->y1 = (int)(MARGIN + (lineno + 1) * line_height); -} - - -/* exported interface documented in render/textplain.h */ -char * -textplain_get_line(struct content *c, - unsigned lineno, - size_t *poffset, - size_t *plen) -{ - textplain_content *text = (textplain_content *) c; - struct textplain_line *line; - - assert(c != NULL); - - if (lineno >= text->physical_line_count) - return NULL; - line = &text->physical_line[lineno]; - - *poffset = line->start; - *plen = line->length; - return text->utf8_data + line->start; -} - - -/* exported interface documented in render/textplain.h */ -int textplain_find_line(struct content *c, unsigned offset) -{ - textplain_content *text = (textplain_content *) c; - struct textplain_line *line; - int nlines; - int lineno = 0; - - assert(c != NULL); - - line = text->physical_line; - nlines = text->physical_line_count; - - if (offset > text->utf8_data_size) { - return -1; - } - -/* \todo - implement binary search here */ - while (lineno < nlines && line[lineno].start < offset) { - lineno++; - } - if (line[lineno].start > offset) { - lineno--; - } - - return lineno; -} - - -/* exported interface documented in render/textplain.h */ -char * -textplain_get_raw_data(struct content *c, - unsigned start, - unsigned end, - size_t *plen) -{ - textplain_content *text = (textplain_content *) c; - size_t utf8_size; - - assert(c != NULL); - - utf8_size = text->utf8_data_size; - - /* any text at all? */ - if (!utf8_size) return NULL; - - /* clamp to valid offset range */ - if (start >= utf8_size) start = utf8_size; - if (end >= utf8_size) end = utf8_size; - - *plen = end - start; - - return text->utf8_data + start; -} - - -/* exported interface documented in render/textplain.h */ -struct browser_window *textplain_get_browser_window(struct content *c) -{ - textplain_content *text = (textplain_content *) c; - - assert(c != NULL); - assert(c->handler == &textplain_content_handler); - - return text->bw; -} diff --git a/render/textplain.h b/render/textplain.h deleted file mode 100644 index 0f0128e56..000000000 --- a/render/textplain.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2006 James Bursa - * Copyright 2006 Adrian Lees - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file - * - * Interface to content handler for plain text. - */ - -#ifndef NETSURF_RENDER_TEXTPLAIN_H -#define NETSURF_RENDER_TEXTPLAIN_H - -#include -#include "netsurf/mouse.h" - -struct content; -struct hlcache_handle; -struct http_parameter; -struct rect; - -/** - * Initialise the text content handler - * - * \return NSERROR_OK on success else appropriate error code. - */ -nserror textplain_init(void); - - -/** - * Retrieve number of lines in content - * - * \param[in] c Content to retrieve line count from - * \return Number of lines - */ -unsigned long textplain_line_count(struct content *c); - - -/** - * Retrieve the size (in bytes) of text data - * - * \param[in] c Content to retrieve size of - * \return Size, in bytes, of data - */ -size_t textplain_size(struct content *c); - - -/** - * Return byte offset within UTF8 textplain content. - * - * given the co-ordinates of a point within a textplain content. 'dir' - * specifies the direction in which to search (-1 = above-left, +1 = - * below-right) if the co-ordinates are not contained within a line. - * - * \param[in] c content of type CONTENT_TEXTPLAIN - * \param[in] x x ordinate of point - * \param[in] y y ordinate of point - * \param[in] dir direction of search if not within line - * \return byte offset of character containing (or nearest to) point - */ -size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir); - - -/** - * Given a range of byte offsets within a UTF8 textplain content, - * return a box that fully encloses the text - * - * \param[in] c content of type CONTENT_TEXTPLAIN - * \param[in] start byte offset of start of text range - * \param[in] end byte offset of end - * \param[out] r rectangle to be completed - */ -void textplain_coords_from_range(struct content *c, - unsigned start, unsigned end, struct rect *r); - -/** - * Return a pointer to the requested line of text. - * - * \param[in] c content of type CONTENT_TEXTPLAIN - * \param[in] lineno line number - * \param[out] poffset receives byte offset of line start within text - * \param[out] plen receives length of returned line - * \return pointer to text, or NULL if invalid line number - */ -char *textplain_get_line(struct content *c, unsigned lineno, - size_t *poffset, size_t *plen); - - -/** - * Find line number of byte in text - * - * Given a byte offset within the text, return the line number - * of the line containing that offset. - * - * \param[in] c content of type CONTENT_TEXTPLAIN - * \param[in] offset byte offset within textual representation - * \return line number, or -1 if offset invalid (larger than size) - */ -int textplain_find_line(struct content *c, unsigned offset); - - -/** - * Return a pointer to the raw UTF-8 data, as opposed to the reformatted - * text to fit the window width. Thus only hard newlines are preserved - * in the saved/copied text of a selection. - * - * \param[in] c content of type CONTENT_TEXTPLAIN - * \param[in] start starting byte offset within UTF-8 text - * \param[in] end ending byte offset - * \param[out] plen receives validated length - * \return pointer to text, or NULL if no text - */ -char *textplain_get_raw_data(struct content *c, unsigned start, unsigned end, size_t *plen); - - -/** - * Get the browser window containing a textplain content - * - * \param[in] c text/plain content - * \return the browser window - */ -struct browser_window *textplain_get_browser_window(struct content *c); - -#endif -- cgit v1.2.3