/contrib/network/netsurf/netsurf/render/box.c |
---|
0,0 → 1,1116 |
/* |
* Copyright 2005-2007 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk> |
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Box tree manipulation (implementation). |
*/ |
#include <assert.h> |
#include <stdbool.h> |
#include <stdio.h> |
#include <string.h> |
#include <dom/dom.h> |
#include "content/content_protected.h" |
#include "content/hlcache.h" |
#include "css/css.h" |
#include "css/utils.h" |
#include "css/dump.h" |
#include "desktop/scrollbar.h" |
#include "desktop/options.h" |
#include "render/box.h" |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "utils/log.h" |
#include "utils/talloc.h" |
#include "utils/utils.h" |
static bool box_contains_point(struct box *box, int x, int y, bool *physically); |
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); |
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); |
#define box_is_float(box) (box->type == BOX_FLOAT_LEFT || \ |
box->type == BOX_FLOAT_RIGHT) |
/** |
* Allocator |
* |
* \param ptr Pointer to reallocate, or NULL for new allocation |
* \param size Number of bytes requires |
* \param pw Allocation context |
* \return Pointer to allocated block, or NULL on failure |
*/ |
void *box_style_alloc(void *ptr, size_t len, void *pw) |
{ |
if (len == 0) { |
free(ptr); |
return NULL; |
} |
return realloc(ptr, len); |
} |
/** |
* 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) |
{ |
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); |
} |
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->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; |
} |
/** |
* Find the boxes at a point. |
* |
* \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(box, x, y, &box_x, &box_y))) { |
* // process box |
* } |
* \endcode |
*/ |
struct box *box_at_point(struct box *box, const int x, const int y, |
int *box_x, int *box_y) |
{ |
int bx = *box_x, by = *box_y; |
struct box *child, *sibling; |
bool physically; |
assert(box); |
/* consider floats first, since they will often overlap other boxes */ |
for (child = box->float_children; child; child = child->next_float) { |
if (box_contains_point(child, x - bx, y - by, &physically)) { |
*box_x = bx + child->x - |
scrollbar_get_offset(child->scroll_x); |
*box_y = by + child->y - |
scrollbar_get_offset(child->scroll_y); |
if (physically) |
return child; |
else |
return box_at_point(child, x, y, box_x, box_y); |
} |
} |
non_float_children: |
/* non-float children */ |
for (child = box->children; child; child = child->next) { |
if (box_is_float(child)) |
continue; |
if (box_contains_point(child, x - bx, y - by, &physically)) { |
*box_x = bx + child->x - |
scrollbar_get_offset(child->scroll_x); |
*box_y = by + child->y - |
scrollbar_get_offset(child->scroll_y); |
if (physically) |
return child; |
else |
return box_at_point(child, x, y, box_x, box_y); |
} |
} |
/* marker boxes */ |
if (box->list_marker) { |
if (box_contains_point(box->list_marker, x - bx, y - by, |
&physically)) { |
*box_x = bx + box->list_marker->x; |
*box_y = by + box->list_marker->y; |
return box->list_marker; |
} |
} |
/* siblings and siblings of ancestors */ |
while (box) { |
if (box_is_float(box)) { |
bx -= box->x - scrollbar_get_offset(box->scroll_x); |
by -= box->y - scrollbar_get_offset(box->scroll_y); |
for (sibling = box->next_float; sibling; |
sibling = sibling->next_float) { |
if (box_contains_point(sibling, |
x - bx, y - by, &physically)) { |
*box_x = bx + sibling->x - |
scrollbar_get_offset( |
sibling->scroll_x); |
*box_y = by + sibling->y - |
scrollbar_get_offset( |
sibling->scroll_y); |
if (physically) |
return sibling; |
else |
return box_at_point(sibling, |
x, y, |
box_x, box_y); |
} |
} |
/* ascend to float's parent */ |
do { |
box = box->parent; |
} while (!box->float_children); |
/* process non-float children of float's parent */ |
goto non_float_children; |
} else { |
bx -= box->x - scrollbar_get_offset(box->scroll_x); |
by -= box->y - scrollbar_get_offset(box->scroll_y); |
for (sibling = box->next; sibling; |
sibling = sibling->next) { |
if (box_is_float(sibling)) |
continue; |
if (box_contains_point(sibling, x - bx, y - by, |
&physically)) { |
*box_x = bx + sibling->x - |
scrollbar_get_offset( |
sibling->scroll_x); |
*box_y = by + sibling->y - |
scrollbar_get_offset( |
sibling->scroll_y); |
if (physically) |
return sibling; |
else |
return box_at_point(sibling, |
x, y, |
box_x, box_y); |
} |
} |
box = box->parent; |
} |
} |
return 0; |
} |
/** |
* Determine if a point lies within a box. |
* |
* \param box box to consider |
* \param x coordinate relative to box parent |
* \param y coordinate relative to box parent |
* \param physically if function returning true, physically is set true if |
* 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 returning 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(). |
*/ |
bool box_contains_point(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->x - box->border[LEFT].width, |
.y0 = box->y - box->border[TOP].width, |
.x1 = box->x + box->padding[LEFT] + box->width + |
box->border[RIGHT].width + |
box->padding[RIGHT], |
.y1 = box->y + 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( |
css_rect.left, css_rect.lunit, |
box->style)); |
} |
if (css_rect.top_auto == false) { |
r.y0 += FIXTOINT(nscss_len2px( |
css_rect.top, css_rect.tunit, |
box->style)); |
} |
if (css_rect.right_auto == false) { |
r.x1 = box->x - box->border[LEFT].width + |
FIXTOINT(nscss_len2px( |
css_rect.right, |
css_rect.runit, |
box->style)); |
} |
if (css_rect.bottom_auto == false) { |
r.y1 = box->y - box->border[TOP].width + |
FIXTOINT(nscss_len2px( |
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 (box->x <= x + box->border[LEFT].width && |
x < box->x + box->padding[LEFT] + box->width + |
box->border[RIGHT].width + box->padding[RIGHT] && |
box->y <= y + box->border[TOP].width && |
y < box->y + box->padding[TOP] + box->height + |
box->border[BOTTOM].width + box->padding[BOTTOM]) { |
*physically = true; |
return true; |
} |
if (box->list_marker && box->list_marker->x <= x + |
box->list_marker->border[LEFT].width && |
x < box->list_marker->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 <= y + |
box->list_marker->border[TOP].width && |
y < box->list_marker->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(box->style) == |
CSS_OVERFLOW_VISIBLE) || !box->style) { |
if (box->x + box->descendant_x0 <= x && |
x < box->x + box->descendant_x1 && |
box->y + box->descendant_y0 <= y && |
y < box->y + box->descendant_y1) { |
*physically = false; |
return true; |
} |
} |
return false; |
} |
/** |
* 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 ny_yd distance to nearest text box found |
* updated if box is nearer than existing nearest |
* \return true if mouse point is inside box |
*/ |
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 ny_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 |
*/ |
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) |
{ |
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 (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, " <%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->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, ")"); |
} |
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); |
} |
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); |
} |
} |
/** |
* 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) { |
LOG(("malloc failed")); |
warn_user("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))) |
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) { |
LOG(("malloc failed")); |
warn_user("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)) |
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; |
} |
/contrib/network/netsurf/netsurf/render/box.h |
---|
0,0 → 1,351 |
/* |
* Copyright 2005 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
typedef unsigned int uint32_t; |
/** \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 |
* <h1>Example Heading</h1> |
* <p>Example paragraph <em>with emphasised text</em> etc.</p> \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 <limits.h> |
#include <stdbool.h> |
#include <stdio.h> |
#include "css/css.h" |
#include "utils/nsurl.h" |
#include "utils/types.h" |
struct box; |
struct browser_window; |
struct column; |
struct object_params; |
struct object_param; |
struct html_content; |
struct dom_node; |
#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 */ |
} 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; |
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; |
/** 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 { |
nsurl *data; |
char *type; |
char *codetype; |
nsurl *codebase; |
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; |
void *box_style_alloc(void *ptr, size_t len, void *pw); |
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); |
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(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 box_extract_link(const char *rel, nsurl *base, 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 box_construct_init(void); |
void box_construct_fini(void); |
nserror dom_to_box(struct dom_node *n, struct html_content *c, |
box_construct_complete_cb cb); |
bool box_normalise_block(struct box *block, struct html_content *c); |
#endif |
/contrib/network/netsurf/netsurf/render/box_construct.c |
---|
0,0 → 1,3312 |
/* |
* Copyright 2005 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk> |
* Copyright 2006 Richard Wilson <info@tinct.net> |
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Conversion of XML tree to box tree (implementation). |
*/ |
#include <assert.h> |
#include <ctype.h> |
#include <stdio.h> |
#include <stdbool.h> |
#include <stdlib.h> |
#include <string.h> |
#include <strings.h> |
#include "utils/config.h" |
#include "content/content_protected.h" |
#include "css/css.h" |
#include "css/utils.h" |
#include "css/select.h" |
#include "desktop/options.h" |
#include "render/box.h" |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "utils/corestrings.h" |
#include "utils/locale.h" |
#include "utils/log.h" |
#include "utils/messages.h" |
#include "utils/schedule.h" |
#include "utils/talloc.h" |
#include "utils/url.h" |
#include "utils/utils.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, 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_input_text(BOX_SPECIAL_PARAMS, bool password); |
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); |
static struct frame_dimension *box_parse_multi_lengths(const char *s, |
unsigned int *count); |
/* 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; |
schedule(0, (schedule_callback_fn) convert_xml_to_box, ctx); |
return NSERROR_OK; |
} |
/* 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*/ |
}; |
/** Key for box userdata on DOM elements (== '__ns_box') */ |
static dom_string *kstr_box_key; |
static dom_string *kstr_title; |
static dom_string *kstr_id; |
static dom_string *kstr_colspan; |
static dom_string *kstr_rowspan; |
static dom_string *kstr_style; |
static dom_string *kstr_href; |
static dom_string *kstr_name; |
static dom_string *kstr_target; |
static dom_string *kstr_alt; |
static dom_string *kstr_src; |
static dom_string *kstr_codebase; |
static dom_string *kstr_classid; |
static dom_string *kstr_data; |
static dom_string *kstr_rows; |
static dom_string *kstr_cols; |
static dom_string *kstr_border; |
static dom_string *kstr_frameborder; |
static dom_string *kstr_bordercolor; |
static dom_string *kstr_noresize; |
static dom_string *kstr_scrolling; |
static dom_string *kstr_marginwidth; |
static dom_string *kstr_marginheight; |
static dom_string *kstr_type; |
static dom_string *kstr_value; |
static dom_string *kstr_selected; |
nserror box_construct_init(void) |
{ |
dom_exception err; |
err = dom_string_create_interned((const uint8_t *) "__ns_box", |
SLEN("__ns_box"), &kstr_box_key); |
if (err != DOM_NO_ERR || kstr_box_key == NULL) |
goto error; |
#define BOX_CONSTRUCT_STRING_INTERN(NAME) \ |
err = dom_string_create_interned((const uint8_t *)#NAME, \ |
sizeof(#NAME) - 1, \ |
&kstr_##NAME ); \ |
if ((err != DOM_NO_ERR) || (kstr_##NAME == NULL)) \ |
goto error |
BOX_CONSTRUCT_STRING_INTERN(title); |
BOX_CONSTRUCT_STRING_INTERN(id); |
BOX_CONSTRUCT_STRING_INTERN(colspan); |
BOX_CONSTRUCT_STRING_INTERN(rowspan); |
BOX_CONSTRUCT_STRING_INTERN(style); |
BOX_CONSTRUCT_STRING_INTERN(href); |
BOX_CONSTRUCT_STRING_INTERN(name); |
BOX_CONSTRUCT_STRING_INTERN(target); |
BOX_CONSTRUCT_STRING_INTERN(alt); |
BOX_CONSTRUCT_STRING_INTERN(src); |
BOX_CONSTRUCT_STRING_INTERN(codebase); |
BOX_CONSTRUCT_STRING_INTERN(classid); |
BOX_CONSTRUCT_STRING_INTERN(data); |
BOX_CONSTRUCT_STRING_INTERN(rows); |
BOX_CONSTRUCT_STRING_INTERN(cols); |
BOX_CONSTRUCT_STRING_INTERN(border); |
BOX_CONSTRUCT_STRING_INTERN(frameborder); |
BOX_CONSTRUCT_STRING_INTERN(bordercolor); |
BOX_CONSTRUCT_STRING_INTERN(noresize); |
BOX_CONSTRUCT_STRING_INTERN(scrolling); |
BOX_CONSTRUCT_STRING_INTERN(marginwidth); |
BOX_CONSTRUCT_STRING_INTERN(marginheight); |
BOX_CONSTRUCT_STRING_INTERN(type); |
BOX_CONSTRUCT_STRING_INTERN(value); |
BOX_CONSTRUCT_STRING_INTERN(selected); |
#undef BOX_CONSTRUCT_STRING_INTERN |
return NSERROR_OK; |
error: |
return NSERROR_NOMEM; |
} |
void box_construct_fini(void) |
{ |
if (kstr_box_key != NULL) { |
dom_string_unref(kstr_box_key); |
kstr_box_key = NULL; |
} |
#define BOX_CONSTRUCT_STRING_UNREF(NAME) \ |
do { \ |
if (kstr_##NAME != NULL) { \ |
dom_string_unref(kstr_##NAME); \ |
kstr_##NAME = NULL; \ |
} \ |
} while (0) \ |
BOX_CONSTRUCT_STRING_UNREF(title); |
BOX_CONSTRUCT_STRING_UNREF(id); |
BOX_CONSTRUCT_STRING_UNREF(colspan); |
BOX_CONSTRUCT_STRING_UNREF(rowspan); |
BOX_CONSTRUCT_STRING_UNREF(style); |
BOX_CONSTRUCT_STRING_UNREF(href); |
BOX_CONSTRUCT_STRING_UNREF(name); |
BOX_CONSTRUCT_STRING_UNREF(target); |
BOX_CONSTRUCT_STRING_UNREF(alt); |
BOX_CONSTRUCT_STRING_UNREF(src); |
BOX_CONSTRUCT_STRING_UNREF(codebase); |
BOX_CONSTRUCT_STRING_UNREF(classid); |
BOX_CONSTRUCT_STRING_UNREF(data); |
BOX_CONSTRUCT_STRING_UNREF(rows); |
BOX_CONSTRUCT_STRING_UNREF(cols); |
BOX_CONSTRUCT_STRING_UNREF(border); |
BOX_CONSTRUCT_STRING_UNREF(frameborder); |
BOX_CONSTRUCT_STRING_UNREF(bordercolor); |
BOX_CONSTRUCT_STRING_UNREF(noresize); |
BOX_CONSTRUCT_STRING_UNREF(scrolling); |
BOX_CONSTRUCT_STRING_UNREF(marginwidth); |
BOX_CONSTRUCT_STRING_UNREF(marginheight); |
BOX_CONSTRUCT_STRING_UNREF(type); |
BOX_CONSTRUCT_STRING_UNREF(value); |
BOX_CONSTRUCT_STRING_UNREF(selected); |
#undef BOX_CONSTRUCT_DOM_STRING_UNREF |
} |
static inline struct box *box_for_node(dom_node *n) |
{ |
struct box *box = NULL; |
dom_exception err; |
err = dom_node_get_user_data(n, kstr_box_key, (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->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 */ |
schedule(0, (schedule_callback_fn) convert_xml_to_box, ctx); |
} |
/** |
* Construct a list marker box |
* |
* \param box Box to attach marker to |
* \param title Current title attribute |
* \param content Containing content |
* \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) { |
if (last->list_marker != NULL) |
break; |
last = last->last; |
} |
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; |
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 */ |
if (css_computed_display(style, box_is_root(n)) == CSS_DISPLAY_BLOCK) { |
/* currently only support block level elements */ |
/** \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[css_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; |
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; |
} |
styles = box_get_style(ctx->content, props.parent_style, ctx->n); |
if (styles == NULL) |
return false; |
/* Extract title attribute, if present */ |
err = dom_element_get_attribute(ctx->n, kstr_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, kstr_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, kstr_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, kstr_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) && |
(css_computed_display_static(box->style) == |
CSS_DISPLAY_INLINE || |
css_computed_display_static(box->style) == |
CSS_DISPLAY_INLINE_BLOCK || |
css_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 { |
/* Normal mapping */ |
box->type = box_map[css_computed_display(box->style, |
props.node_is_root)]; |
} |
/* Handle the :before pseudo element */ |
box_construct_generate(ctx->n, ctx->content, box, |
box->styles->styles[CSS_PSEUDO_ELEMENT_BEFORE]); |
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; |
} |
if (box->type == BOX_NONE || css_computed_display(box->style, |
props.node_is_root) == CSS_DISPLAY_NONE) { |
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, kstr_box_key, 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)) { |
/* 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 && |
"Root box must not be inline or floated"); |
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) |
return false; |
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 (css_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 (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 { |
/* 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 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, dom_node *n) |
{ |
dom_string *s; |
dom_exception err; |
int pseudo_element; |
css_error error; |
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, kstr_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(content_get_url(&c->base)), |
c->quirks != DOM_DOCUMENT_QUIRKS_MODE_NONE, |
box_style_alloc, NULL); |
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; |
/* Select partial style for element */ |
styles = nscss_get_style(&ctx, n, CSS_MEDIA_SCREEN, inline_style, |
box_style_alloc, NULL); |
/* No longer need inline style */ |
if (inline_style != NULL) |
css_stylesheet_destroy(inline_style); |
/* Failed selecting partial style -- bail out */ |
if (styles == NULL) |
return NULL; |
/* If there's a parent style, compose with partial to obtain |
* complete computed style for element */ |
if (parent_style != NULL) { |
/* Complete the computed style, by composing with the parent |
* element's style */ |
error = css_computed_style_compose(parent_style, |
styles->styles[CSS_PSEUDO_ELEMENT_NONE], |
nscss_compute_font_size, NULL, |
styles->styles[CSS_PSEUDO_ELEMENT_NONE]); |
if (error != CSS_OK) { |
css_select_results_destroy(styles); |
return NULL; |
} |
} |
for (pseudo_element = CSS_PSEUDO_ELEMENT_NONE + 1; |
pseudo_element < CSS_PSEUDO_ELEMENT_COUNT; |
pseudo_element++) { |
if (pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LETTER || |
pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LINE) |
/* TODO: Handle first-line and first-letter pseudo |
* element computed style completion */ |
continue; |
if (styles->styles[pseudo_element] == NULL) |
/* There were no rules concerning this pseudo element */ |
continue; |
/* Complete the pseudo element's computed style, by composing |
* with the base element's style */ |
error = css_computed_style_compose( |
styles->styles[CSS_PSEUDO_ELEMENT_NONE], |
styles->styles[pseudo_element], |
nscss_compute_font_size, NULL, |
styles->styles[pseudo_element]); |
if (error != CSS_OK) { |
/* TODO: perhaps this shouldn't be quite so |
* catastrophic? */ |
css_select_results_destroy(styles); |
return NULL; |
} |
} |
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] = ls_toupper(s[i]); |
break; |
case CSS_TEXT_TRANSFORM_LOWERCASE: |
for (i = 0; i < len; ++i) |
if ((unsigned char) s[i] < 0x80) |
s[i] = ls_tolower(s[i]); |
break; |
case CSS_TEXT_TRANSFORM_CAPITALIZE: |
if ((unsigned char) s[0] < 0x80) |
s[0] = ls_toupper(s[0]); |
for (i = 1; i < len; ++i) |
if ((unsigned char) s[i] < 0x80 && |
ls_isspace(s[i - 1])) |
s[i] = ls_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, kstr_href, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
ok = box_extract_link(dom_string_data(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, kstr_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, kstr_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 |
* <base target=...>, 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 && css_computed_display(box->style, |
box_is_root(n)) == CSS_DISPLAY_NONE) |
return true; |
/* handle alt text */ |
err = dom_element_get_attribute(n, kstr_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, kstr_src, &s); |
if (err != DOM_NO_ERR || s == NULL) |
return true; |
if (box_extract_link(dom_string_data(s), content->base_url, |
&url) == false) { |
dom_string_unref(s); |
return false; |
} |
dom_string_unref(s); |
if (url == NULL) |
return true; |
/* start fetch */ |
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 (nsoption_bool(enable_javascript)) |
*convert_children = false; |
return true; |
} |
/** |
* Destructor for object_params, for <object> elements |
* |
* \param b 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 && css_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, kstr_codebase, &codebase); |
if (err == DOM_NO_ERR && codebase != NULL) { |
if (box_extract_link(dom_string_data(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, kstr_classid, &classid); |
if (err == DOM_NO_ERR && classid != NULL) { |
if (box_extract_link(dom_string_data(classid), params->codebase, |
¶ms->classid) == false) { |
dom_string_unref(classid); |
return false; |
} |
dom_string_unref(classid); |
} |
err = dom_element_get_attribute(n, kstr_data, &data); |
if (err == DOM_NO_ERR && data != NULL) { |
if (box_extract_link(dom_string_data(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) */ |
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) { |
LOG(("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 b 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; |
} |
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, kstr_rows, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
row_height = box_parse_multi_lengths(dom_string_data(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, kstr_cols, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
col_width = box_parse_multi_lengths(dom_string_data(s), &cols); |
dom_string_unref(s); |
if (col_width == NULL) |
return false; |
} else { |
col_width = calloc(1, sizeof(struct frame_dimension)); |
if (col_width == NULL) |
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, kstr_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, kstr_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|<named colour>" to control |
*all children */ |
err = dom_element_get_attribute(n, kstr_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 = 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 = 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_node_unref(c); |
return false; |
} |
dom_node_unref(c); |
c = next; |
} else { |
/* Got a FRAME or FRAMESET element */ |
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, kstr_src, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
box_extract_link(dom_string_data(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, kstr_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, kstr_noresize, |
&frame->no_resize); |
err = dom_element_get_attribute(c, kstr_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, kstr_scrolling, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_yes)) |
frame->scrolling = SCROLLING_YES; |
else if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_no)) |
frame->scrolling = SCROLLING_NO; |
dom_string_unref(s); |
} |
err = dom_element_get_attribute(c, kstr_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, kstr_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, kstr_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; |
} |
} |
return true; |
} |
/** |
* Destructor for content_html_iframe, for <iframe> elements |
* |
* \param b 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 && css_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, kstr_src, &s); |
if (err != DOM_NO_ERR || s == NULL) |
return true; |
if (box_extract_link(dom_string_data(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 = 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, kstr_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, kstr_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, kstr_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, kstr_scrolling, &s); |
if (err == DOM_NO_ERR && s != NULL) { |
if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_yes)) |
iframe->scrolling = SCROLLING_YES; |
else if (dom_string_caseless_lwc_isequal(s, |
corestring_lwc_no)) |
iframe->scrolling = SCROLLING_NO; |
dom_string_unref(s); |
} |
err = dom_element_get_attribute(n, kstr_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, kstr_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; |
/* Showing iframe, so don't show alternate content */ |
if (convert_children) |
*convert_children = false; |
return true; |
} |
/** |
* 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, kstr_type, &type); |
gadget = html_forms_get_control_for_node(content->forms, n); |
if (gadget == NULL) |
goto no_memory; |
box->gadget = gadget; |
gadget->box = box; |
if (type && dom_string_caseless_lwc_isequal(type, |
corestring_lwc_password)) { |
if (box_input_text(n, content, box, 0, true) == 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 && css_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, kstr_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(n, content, box, 0, false) == 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; |
} |
/** |
* Helper function for box_input(). |
*/ |
bool box_input_text(BOX_SPECIAL_PARAMS, bool password) |
{ |
struct box *inline_container, *inline_box; |
box->type = BOX_INLINE_BLOCK; |
inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->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, |
content->bctx); |
if (!inline_box) |
return false; |
inline_box->type = BOX_TEXT; |
if (password) { |
inline_box->length = strlen(box->gadget->value); |
inline_box->text = talloc_array(content->bctx, char, |
inline_box->length + 1); |
if (!inline_box->text) |
return false; |
memset(inline_box->text, '*', inline_box->length); |
inline_box->text[inline_box->length] = '\0'; |
} else { |
/* replace spaces/TABs with hard spaces to prevent line |
* wrapping */ |
char *text = cnv_space2nbsp(box->gadget->value); |
if (!text) |
return false; |
inline_box->text = talloc_strdup(content->bctx, text); |
free(text); |
if (!inline_box->text) |
return false; |
inline_box->length = strlen(inline_box->text); |
} |
box_add_child(inline_container, inline_box); |
box_add_child(box, inline_container); |
return true; |
} |
/** |
* 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; |
box->gadget = gadget; |
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; |
err = dom_node_get_first_child(n, &c); |
if (err != DOM_NO_ERR) |
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); |
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); |
goto no_memory; |
} |
} 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); |
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); |
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); |
goto no_memory; |
} |
} 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); |
return false; |
} |
dom_node_unref(c); |
c = next; |
} |
if (gadget->data.select.num_items == 0) { |
/* no options: ignore entire select */ |
return true; |
} |
box->type = BOX_INLINE_BLOCK; |
box->gadget = gadget; |
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; |
} |
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, kstr_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, kstr_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) == 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) |
{ |
/* A textarea is an INLINE_BLOCK containing a single INLINE_CONTAINER, |
* which contains the text as runs of TEXT separated by BR. There is |
* at least one TEXT. The first and last boxes are TEXT. |
* Consecutive BR may not be present. These constraints are satisfied |
* by using a 0-length TEXT for blank lines. */ |
const char *current; |
dom_string *area_data = NULL; |
dom_exception err; |
struct box *inline_container, *inline_box, *br_box; |
char *s; |
size_t len; |
box->type = BOX_INLINE_BLOCK; |
box->gadget = html_forms_get_control_for_node(content->forms, n); |
if (box->gadget == NULL) |
return false; |
box->gadget->box = box; |
inline_container = box_create(NULL, 0, false, 0, 0, box->title, 0, |
content->bctx); |
if (inline_container == NULL) |
return false; |
inline_container->type = BOX_INLINE_CONTAINER; |
box_add_child(box, inline_container); |
err = dom_node_get_text_content(n, &area_data); |
if (err != DOM_NO_ERR) |
return false; |
if (area_data != NULL) { |
current = dom_string_data(area_data); |
} else { |
/* No content, or failed reading it: use a blank string */ |
current = ""; |
} |
while (true) { |
/* BOX_TEXT */ |
len = strcspn(current, "\r\n"); |
s = talloc_strndup(content->bctx, current, len); |
if (s == NULL) { |
if (area_data != NULL) |
dom_string_unref(area_data); |
return false; |
} |
inline_box = box_create(NULL, box->style, false, 0, 0, |
box->title, 0, content->bctx); |
if (inline_box == NULL) { |
if (area_data != NULL) |
dom_string_unref(area_data); |
return false; |
} |
inline_box->type = BOX_TEXT; |
inline_box->text = s; |
inline_box->length = len; |
box_add_child(inline_container, inline_box); |
current += len; |
if (current[0] == 0) |
/* finished */ |
break; |
/* BOX_BR */ |
br_box = box_create(NULL, box->style, false, 0, 0, box->title, |
0, content->bctx); |
if (br_box == NULL) { |
if (area_data != NULL) |
dom_string_unref(area_data); |
return false; |
} |
br_box->type = BOX_BR; |
box_add_child(inline_container, br_box); |
if (current[0] == '\r' && current[1] == '\n') |
current += 2; |
else |
current++; |
} |
if (area_data != NULL) |
dom_string_unref(area_data); |
*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 && css_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, kstr_src, &src); |
if (err != DOM_NO_ERR || src == NULL) |
return true; |
if (box_extract_link(dom_string_data(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 */ |
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; |
} |
/** |
* Extract a URL from a relative link, handling junk like whitespace and |
* attempting to read a real URL from "javascript:" links. |
* |
* \param rel relative URL 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 char *rel, nsurl *base, nsurl **result) |
{ |
char *s, *s1, *apos0 = 0, *apos1 = 0, *quot0 = 0, *quot1 = 0; |
unsigned int i, j, end; |
nserror error; |
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] && isspace(rel[i]); i++) |
; |
for (end = strlen(rel); end != i && isspace(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; |
/* 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; |
} |
/** |
* Parse a multi-length-list, as defined by HTML 4.01. |
* |
* \param s string to parse |
* \param count updated to number of entries |
* \return array of struct box_multi_length, or 0 on memory exhaustion |
*/ |
struct frame_dimension *box_parse_multi_lengths(const char *s, |
unsigned int *count) |
{ |
char *end; |
unsigned int i, n; |
struct frame_dimension *length; |
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 (isspace(*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; |
} |
/contrib/network/netsurf/netsurf/render/box_normalise.c |
---|
0,0 → 1,974 |
/* |
* Copyright 2005 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2005 John M Bell <jmb202@ecs.soton.ac.uk> |
* Copyright 2004 Kevin Bagust <kevin.bagust@ntlworld.com> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Box tree normalisation (implementation). |
*/ |
#include <assert.h> |
#include <stdbool.h> |
#include <string.h> |
#include "css/css.h" |
#include "css/select.h" |
#include "render/box.h" |
#include "render/html_internal.h" |
#include "render/table.h" |
#include "utils/log.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; |
/** 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, html_content *c); |
static bool box_normalise_table_spans(struct box *table, |
struct span_info *spans, html_content *c); |
static bool box_normalise_table_row_group(struct box *row_group, |
struct columns *col_info, |
html_content *c); |
static bool box_normalise_table_row(struct box *row, |
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); |
static bool box_normalise_inline_container(struct box *cont, 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 box_pool pool to allocate new boxes in |
* \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, html_content *c) |
{ |
struct box *child; |
struct box *next_child; |
struct box *table; |
css_computed_style *style; |
nscss_select_ctx ctx; |
assert(block != NULL); |
#ifdef BOX_NORMALISE_DEBUG |
LOG(("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 |
LOG(("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, c) == false) |
return false; |
break; |
case BOX_INLINE_CONTAINER: |
if (box_normalise_inline_container(child, c) == false) |
return false; |
break; |
case BOX_TABLE: |
if (box_normalise_table(child, 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, |
box_style_alloc, NULL); |
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, c) == false) |
return false; |
break; |
default: |
assert(0); |
} |
} |
return true; |
} |
bool box_normalise_table(struct box *table, 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); |
#ifdef BOX_NORMALISE_DEBUG |
LOG(("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, |
&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, |
box_style_alloc, NULL); |
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, |
&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 |
LOG(("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, |
box_style_alloc, NULL); |
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, |
box_style_alloc, NULL); |
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, col_info.spans, c) == false) { |
free(col_info.spans); |
return false; |
} |
free(col_info.spans); |
if (table_calculate_column_types(table) == false) |
return false; |
#ifdef BOX_NORMALISE_DEBUG |
LOG(("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 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, 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 col; |
nscss_select_ctx ctx; |
/* 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) { |
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; |
/* rowspan = 0 -> rowspan = rows_left */ |
if (table_cell->rows == 0) |
table_cell->rows = 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, |
box_style_alloc, NULL); |
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--; |
} |
} |
return true; |
} |
bool box_normalise_table_row_group(struct box *row_group, |
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; |
assert(row_group != 0); |
assert(row_group->type == BOX_TABLE_ROW_GROUP); |
#ifdef BOX_NORMALISE_DEBUG |
LOG(("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 */ |
if (box_normalise_table_row(child, 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, |
box_style_alloc, NULL); |
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; |
if (box_normalise_table_row(row, 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 |
LOG(("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, |
box_style_alloc, NULL); |
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; |
/* Keep table's row count in sync */ |
col_info->num_rows++; |
} |
#ifdef BOX_NORMALISE_DEBUG |
LOG(("row_group %p done", row_group)); |
#endif |
return true; |
} |
bool box_normalise_table_row(struct box *row, |
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); |
#ifdef BOX_NORMALISE_DEBUG |
LOG(("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, 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, |
box_style_alloc, NULL); |
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, 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) == 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 |
LOG(("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 |
* \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) |
{ |
unsigned int cell_start_col = col_info->current_column; |
unsigned int cell_end_col; |
unsigned int i; |
struct span_info *spans; |
/* Skip columns with cells spanning from above */ |
while (col_info->spans[cell_start_col].row_span != 0) |
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); |
} |
/* 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, html_content * c) |
{ |
struct box *child; |
struct box *next_child; |
assert(cont != NULL); |
assert(cont->type == BOX_INLINE_CONTAINER); |
#ifdef BOX_NORMALISE_DEBUG |
LOG(("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, 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, |
c) == false) |
return false; |
break; |
case BOX_TABLE: |
if (box_normalise_table(child->children, |
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 |
LOG(("cont %p done", cont)); |
#endif |
return true; |
} |
/contrib/network/netsurf/netsurf/render/font.c |
---|
0,0 → 1,169 |
/* |
* Copyright 2009 John-Mark Bell <jmb@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
#include "css/css.h" |
#include "css/utils.h" |
#include "desktop/options.h" |
#include "render/font.h" |
static plot_font_generic_family_t plot_font_generic_family( |
enum css_font_family_e css); |
static int plot_font_weight(enum css_font_weight_e css); |
static plot_font_flags_t plot_font_flags(enum css_font_style_e style, |
enum css_font_variant_e variant); |
/** |
* Populate a font style using data from a computed CSS style |
* |
* \param css Computed style to consider |
* \param fstyle Font style to populate |
*/ |
void font_plot_style_from_css(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(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; |
} |
/****************************************************************************** |
* Helper functions * |
******************************************************************************/ |
/** |
* Map a generic CSS font family to a generic plot font family |
* |
* \param css Generic CSS font family |
* \return Plot font family |
*/ |
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 |
*/ |
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 |
*/ |
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; |
} |
/contrib/network/netsurf/netsurf/render/font.h |
---|
0,0 → 1,94 |
/* |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2005 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2004 John Tytgat <joty@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Font handling (interface). |
* |
* These functions provide font related services. They all work on UTF-8 strings |
* with lengths given. |
* |
* Note that an interface to painting is not defined here. Painting is |
* redirected through platform-dependent plotters anyway, so there is no gain in |
* abstracting it here. |
*/ |
#ifndef _NETSURF_RENDER_FONT_H_ |
#define _NETSURF_RENDER_FONT_H_ |
#include <stdbool.h> |
#include <stddef.h> |
#include "css/css.h" |
#include "desktop/plot_style.h" |
struct font_functions |
{ |
/** |
* Measure the width of a string. |
* |
* \param fstyle plot style for this text |
* \param string UTF-8 string to measure |
* \param length length of string, in bytes |
* \param width updated to width of string[0..length) |
* \return true on success, false on error and error reported |
*/ |
bool (*font_width)(const plot_font_style_t *fstyle, |
const char *string, size_t length, |
int *width); |
/** |
* Find the position in a string where an x coordinate falls. |
* |
* \param fstyle style for this text |
* \param string UTF-8 string to measure |
* \param length length of string, in bytes |
* \param x x coordinate to search for |
* \param char_offset updated to offset in string of actual_x, [0..length] |
* \param actual_x updated to x coordinate of character closest to x |
* \return true on success, false on error and error reported |
*/ |
bool (*font_position_in_string)(const plot_font_style_t *fstyle, |
const char *string, size_t length, |
int x, size_t *char_offset, int *actual_x); |
/** |
* Find where to split a string to make it fit a width. |
* |
* \param fstyle style for this text |
* \param string UTF-8 string to measure |
* \param length length of string, in bytes |
* \param x width available |
* \param char_offset updated to offset in string of actual_x, [0..length] |
* \param actual_x updated to x coordinate of character closest to x |
* \return true on success, false on error and error reported |
* |
* On exit, [char_offset == 0 || |
* string[char_offset] == ' ' || |
* char_offset == length] |
*/ |
bool (*font_split)(const plot_font_style_t *fstyle, |
const char *string, size_t length, |
int x, size_t *char_offset, int *actual_x); |
}; |
extern const struct font_functions nsfont; |
void font_plot_style_from_css(const css_computed_style *css, |
plot_font_style_t *fstyle); |
#endif |
/contrib/network/netsurf/netsurf/render/form.c |
---|
0,0 → 1,1545 |
/* |
* Copyright 2004 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2004 John Tytgat <joty@netsurf-browser.org> |
* Copyright 2005-9 John-Mark Bell <jmb@netsurf-browser.org> |
* Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> |
* Copyright 2010 Michael Drake <tlsa@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Form handling functions (implementation). |
*/ |
#include <assert.h> |
#include <ctype.h> |
#include <limits.h> |
#include <stdbool.h> |
#include <stdio.h> |
#include <string.h> |
#include <dom/dom.h> |
#include "content/fetch.h" |
#include "content/hlcache.h" |
#include "css/css.h" |
#include "css/utils.h" |
#include "desktop/browser.h" |
#include "desktop/gui.h" |
#include "desktop/mouse.h" |
#include "desktop/knockout.h" |
#include "desktop/plot_style.h" |
#include "desktop/plotters.h" |
#include "desktop/scrollbar.h" |
#include "render/box.h" |
#include "render/font.h" |
#include "render/form.h" |
#include "render/html.h" |
#include "render/html_internal.h" |
#include "render/layout.h" |
#include "utils/log.h" |
#include "utils/messages.h" |
#include "utils/talloc.h" |
#include "utils/url.h" |
#include "utils/utf8.h" |
#include "utils/utils.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_textarea_value(struct form_control *textarea); |
static char *form_acceptable_charset(struct form *form); |
static char *form_encode_item(const char *item, 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); |
/** |
* 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 structure, 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) |
{ |
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; |
} |
/** |
* Free a form, and any controls it owns. |
* |
* \param form The form to free |
* |
* \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. |
*/ |
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); |
} |
/** |
* 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) |
{ |
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) |
{ |
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) |
{ |
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; |
free(option->text); |
free(option->value); |
free(option); |
} |
if (control->data.select.menu != NULL) |
form_free_select_menu(control); |
} |
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 |
* \return true on success, false on memory exhaustion |
*/ |
bool form_add_option(struct form_control *control, char *value, char *text, |
bool selected) |
{ |
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++; |
return true; |
} |
/** |
* Identify 'successful' controls. |
* |
* 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) |
* |
* \param form form to search for successful controls |
* \param submit_button control used to submit the form, if any |
* \param successful_controls updated to point to linked list of |
* fetch_multipart_data, 0 if no controls |
* \return true on success, false on memory exhaustion |
* |
* See HTML 4.01 section 17.13.2. |
*/ |
bool form_successful_controls(struct form *form, |
struct form_control *submit_button, |
struct fetch_multipart_data **successful_controls) |
{ |
struct form_control *control; |
struct form_option *option; |
struct fetch_multipart_data sentinel, *last_success, *success_new; |
char *value = NULL; |
bool had_submit = false; |
char *charset; |
last_success = &sentinel; |
sentinel.next = NULL; |
charset = form_acceptable_charset(form); |
if (!charset) |
return false; |
#define ENCODE_ITEM(i) form_encode_item((i), charset, form->document_charset) |
for (control = form->controls; control; control = control->next) { |
/* ignore disabled controls */ |
if (control->disabled) |
continue; |
/* ignore controls with no name */ |
if (!control->name) |
continue; |
switch (control->type) { |
case GADGET_HIDDEN: |
case GADGET_TEXTBOX: |
case GADGET_PASSWORD: |
if (control->value) |
value = ENCODE_ITEM(control->value); |
else |
value = ENCODE_ITEM(""); |
if (!value) { |
LOG(("failed to duplicate value" |
"'%s' for control %s", |
control->value, |
control->name)); |
goto no_memory; |
} |
break; |
case GADGET_RADIO: |
case GADGET_CHECKBOX: |
/* ignore checkboxes and radio buttons which |
* aren't selected */ |
if (!control->selected) |
continue; |
if (control->value) |
value = ENCODE_ITEM(control->value); |
else |
value = ENCODE_ITEM("on"); |
if (!value) { |
LOG(("failed to duplicate" |
"value '%s' for" |
"control %s", |
control->value, |
control->name)); |
goto no_memory; |
} |
break; |
case GADGET_SELECT: |
/* select */ |
for (option = control->data.select.items; |
option != NULL; |
option = option->next) { |
if (!option->selected) |
continue; |
success_new = |
malloc(sizeof(*success_new)); |
if (!success_new) { |
LOG(("malloc failed")); |
goto no_memory; |
} |
success_new->file = false; |
success_new->name = |
ENCODE_ITEM(control->name); |
success_new->value = |
ENCODE_ITEM(option->value); |
success_new->next = NULL; |
last_success->next = success_new; |
last_success = success_new; |
if (!success_new->name || |
!success_new->value) { |
LOG(("strdup failed")); |
goto no_memory; |
} |
} |
continue; |
break; |
case GADGET_TEXTAREA: |
{ |
char *v2; |
/* textarea */ |
value = form_textarea_value(control); |
if (!value) { |
LOG(("failed handling textarea")); |
goto no_memory; |
} |
if (value[0] == 0) { |
free(value); |
continue; |
} |
v2 = ENCODE_ITEM(value); |
if (!v2) { |
LOG(("failed handling textarea")); |
free(value); |
goto no_memory; |
} |
free(value); |
value = v2; |
} |
break; |
case GADGET_IMAGE: { |
/* image */ |
size_t len; |
char *name; |
if (control != submit_button) |
/* only the activated submit button |
* is successful */ |
continue; |
name = ENCODE_ITEM(control->name); |
if (name == NULL) |
goto no_memory; |
len = strlen(name) + 3; |
/* x */ |
success_new = malloc(sizeof(*success_new)); |
if (!success_new) { |
free(name); |
LOG(("malloc failed")); |
goto no_memory; |
} |
success_new->file = false; |
success_new->name = malloc(len); |
success_new->value = malloc(20); |
if (!success_new->name || |
!success_new->value) { |
free(success_new->name); |
free(success_new->value); |
free(success_new); |
free(name); |
LOG(("malloc failed")); |
goto no_memory; |
} |
sprintf(success_new->name, "%s.x", name); |
sprintf(success_new->value, "%i", |
control->data.image.mx); |
success_new->next = 0; |
last_success->next = success_new; |
last_success = success_new; |
/* y */ |
success_new = malloc(sizeof(*success_new)); |
if (!success_new) { |
free(name); |
LOG(("malloc failed")); |
goto no_memory; |
} |
success_new->file = false; |
success_new->name = malloc(len); |
success_new->value = malloc(20); |
if (!success_new->name || |
!success_new->value) { |
free(success_new->name); |
free(success_new->value); |
free(success_new); |
free(name); |
LOG(("malloc failed")); |
goto no_memory; |
} |
sprintf(success_new->name, "%s.y", name); |
sprintf(success_new->value, "%i", |
control->data.image.my); |
success_new->next = 0; |
last_success->next = success_new; |
last_success = success_new; |
free(name); |
continue; |
break; |
} |
case GADGET_SUBMIT: |
if (!submit_button && !had_submit) |
/* no submit button specified, so |
* use first declared in form */ |
had_submit = true; |
else if (control != submit_button) |
/* only the activated submit button |
* is successful */ |
continue; |
if (control->value) |
value = ENCODE_ITEM(control->value); |
else |
value = ENCODE_ITEM(""); |
if (!value) { |
LOG(("failed to duplicate value" |
"'%s' for control %s", |
control->value, |
control->name)); |
goto no_memory; |
} |
break; |
case GADGET_RESET: |
/* ignore reset */ |
continue; |
break; |
case GADGET_FILE: |
/* file */ |
/* Handling of blank file entries is |
* implementation defined - we're perfectly |
* within our rights to treat it as an |
* unsuccessful control. Unfortunately, every |
* other browser submits the field with |
* a blank filename and no content. So, |
* that's what we have to do, too. |
*/ |
success_new = malloc(sizeof(*success_new)); |
if (!success_new) { |
LOG(("malloc failed")); |
goto no_memory; |
} |
success_new->file = true; |
success_new->name = ENCODE_ITEM(control->name); |
success_new->value = |
ENCODE_ITEM(control->value ? |
control->value : ""); |
success_new->next = 0; |
last_success->next = success_new; |
last_success = success_new; |
if (!success_new->name || |
!success_new->value) { |
LOG(("strdup failed")); |
goto no_memory; |
} |
continue; |
break; |
case GADGET_BUTTON: |
/* Ignore it */ |
continue; |
break; |
default: |
assert(0); |
break; |
} |
success_new = malloc(sizeof(*success_new)); |
if (!success_new) { |
LOG(("malloc failed")); |
goto no_memory; |
} |
success_new->file = false; |
success_new->name = ENCODE_ITEM(control->name); |
success_new->value = value; |
success_new->next = NULL; |
last_success->next = success_new; |
last_success = success_new; |
if (!success_new->name) { |
LOG(("failed to duplicate name '%s'", |
control->name)); |
goto no_memory; |
} |
} |
*successful_controls = sentinel.next; |
return true; |
no_memory: |
warn_user("NoMemory", 0); |
fetch_multipart_data_destroy(sentinel.next); |
return false; |
#undef ENCODE_ITEM |
} |
/** |
* Find the value for a textarea control. |
* |
* \param textarea control of type GADGET_TEXTAREA |
* \return the value as a UTF-8 string on heap, or 0 on memory exhaustion |
*/ |
char *form_textarea_value(struct form_control *textarea) |
{ |
unsigned int len = 0; |
char *value, *s; |
struct box *text_box; |
/* Textarea may have no associated box if styled with display: none */ |
if (textarea->box == NULL) { |
/* Return the empty string: caller treats this as a |
* non-successful control. */ |
return strdup(""); |
} |
/* find required length */ |
for (text_box = textarea->box->children->children; text_box; |
text_box = text_box->next) { |
if (text_box->type == BOX_TEXT) |
len += text_box->length + 1; |
else /* BOX_BR */ |
len += 2; |
} |
/* construct value */ |
s = value = malloc(len + 1); |
if (!s) |
return NULL; |
for (text_box = textarea->box->children->children; text_box; |
text_box = text_box->next) { |
if (text_box->type == BOX_TEXT) { |
strncpy(s, text_box->text, text_box->length); |
s += text_box->length; |
if (text_box->next && text_box->next->type != BOX_BR) |
/* only add space if this isn't |
* the last box on a line (or in the area) */ |
*s++ = ' '; |
} else { /* BOX_BR */ |
*s++ = '\r'; |
*s++ = '\n'; |
} |
} |
*s = 0; |
return value; |
} |
/** |
* 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; |
url_func_result 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, 0, true, NULL, &name); |
if (url_err == URL_FUNC_NOMEM) { |
free(s); |
return NULL; |
} |
assert(url_err == URL_FUNC_OK); |
url_err = url_escape(control->value, 0, true, NULL, &value); |
if (url_err == URL_FUNC_NOMEM) { |
free(name); |
free(s); |
return NULL; |
} |
assert(url_err == URL_FUNC_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 = toupper(*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; |
/* What would be 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 && !isspace(*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 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, const char *charset, |
const char *fallback) |
{ |
utf8_convert_ret 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 == UTF8_CONVERT_BADENC) { |
/* charset not understood, try without transliteration */ |
snprintf(cset, sizeof cset, "%s", charset); |
err = utf8_to_enc(item, cset, 0, &ret); |
if (err == UTF8_CONVERT_BADENC) { |
/* 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 == UTF8_CONVERT_BADENC) { |
/* and without transliteration */ |
snprintf(cset, sizeof cset, |
"%s", fallback); |
err = utf8_to_enc(item, cset, 0, &ret); |
} |
} |
if (err == UTF8_CONVERT_BADENC) { |
/* that also failed, use 8859-1 */ |
err = utf8_to_enc(item, "ISO-8859-1//TRANSLIT", |
0, &ret); |
if (err == UTF8_CONVERT_BADENC) { |
/* and without transliteration */ |
err = utf8_to_enc(item, "ISO-8859-1", |
0, &ret); |
} |
} |
} |
} |
if (err == UTF8_CONVERT_NOMEM) { |
return NULL; |
} |
return ret; |
} |
/** |
* 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 callback redraw callback for the select menu |
* \param bw the browser window in which the select menu is being |
* opened |
* \return false on memory exhaustion, true otherwise |
*/ |
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; |
/* 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) { |
warn_user("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(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))) { |
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; |
} |
/** |
* 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) |
{ |
if (control->data.select.menu->scrollbar != NULL) |
scrollbar_destroy(control->data.select.menu->scrollbar); |
free(control->data.select.menu); |
control->data.select.menu = NULL; |
} |
/** |
* Redraw an opened select menu. |
* |
* \param control the select menu being redrawn |
* \param x the X coordinate to draw the menu at |
* \param x 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) |
{ |
const struct plotter_table *plot = ctx->plot; |
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; |
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; |
if (!plot->clip(&r)) |
return false; |
if (!plot->rectangle(x0, y0, x1, y1 ,plot_style_stroke_darkwbasec)) |
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; |
if (!plot->clip(&r)) |
return false; |
if (!plot->rectangle(x0, y0, x1 + 1, y1 + 1, |
plot_style_fill_lightwbasec)) |
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; |
if (!plot->rectangle(x0, (y0 > y2 ? y0 : y2), |
scrollbar_x + 1, |
(y3 < y1 + 1 ? y3 : y1 + 1), |
&plot_style_fill_selected)) |
return false; |
} |
y2 = text_pos_offset + item_y; |
if (!plot->text(text_x, y2, option->text, |
strlen(option->text), &plot_fstyle_entry)) |
return false; |
item_y += line_height_with_spacing; |
option = option->next; |
} |
if (!scrollbar_redraw(menu->scrollbar, |
x_cp + menu->width - SCROLLBAR_WIDTH, |
y_cp, |
clip, scale, ctx)) |
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_x0 minimum x of clipping rectangle |
* \param clip_y0 minimum y of clipping rectangle |
* \param clip_x1 maximum x of clipping rectangle |
* \param clip_y1 maximum y of 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 bw browser window with menu |
* \param control form control with menu |
* \param item index of item selected from the menu |
*/ |
static void form__select_process_selection(html_content *html, |
struct form_control *control, int item) |
{ |
struct box *inline_box; |
struct form_option *o; |
int count; |
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 = false; |
if (count == item) { |
if (control->data.select.multiple) { |
if (o->selected) { |
o->selected = false; |
control->data.select.num_selected--; |
} else { |
o->selected = true; |
control->data.select.num_selected++; |
} |
} else { |
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) { |
warn_user("NoMemory", 0); |
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); |
} |
void form_select_process_selection(hlcache_handle *h, |
struct form_control *control, int item) |
{ |
assert(h != NULL); |
form__select_process_selection( |
(html_content *)hlcache_handle_get_content(h), |
control, item); |
} |
/** |
* 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_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 content content containing the form, of type CONTENT_TYPE |
* \param radio form control of type GADGET_RADIO |
*/ |
void form_radio_set(html_content *html, |
struct form_control *radio) |
{ |
struct form_control *control; |
assert(html); |
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; |
html__redraw_a_box(html, control->box); |
} |
} |
radio->selected = true; |
html__redraw_a_box(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; |
nsurl *action_query; |
assert(form != NULL); |
if (form_successful_controls(form, submit_button, &success) == false) { |
warn_user("NoMemory", 0); |
return; |
} |
switch (form->method) { |
case method_GET: |
data = form_url_encode(form, success, true); |
if (data == NULL) { |
fetch_multipart_data_destroy(success); |
warn_user("NoMemory", 0); |
return; |
} |
/* Decompose action */ |
if (nsurl_create(form->action, &action) != NSERROR_OK) { |
free(data); |
fetch_multipart_data_destroy(success); |
warn_user("NoMemory", 0); |
return; |
} |
/* Replace query segment */ |
if (nsurl_replace_query(action, data, &action_query) != |
NSERROR_OK) { |
nsurl_unref(action); |
free(data); |
fetch_multipart_data_destroy(success); |
warn_user("NoMemory", 0); |
return; |
} |
/* Construct submit url */ |
browser_window_go(target, nsurl_access(action_query), |
nsurl_access(page_url), true); |
nsurl_unref(action); |
nsurl_unref(action_query); |
break; |
case method_POST_URLENC: |
data = form_url_encode(form, success, false); |
if (data == NULL) { |
fetch_multipart_data_destroy(success); |
warn_user("NoMemory", 0); |
return; |
} |
browser_window_go_post(target, form->action, data, 0, |
true, nsurl_access(page_url), false, true, 0); |
break; |
case method_POST_MULTIPART: |
browser_window_go_post(target, form->action, 0, success, |
true, nsurl_access(page_url), false, true, 0); |
break; |
} |
fetch_multipart_data_destroy(success); |
free(data); |
} |
/contrib/network/netsurf/netsurf/render/form.h |
---|
0,0 → 1,179 |
/* |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2003 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Form handling functions (interface). |
*/ |
#ifndef _NETSURF_RENDER_FORM_H_ |
#define _NETSURF_RENDER_FORM_H_ |
#include <stdbool.h> |
#include "desktop/browser.h" |
#include "utils/config.h" |
struct box; |
struct form_control; |
struct form_option; |
struct form_select_menu; |
struct html_content; |
/** 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. */ |
}; |
/** 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; |
/** Form control. */ |
struct form_control { |
void *node; /**< Corresponding DOM node */ |
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 */ |
/** Caret details. */ |
struct box *caret_inline_container; |
struct box *caret_text_box; |
size_t caret_box_offset, caret_form_offset; |
int caret_pixel_offset; |
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; |
} data; |
struct form_control *prev; /**< Previous control in this form */ |
struct form_control *next; /**< Next control in this form. */ |
}; |
/** Option in a select. */ |
struct form_option { |
bool selected; |
bool initial_selected; |
char *value; |
char *text; /**< NUL terminated. */ |
struct form_option* next; |
}; |
/** |
* 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); |
struct form *form_new(void *node, const char *action, const char *target, |
form_method method, const char *charset, |
const char *doc_charset); |
void form_free(struct form *form); |
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); |
bool form_successful_controls(struct form *form, |
struct form_control *submit_button, |
struct fetch_multipart_data **successful_controls); |
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); |
void form_free_select_menu(struct form_control *control); |
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, |
browser_mouse_state mouse, int x, int y); |
void form_select_mouse_drag_end(struct form_control *control, |
browser_mouse_state mouse, int x, int y); |
void form_select_get_dimensions(struct form_control *control, |
int *width, int *height); |
void form_select_process_selection(hlcache_handle *h, |
struct form_control *control, int item); |
void form_submit(nsurl *page_url, struct browser_window *target, |
struct form *form, struct form_control *submit_button); |
void form_radio_set(struct html_content *html, struct form_control *radio); |
#endif |
/contrib/network/netsurf/netsurf/render/html.c |
---|
0,0 → 1,3262 |
/* |
* Copyright 2007 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2010 Michael Drake <tlsa@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Content for text/html (implementation). |
*/ |
#include <assert.h> |
#include <ctype.h> |
#include <stdint.h> |
#include <string.h> |
#include <strings.h> |
#include <stdlib.h> |
#include "utils/config.h" |
#include "content/content_protected.h" |
#include "content/fetch.h" |
#include "content/hlcache.h" |
#include "desktop/options.h" |
#include "desktop/selection.h" |
#include "desktop/scrollbar.h" |
#include "image/bitmap.h" |
#include "render/box.h" |
#include "render/font.h" |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "render/imagemap.h" |
#include "render/layout.h" |
#include "render/search.h" |
#include "javascript/js.h" |
#include "utils/corestrings.h" |
#include "utils/http.h" |
#include "utils/libdom.h" |
#include "utils/log.h" |
#include "utils/messages.h" |
#include "utils/schedule.h" |
#include "utils/talloc.h" |
#include "utils/url.h" |
#include "utils/utf8.h" |
#include "utils/utils.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" |
}; |
/* forward declared functions */ |
static void html_object_refresh(void *p); |
/* pre-interned character set */ |
static lwc_string *html_charset; |
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; |
} |
static void html_destroy_objects(html_content *html) |
{ |
while (html->object_list != NULL) { |
struct content_html_object *victim = html->object_list; |
if (victim->content != NULL) { |
LOG(("object %p", victim->content)); |
if (content_get_type(victim->content) == CONTENT_HTML) |
schedule_remove(html_object_refresh, victim); |
hlcache_handle_release(victim->content); |
} |
html->object_list = victim->next; |
free(victim); |
} |
} |
/** |
* 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; |
LOG(("Done XML to box (%p)", c)); |
/* Clean up and report error if unsuccessful or aborted */ |
if ((success == false) || (c->aborted)) { |
html_destroy_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); |
#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_destroy_objects(c); |
* like the other error paths |
*/ |
LOG(("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) { |
LOG(("imagemap extraction failed")); |
html_destroy_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); |
} |
html_set_status(c, ""); |
dom_node_unref(html); |
} |
/** |
* Complete conversion of an HTML document |
* |
* \param c Content to convert |
*/ |
void html_finish_conversion(html_content *c) |
{ |
union content_msg_data msg_data; |
dom_exception exc; /* returned by libdom functions */ |
dom_node *html; |
uint32_t i; |
css_error css_ret; |
nserror error; |
/* Bail out if we've been aborted */ |
if (c->aborted) { |
content_broadcast_errorcode(&c->base, NSERROR_STOPPED); |
content_set_error(&c->base); |
return; |
} |
/* check that the base stylesheet loaded; layout fails without it */ |
if (c->stylesheets[STYLESHEET_BASE].data.external == NULL) { |
content_broadcast_errorcode(&c->base, NSERROR_CSS_BASE); |
content_set_error(&c->base); |
return; |
} |
/* Create selection context */ |
css_ret = css_select_ctx_create(ns_realloc, c, &c->select_ctx); |
if (css_ret != CSS_OK) { |
content_broadcast_errorcode(&c->base, |
css_error_to_nserror(css_ret)); |
content_set_error(&c->base); |
return; |
} |
/* Add sheets to it */ |
for (i = STYLESHEET_BASE; i != c->stylesheet_count; i++) { |
const struct html_stylesheet *hsheet = &c->stylesheets[i]; |
css_stylesheet *sheet; |
css_origin origin = CSS_ORIGIN_AUTHOR; |
if (i < STYLESHEET_USER) |
origin = CSS_ORIGIN_UA; |
else if (i < STYLESHEET_START) |
origin = CSS_ORIGIN_USER; |
if (hsheet->type == HTML_STYLESHEET_EXTERNAL && |
hsheet->data.external != NULL) { |
sheet = nscss_get_stylesheet(hsheet->data.external); |
} else if (hsheet->type == HTML_STYLESHEET_INTERNAL) { |
sheet = hsheet->data.internal->sheet; |
} else { |
sheet = NULL; |
} |
if (sheet != NULL) { |
css_ret = css_select_ctx_append_sheet(c->select_ctx, |
sheet, |
origin, |
CSS_MEDIA_SCREEN); |
if (css_ret != CSS_OK) { |
content_broadcast_errorcode(&c->base, |
css_error_to_nserror(css_ret)); |
content_set_error(&c->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) |
*/ |
js_fire_event(c->jscontext, "load", c->document, NULL); |
/* convert dom tree to box tree */ |
LOG(("DOM to box (%p)", c)); |
content_set_status(&c->base, messages_get("Processing")); |
msg_data.explicit_status_text = NULL; |
content_broadcast(&c->base, CONTENT_MSG_STATUS, msg_data); |
exc = dom_document_get_document_element(c->document, (void *) &html); |
if ((exc != DOM_NO_ERR) || (html == NULL)) { |
LOG(("error retrieving html element from dom")); |
content_broadcast_errorcode(&c->base, NSERROR_DOM); |
content_set_error(&c->base); |
return; |
} |
error = dom_to_box(html, c, html_box_convert_done); |
if (error != NSERROR_OK) { |
dom_node_unref(html); |
html_destroy_objects(c); |
content_broadcast_errorcode(&c->base, error); |
content_set_error(&c->base); |
return; |
} |
dom_node_unref(html); |
} |
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; |
c->parser = NULL; |
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->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 = &nsfont; |
c->scrollbar = NULL; |
c->scripts_count = 0; |
c->scripts = NULL; |
c->jscontext = NULL; |
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, html_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 = nsoption_bool(enable_javascript); |
parse_params.msg = NULL; |
parse_params.script = html_process_script; |
parse_params.ctx = c; |
parse_params.daf = NULL; |
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); |
} |
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; |
} |
*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 = 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 = nsoption_bool(enable_javascript); |
parse_params.msg = NULL; |
parse_params.script = html_process_script; |
parse_params.ctx = html; |
parse_params.daf = NULL; |
/* 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; |
} |
/** 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; |
if (c->base.title != NULL) |
return true; |
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; |
} |
/** |
* Process elements in <head>. |
* |
* \param c content structure |
* \param head xml node of head element |
* \return true on success, false on memory exhaustion |
* |
* The title and base href are extracted if present. |
*/ |
static nserror html_head(html_content *c, dom_node *head) |
{ |
dom_node *node; |
dom_exception exc; /* returned by libdom functions */ |
dom_string *node_name; |
dom_node_type node_type; |
dom_node *next_node; |
exc = dom_node_get_first_child(head, &node); |
if (exc != DOM_NO_ERR) { |
return NSERROR_DOM; |
} |
while (node != NULL) { |
exc = dom_node_get_node_type(node, &node_type); |
if ((exc == DOM_NO_ERR) && (node_type == DOM_ELEMENT_NODE)) { |
exc = dom_node_get_node_name(node, &node_name); |
if ((exc == DOM_NO_ERR) && (node_name != NULL)) { |
if (dom_string_caseless_lwc_isequal( |
node_name, |
corestring_lwc_title)) { |
html_process_title(c, node); |
} else if (dom_string_caseless_lwc_isequal( |
node_name, |
corestring_lwc_base)) { |
html_process_base(c, node); |
} else if (dom_string_caseless_lwc_isequal( |
node_name, |
corestring_lwc_link)) { |
html_process_link(c, node); |
} |
} |
if (node_name != NULL) { |
dom_string_unref(node_name); |
} |
} |
/* move to next node */ |
exc = dom_node_get_next_sibling(node, &next_node); |
dom_node_unref(node); |
if (exc == DOM_NO_ERR) { |
node = next_node; |
} else { |
node = NULL; |
} |
} |
return NSERROR_OK; |
} |
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 && isspace(*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 && isspace(*url)) { |
url++; |
} |
/* ';' */ |
if (url < end && *url == ';') |
url++; |
/* *LWS */ |
while (url < end && isspace(*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 && isspace(*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 && isspace(*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 && !isspace(*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); |
} |
free(new_url); |
} |
dom_string_unref(content); |
return error; |
} |
/** |
* Search for meta refresh |
* |
* http://wp.netscape.com/assist/net_sites/pushpull.html |
* |
* \param c content structure |
* \param head xml node of head element |
* \return true on success, false otherwise (error reported) |
*/ |
static nserror html_meta_refresh(html_content *c, dom_node *head) |
{ |
dom_node *n, *next; |
dom_exception exc; |
nserror ns_error = NSERROR_OK; |
if (head == NULL) { |
return ns_error; |
} |
exc = dom_node_get_first_child(head, &n); |
if (exc != DOM_NO_ERR) { |
return NSERROR_DOM; |
} |
while (n != NULL) { |
dom_node_type type; |
exc = dom_node_get_node_type(n, &type); |
if (exc != DOM_NO_ERR) { |
dom_node_unref(n); |
return NSERROR_DOM; |
} |
if (type == DOM_ELEMENT_NODE) { |
dom_string *name; |
exc = dom_node_get_node_name(n, &name); |
if (exc != DOM_NO_ERR) { |
dom_node_unref(n); |
return NSERROR_DOM; |
} |
/* Recurse into noscript elements */ |
if (dom_string_caseless_lwc_isequal(name, corestring_lwc_noscript)) { |
ns_error = html_meta_refresh(c, n); |
if (ns_error != NSERROR_OK) { |
/* Some error occurred */ |
dom_string_unref(name); |
dom_node_unref(n); |
return ns_error; |
} else if (c->base.refresh != NULL) { |
/* Meta refresh found - stop */ |
dom_string_unref(name); |
dom_node_unref(n); |
return NSERROR_OK; |
} |
} else if (dom_string_caseless_lwc_isequal(name, corestring_lwc_meta)) { |
ns_error = html_meta_refresh_process_element(c, n); |
if (ns_error != NSERROR_OK) { |
/* Some error occurred */ |
dom_string_unref(name); |
dom_node_unref(n); |
return ns_error; |
} else if (c->base.refresh != NULL) { |
/* Meta refresh found - stop */ |
dom_string_unref(name); |
dom_node_unref(n); |
return NSERROR_OK; |
} |
} |
dom_string_unref(name); |
} |
exc = dom_node_get_next_sibling(n, &next); |
if (exc != DOM_NO_ERR) { |
dom_node_unref(n); |
return NSERROR_DOM; |
} |
dom_node_unref(n); |
n = next; |
} |
return ns_error; |
} |
/** |
* 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; |
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; |
} |
} |
} |
/** |
* 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; |
} |
/** |
* 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; |
assert(c->base.status != CONTENT_STATUS_ERROR); |
box = o->box; |
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--; |
LOG(("%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_ERROR: |
hlcache_handle_release(object); |
o->content = NULL; |
c->base.active--; |
LOG(("%d fetches active", c->base.active)); |
content_add_error(&c->base, "?", 0); |
html_object_failed(box, c, o->background); |
break; |
case CONTENT_MSG_STATUS: |
if (event->data.explicit_status_text == NULL) { |
/* Object content's status text updated */ |
union content_msg_data data; |
data.explicit_status_text = |
content_get_status_message(object); |
html_set_status(c, data.explicit_status_text); |
content_broadcast(&c->base, CONTENT_MSG_STATUS, data); |
} else { |
/* Object content wants to set explicit message */ |
content_broadcast(&c->base, CONTENT_MSG_STATUS, |
event->data); |
} |
break; |
case CONTENT_MSG_REFORMAT: |
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 (hlcache_handle_get_content(object) == |
event->data.redraw.object) { |
data.redraw.x = data.redraw.x * |
box->width / content_get_width(object); |
data.redraw.y = data.redraw.y * |
box->height / |
content_get_height(object); |
data.redraw.width = data.redraw.width * |
box->width / content_get_width(object); |
data.redraw.height = data.redraw.height * |
box->height / |
content_get_height(object); |
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 */ |
schedule(event->data.delay * 100, |
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: |
/* These messages are for browser window layer. |
* we're not interested, so pass them on. */ |
content_broadcast(&c->base, event->type, event->data); |
break; |
default: |
assert(0); |
} |
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)) { |
/* all objects have arrived */ |
content__reformat(&c->base, false, c->base.available_width, |
c->base.height); |
html_set_status(c, ""); |
content_set_done(&c->base); |
} |
/* If 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, |
* 5) the time since the previous reformat is more than the |
* configured minimum time between reformats |
* then reformat the page to display newly fetched objects */ |
else if (nsoption_bool(incremental_reflow) && |
event->type == CONTENT_MSG_DONE && |
!(box->flags & REPLACE_DIM) && |
(c->base.status == CONTENT_STATUS_READY || |
c->base.status == CONTENT_STATUS_DONE) && |
(wallclock() > c->base.reformat_time)) { |
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); |
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--; |
LOG(("%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++; |
LOG(("%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 */ |
} |
} |
/** |
* Callback for fetchcache() for linked 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->type == HTML_STYLESHEET_EXTERNAL && |
s->data.external == css) |
break; |
} |
assert(i != parent->stylesheet_count); |
switch (event->type) { |
case CONTENT_MSG_LOADING: |
break; |
case CONTENT_MSG_READY: |
break; |
case CONTENT_MSG_DONE: |
LOG(("done stylesheet slot %d '%s'", i, |
nsurl_access(hlcache_handle_get_url(css)))); |
parent->base.active--; |
LOG(("%d fetches active", parent->base.active)); |
break; |
case CONTENT_MSG_ERROR: |
LOG(("stylesheet %s failed: %s", |
nsurl_access(hlcache_handle_get_url(css)), |
event->data.error)); |
hlcache_handle_release(css); |
s->data.external = NULL; |
parent->base.active--; |
LOG(("%d fetches active", parent->base.active)); |
content_add_error(&parent->base, "?", 0); |
break; |
case CONTENT_MSG_STATUS: |
if (event->data.explicit_status_text == NULL) { |
/* Object content's status text updated */ |
html_set_status(parent, |
content_get_status_message(css)); |
content_broadcast(&parent->base, CONTENT_MSG_STATUS, |
event->data); |
} else { |
/* Object content wants to set explicit message */ |
content_broadcast(&parent->base, CONTENT_MSG_STATUS, |
event->data); |
} |
break; |
default: |
assert(0); |
} |
if (parent->base.active == 0) |
html_finish_conversion(parent); |
return NSERROR_OK; |
} |
/** |
* Handle notification of inline style completion |
* |
* \param css Inline style object |
* \param pw Private data |
*/ |
static void html_inline_style_done(struct content_css_data *css, void *pw) |
{ |
html_content *html = pw; |
if (--html->base.active == 0) |
html_finish_conversion(html); |
} |
/** |
* Process an inline stylesheet in the document. |
* |
* \param c content structure |
* \param index Index of stylesheet in stylesheet_content array, |
* updated if successful |
* \param style xml node of style element |
* \return true on success, false if an error occurred |
*/ |
static bool |
html_process_style_element(html_content *c, |
unsigned int *index, |
dom_node *style) |
{ |
dom_node *child, *next; |
dom_string *val; |
dom_exception exc; |
struct html_stylesheet *stylesheets; |
struct content_css_data *sheet; |
nserror error; |
/* 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 true; |
} |
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 true; |
} |
dom_string_unref(val); |
} |
/* Extend array */ |
stylesheets = realloc(c->stylesheets, |
sizeof(struct html_stylesheet) * (*index + 1)); |
if (stylesheets == NULL) |
goto no_memory; |
c->stylesheets = stylesheets; |
c->stylesheet_count++; |
c->stylesheets[(*index)].type = HTML_STYLESHEET_INTERNAL; |
c->stylesheets[(*index)].data.internal = NULL; |
/* create stylesheet */ |
sheet = calloc(1, sizeof(struct content_css_data)); |
if (sheet == NULL) { |
c->stylesheet_count--; |
goto no_memory; |
} |
error = nscss_create_css_data(sheet, |
nsurl_access(c->base_url), NULL, c->quirks, |
html_inline_style_done, c); |
if (error != NSERROR_OK) { |
free(sheet); |
c->stylesheet_count--; |
content_broadcast_errorcode(&c->base, error); |
return false; |
} |
/* can't just use xmlNodeGetContent(style), because that won't |
* give the content of comments which may be used to 'hide' |
* the content */ |
exc = dom_node_get_first_child(style, &child); |
if (exc != DOM_NO_ERR) { |
nscss_destroy_css_data(sheet); |
free(sheet); |
c->stylesheet_count--; |
goto no_memory; |
} |
while (child != NULL) { |
dom_string *data; |
exc = dom_node_get_text_content(child, &data); |
if (exc != DOM_NO_ERR) { |
dom_node_unref(child); |
nscss_destroy_css_data(sheet); |
free(sheet); |
c->stylesheet_count--; |
goto no_memory; |
} |
if (nscss_process_css_data(sheet, dom_string_data(data), |
dom_string_byte_length(data)) == false) { |
dom_string_unref(data); |
dom_node_unref(child); |
nscss_destroy_css_data(sheet); |
free(sheet); |
c->stylesheet_count--; |
goto no_memory; |
} |
dom_string_unref(data); |
exc = dom_node_get_next_sibling(child, &next); |
if (exc != DOM_NO_ERR) { |
dom_node_unref(child); |
nscss_destroy_css_data(sheet); |
free(sheet); |
c->stylesheet_count--; |
goto no_memory; |
} |
dom_node_unref(child); |
child = next; |
} |
c->base.active++; |
LOG(("%d fetches active", c->base.active)); |
/* Convert the content -- manually, as we want the result */ |
if (nscss_convert_css_data(sheet) != CSS_OK) { |
/* conversion failed */ |
c->base.active--; |
LOG(("%d fetches active", c->base.active)); |
nscss_destroy_css_data(sheet); |
free(sheet); |
sheet = NULL; |
} |
/* Update index */ |
c->stylesheets[(*index)].data.internal = sheet; |
(*index)++; |
return true; |
no_memory: |
content_broadcast_errorcode(&c->base, NSERROR_NOMEM); |
return false; |
} |
struct find_stylesheet_ctx { |
unsigned int count; |
html_content *c; |
}; |
/** callback to process stylesheet elements |
*/ |
static bool |
html_process_stylesheet(dom_node *node, dom_string *name, void *vctx) |
{ |
struct find_stylesheet_ctx *ctx = (struct find_stylesheet_ctx *)vctx; |
dom_string *rel, *type_attr, *media, *href; |
struct html_stylesheet *stylesheets; |
nsurl *joined; |
dom_exception exc; |
nserror ns_error; |
hlcache_child_context child; |
/* deal with style nodes */ |
if (dom_string_caseless_lwc_isequal(name, corestring_lwc_style)) { |
if (!html_process_style_element(ctx->c, &ctx->count, node)) |
return false; |
return true; |
} |
/* if it is not a link node skip it */ |
if (!dom_string_caseless_lwc_isequal(name, corestring_lwc_link)) { |
return true; |
} |
/* rel=<space separated list, including 'stylesheet'> */ |
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(ctx->c->base_url, dom_string_data(href), &joined); |
if (ns_error != NSERROR_OK) { |
dom_string_unref(href); |
goto no_memory; |
} |
dom_string_unref(href); |
LOG(("linked stylesheet %i '%s'", ctx->count, nsurl_access(joined))); |
/* start fetch */ |
stylesheets = realloc(ctx->c->stylesheets, |
sizeof(struct html_stylesheet) * (ctx->count + 1)); |
if (stylesheets == NULL) { |
nsurl_unref(joined); |
ns_error = NSERROR_NOMEM; |
goto no_memory; |
} |
ctx->c->stylesheets = stylesheets; |
ctx->c->stylesheet_count++; |
ctx->c->stylesheets[ctx->count].type = HTML_STYLESHEET_EXTERNAL; |
child.charset = ctx->c->encoding; |
child.quirks = ctx->c->base.quirks; |
ns_error = hlcache_handle_retrieve(joined, |
0, |
content_get_url(&ctx->c->base), |
NULL, |
html_convert_css_callback, |
ctx->c, |
&child, |
CONTENT_CSS, |
&ctx->c->stylesheets[ctx->count].data.external); |
nsurl_unref(joined); |
if (ns_error != NSERROR_OK) |
goto no_memory; |
ctx->c->base.active++; |
LOG(("%d fetches active", ctx->c->base.active)); |
ctx->count++; |
return true; |
no_memory: |
content_broadcast_errorcode(&ctx->c->base, ns_error); |
return false; |
} |
/** |
* Process inline stylesheets and fetch linked stylesheets. |
* |
* Uses STYLE and LINK elements inside and outside HEAD |
* |
* \param c content structure |
* \param html dom node of html element |
* \return true on success, false if an error occurred |
*/ |
static bool html_find_stylesheets(html_content *c, dom_node *html) |
{ |
nserror ns_error; |
bool result; |
struct find_stylesheet_ctx ctx; |
hlcache_child_context child; |
/* setup context */ |
ctx.c = c; |
ctx.count = STYLESHEET_START; |
/* 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) { |
ns_error = NSERROR_NOMEM; |
goto html_find_stylesheets_no_memory; |
} |
c->stylesheets[STYLESHEET_BASE].type = HTML_STYLESHEET_EXTERNAL; |
c->stylesheets[STYLESHEET_BASE].data.external = NULL; |
c->stylesheets[STYLESHEET_QUIRKS].type = HTML_STYLESHEET_EXTERNAL; |
c->stylesheets[STYLESHEET_QUIRKS].data.external = NULL; |
c->stylesheets[STYLESHEET_ADBLOCK].type = HTML_STYLESHEET_EXTERNAL; |
c->stylesheets[STYLESHEET_ADBLOCK].data.external = NULL; |
c->stylesheets[STYLESHEET_USER].type = HTML_STYLESHEET_EXTERNAL; |
c->stylesheets[STYLESHEET_USER].data.external = 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].data.external); |
if (ns_error != NSERROR_OK) |
goto html_find_stylesheets_no_memory; |
c->base.active++; |
LOG(("%d fetches active", c->base.active)); |
if (c->quirks == DOM_DOCUMENT_QUIRKS_MODE_FULL) { |
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].data.external); |
if (ns_error != NSERROR_OK) |
goto html_find_stylesheets_no_memory; |
c->base.active++; |
LOG(("%d fetches active", c->base.active)); |
} |
if (nsoption_bool(block_ads)) { |
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]. |
data.external); |
if (ns_error != NSERROR_OK) |
goto html_find_stylesheets_no_memory; |
c->base.active++; |
LOG(("%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].data.external); |
if (ns_error != NSERROR_OK) |
goto html_find_stylesheets_no_memory; |
c->base.active++; |
LOG(("%d fetches active", c->base.active)); |
result = libdom_treewalk(html, html_process_stylesheet, &ctx); |
assert(c->stylesheet_count == ctx.count); |
return result; |
html_find_stylesheets_no_memory: |
content_broadcast_errorcode(&c->base, ns_error); |
return false; |
} |
/** |
* 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; |
htmlc->base.active--; /* the html fetch is no longer active */ |
LOG(("%d fetches active", htmlc->base.active)); |
/* if there are no active fetches in progress no scripts are |
* being fetched or they completed already. |
*/ |
if (htmlc->base.active == 0) { |
return html_begin_conversion(htmlc); |
} |
return true; |
} |
bool |
html_begin_conversion(html_content *htmlc) |
{ |
dom_node *html, *head; |
nserror ns_error; |
struct form *f; |
dom_exception exc; /* returned by libdom functions */ |
dom_string *node_name = NULL; |
dom_hubbub_error error; |
/* complete parsing */ |
error = dom_hubbub_parser_completed(htmlc->parser); |
if (error != DOM_HUBBUB_OK) { |
LOG(("Parsing failed")); |
content_broadcast_errorcode(&htmlc->base, |
libdom_hubbub_error_to_nserror(error)); |
return false; |
} |
/* Give up processing if we've been aborted */ |
if (htmlc->aborted) { |
content_broadcast_errorcode(&htmlc->base, NSERROR_STOPPED); |
return false; |
} |
/* complete script execution */ |
html_scripts_exec(htmlc); |
/* fire a simple event that bubbles named DOMContentLoaded at |
* the Document. |
*/ |
/* quirks mode */ |
exc = dom_document_get_quirks_mode(htmlc->document, &htmlc->quirks); |
if (exc != DOM_NO_ERR) { |
LOG(("error retrieving quirks")); |
/** @todo should this be fatal to the conversion? */ |
} |
LOG(("quirks set to %d", htmlc->quirks)); |
/* 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)) { |
LOG(("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))) { |
LOG(("root element not html")); |
content_broadcast_errorcode(&htmlc->base, NSERROR_DOM); |
dom_node_unref(html); |
return false; |
} |
dom_string_unref(node_name); |
head = libdom_find_first_element(html, corestring_lwc_head); |
if (head != NULL) { |
ns_error = html_head(htmlc, head); |
if (ns_error != NSERROR_OK) { |
content_broadcast_errorcode(&htmlc->base, ns_error); |
dom_node_unref(html); |
dom_node_unref(head); |
return false; |
} |
/* handle meta refresh */ |
ns_error = html_meta_refresh(htmlc, head); |
if (ns_error != NSERROR_OK) { |
content_broadcast_errorcode(&htmlc->base, ns_error); |
dom_node_unref(html); |
dom_node_unref(head); |
return false; |
} |
} |
/* 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); |
dom_node_unref(head); |
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); |
dom_node_unref(head); |
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); |
dom_node_unref(head); |
return false; |
} |
} |
} |
dom_node_unref(head); |
/* get stylesheets */ |
if (html_find_stylesheets(htmlc, html) == false) { |
dom_node_unref(html); |
return false; |
} |
dom_node_unref(html); |
if (htmlc->base.active == 0) { |
html_finish_conversion(htmlc); |
} |
return true; |
} |
/** |
* 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) |
{ |
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++; |
c->base.active++; |
LOG(("%d fetches active", c->base.active)); |
return true; |
} |
/** |
* Stop loading a CONTENT_HTML. |
*/ |
static void html_stop(struct content *c) |
{ |
html_content *htmlc = (html_content *) c; |
struct content_html_object *object; |
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: |
for (object = htmlc->object_list; object != NULL; |
object = object->next) { |
if (object->content == NULL) |
continue; |
if (content_get_status(object->content) == |
CONTENT_STATUS_DONE) |
; /* already loaded: do nothing */ |
else if (content_get_status(object->content) == |
CONTENT_STATUS_READY) |
hlcache_handle_abort(object->content); |
/* Active count will be updated when |
* html_object_callback receives |
* CONTENT_MSG_DONE from this object */ |
else { |
hlcache_handle_abort(object->content); |
hlcache_handle_release(object->content); |
object->content = NULL; |
c->active--; |
LOG(("%d fetches active", c->active)); |
} |
} |
/* 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) { |
html_set_status(htmlc, ""); |
content_set_done(c); |
} |
break; |
case CONTENT_STATUS_DONE: |
/* Nothing to do */ |
break; |
default: |
LOG(("Unexpected status %d", c->status)); |
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; |
unsigned int time_before, time_taken; |
time_before = wallclock(); |
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); |
time_taken = wallclock() - time_before; |
c->reformat_time = wallclock() + |
((time_taken * 3 < nsoption_int(min_reflow_period) ? |
nsoption_int(min_reflow_period) : time_taken * 3)); |
} |
/** |
* 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 h 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; |
unsigned int i; |
struct form *f, *g; |
LOG(("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); |
} |
/* 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 */ |
for (i = 0; i != html->stylesheet_count; i++) { |
if (html->stylesheets[i].type == HTML_STYLESHEET_EXTERNAL && |
html->stylesheets[i].data.external != NULL) { |
hlcache_handle_release( |
html->stylesheets[i].data.external); |
} else if (html->stylesheets[i].type == |
HTML_STYLESHEET_INTERNAL && |
html->stylesheets[i].data.internal != NULL) { |
nscss_destroy_css_data( |
html->stylesheets[i].data.internal); |
} |
} |
free(html->stylesheets); |
/* Free scripts */ |
html_free_scripts(html); |
/* Free objects */ |
html_destroy_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; |
} |
/** |
* Set the content status. |
*/ |
void html_set_status(html_content *c, const char *extra) |
{ |
content_set_status(&c->base, extra); |
} |
/** |
* 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; |
struct content_html_object *object, *next; |
html->bw = bw; |
html->page = (html_content *) page; |
/* text selection */ |
selection_init(&html->sel, html->layout); |
for (object = html->object_list; object != NULL; object = next) { |
next = object->next; |
if (object->content == NULL) |
continue; |
if (content_get_type(object->content) == CONTENT_NONE) |
continue; |
content_open(object->content, |
bw, c, |
object->box->object_params); |
} |
} |
/** |
* Handle a window containing a CONTENT_HTML being closed. |
*/ |
static void html_close(struct content *c) |
{ |
html_content *html = (html_content *) c; |
struct content_html_object *object, *next; |
if (html->search != NULL) |
search_destroy_context(html->search); |
html->bw = NULL; |
for (object = html->object_list; object != NULL; object = next) { |
next = object->next; |
if (object->content == NULL) |
continue; |
if (content_get_type(object->content) == CONTENT_NONE) |
continue; |
if (content_get_type(object->content) == CONTENT_HTML) |
schedule_remove(html_object_refresh, object); |
content_close(object->content); |
} |
} |
/** |
* Return an HTML content's selection context |
*/ |
static struct selection *html_get_selection(struct content *c) |
{ |
html_content *html = (html_content *) c; |
return &html->sel; |
} |
/** |
* Get access to any content, link URLs and objects (images) currently |
* at the given (x, y) coordinates. |
* |
* \param c html content to look inside |
* \param x x-coordinate of point of interest |
* \param y y-coordinate of point of interest |
* \param data pointer to contextual_content struct. Its fields are updated |
* with pointers to any relevent content, or set to NULL if none. |
*/ |
static void |
html_get_contextual_content(struct content *c, |
int x, |
int y, |
struct contextual_content *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(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) |
browser_window_get_contextual_content(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_url = nsurl_access(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 = nsurl_access(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; |
} |
} |
} |
} |
/** |
* 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 x-coordinate of point of interest |
* \param scry y-coordinate of point of interest |
* \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(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 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; |
} |
/** |
* 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(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 */ |
utf8_convert_ret ret; |
char *utf8_fn; |
ret = utf8_from_local_encoding(file, 0, |
&utf8_fn); |
if (ret != UTF8_CONVERT_OK) { |
/* A bad encoding should never happen */ |
assert(ret != UTF8_CONVERT_BADENC); |
LOG(("utf8_from_local_encoding failed")); |
/* Load was for us - just no memory */ |
return true; |
} |
/* Found: update form input */ |
free(file_box->gadget->value); |
file_box->gadget->value = utf8_fn; |
/* Redraw box. */ |
html__redraw_a_box(html, file_box); |
} else if (html->bw != NULL) { |
/* File dropped on text input */ |
size_t file_len; |
FILE *fp = NULL; |
char *buffer; |
char *utf8_buff; |
utf8_convert_ret ret; |
unsigned int size; |
struct browser_window *bw; |
/* 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); |
/* 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 = utf8_from_local_encoding(buffer, file_len, &utf8_buff); |
if (ret != UTF8_CONVERT_OK) { |
/* bad encoding shouldn't happen */ |
assert(ret != UTF8_CONVERT_BADENC); |
LOG(("utf8_from_local_encoding failed")); |
free(buffer); |
warn_user("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 */ |
browser_window_mouse_click(html->bw, |
BROWSER_MOUSE_PRESS_1, x, y); |
bw = browser_window_get_root(html->bw); |
/* Paste the file as text */ |
browser_window_paste_text(bw, utf8_buff, size, true); |
free(utf8_buff); |
} |
return true; |
} |
/** |
* Dump debug info concerning the html_content |
* |
* \param bw The browser window |
* \param bw The file to dump to |
*/ |
static void html_debug_dump(struct content *c, FILE *f) |
{ |
html_content *html = (html_content *) c; |
assert(html != NULL); |
assert(html->layout != NULL); |
box_dump(f, html->layout, 0); |
} |
/** |
* Set an HTML content's search context |
* |
* \param c content of type html |
* \param s search context, or NULL if none |
*/ |
void html_set_search(struct content *c, struct search_context *s) |
{ |
html_content *html = (html_content *) c; |
html->search = s; |
} |
/** |
* Return an HTML content's search context |
* |
* \param c content of type html |
* \return content's search context, or NULL if none |
*/ |
struct search_context *html_get_search(struct content *c) |
{ |
html_content *html = (html_content *) c; |
return html->search; |
} |
#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 h Content to retrieve charset from |
* \return Pointer to charset, or NULL |
*/ |
const char *html_get_encoding(hlcache_handle *h) |
{ |
html_content *c = (html_content *) hlcache_handle_get_content(h); |
assert(c != NULL); |
return c->encoding; |
} |
/** |
* Retrieve the charset of an HTML document |
* |
* \param h Content to retrieve charset from |
* \return Pointer to charset, or NULL |
*/ |
dom_hubbub_encoding_source html_get_encoding_source(hlcache_handle *h) |
{ |
html_content *c = (html_content *) hlcache_handle_get_content(h); |
assert(c != NULL); |
return c->encoding_source; |
} |
/** |
* 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 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(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; |
} |
/** |
* 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; |
} |
/** |
* 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) |
{ |
box_construct_fini(); |
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; |
} |
if (html_charset != NULL) { |
lwc_string_unref(html_charset); |
html_charset = NULL; |
} |
} |
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, |
.redraw = html_redraw, |
.open = html_open, |
.close = html_close, |
.get_selection = html_get_selection, |
.get_contextual_content = html_get_contextual_content, |
.scroll_at_point = html_scroll_at_point, |
.drop_file_at_point = html_drop_file_at_point, |
.debug_dump = html_debug_dump, |
.clone = html_clone, |
.type = html_content_type, |
.no_share = true, |
}; |
nserror html_init(void) |
{ |
uint32_t i; |
lwc_error lerror; |
nserror error; |
lerror = lwc_intern_string("charset", SLEN("charset"), &html_charset); |
if (lerror != lwc_error_ok) { |
error = NSERROR_NOMEM; |
goto error; |
} |
error = nsurl_create("resource:default.css", |
&html_default_stylesheet_url); |
if (error != NSERROR_OK) |
goto error; |
error = nsurl_create("resource:adblock.css", |
&html_adblock_stylesheet_url); |
if (error != NSERROR_OK) |
goto error; |
error = nsurl_create("resource:quirks.css", |
&html_quirks_stylesheet_url); |
if (error != NSERROR_OK) |
goto error; |
error = nsurl_create("resource:user.css", |
&html_user_stylesheet_url); |
if (error != NSERROR_OK) |
goto error; |
error = box_construct_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; |
} |
/contrib/network/netsurf/netsurf/render/html.h |
---|
0,0 → 1,189 |
/* |
* Copyright 2004 James Bursa <bursa@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \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 <stdbool.h> |
#include <dom/dom.h> |
#include <dom/bindings/hubbub/parser.h> |
#include "content/content_type.h" |
#include "css/css.h" |
#include "desktop/mouse.h" |
#include "desktop/plot_style.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 scrollbar; |
struct scrollbar_msg_data; |
struct search_context; |
/** |
* Container for stylesheets used by an HTML document |
*/ |
struct html_stylesheet { |
/** Type of sheet */ |
enum { HTML_STYLESHEET_EXTERNAL, HTML_STYLESHEET_INTERNAL } type; |
union { |
struct hlcache_handle *external; |
struct content_css_data *internal; |
} data; /**< Sheet data */ |
}; |
/** |
* 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.) 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>, <frame>) */ |
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) */ |
nsurl *url; /** frame url */ |
bool no_resize; /** frame is not resizable */ |
frame_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>) */ |
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) */ |
nsurl *url; /** frame url */ |
frame_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 */ |
/** Render padding and margin box outlines in html_redraw(). */ |
extern bool html_redraw_debug; |
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 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); |
dom_document *html_get_document(struct hlcache_handle *h); |
struct box *html_get_box_tree(struct hlcache_handle *h); |
const char *html_get_encoding(struct hlcache_handle *h); |
dom_hubbub_encoding_source html_get_encoding_source(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); |
nsurl *html_get_base_url(struct hlcache_handle *h); |
const char *html_get_base_target(struct hlcache_handle *h); |
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 |
/contrib/network/netsurf/netsurf/render/html_forms.c |
---|
0,0 → 1,574 |
/* |
* Copyright 2011 Vincent Sanders <vince@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "utils/corestrings.h" |
#include "utils/log.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; |
} |
/contrib/network/netsurf/netsurf/render/html_interaction.c |
---|
0,0 → 1,990 |
/* |
* Copyright 2006 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2006 Richard Wilson <info@tinct.net> |
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> |
* Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* User interaction with a CONTENT_HTML (implementation). |
*/ |
#include <assert.h> |
#include <stdbool.h> |
#include <dom/dom.h> |
#include "content/content.h" |
#include "desktop/browser.h" |
#include "desktop/frames.h" |
#include "desktop/mouse.h" |
#include "desktop/options.h" |
#include "desktop/scrollbar.h" |
#include "desktop/selection.h" |
#include "desktop/textinput.h" |
#include "render/box.h" |
#include "render/font.h" |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "render/imagemap.h" |
#include "render/textinput.h" |
#include "javascript/js.h" |
#include "utils/messages.h" |
#include "utils/utils.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 h html content's high level cache entry |
* \param mouse state of mouse buttons and modifier keys |
* \param x coordinate of mouse |
* \param y coordinate of mouse |
*/ |
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(box->style, &fstyle); |
nsfont.font_position_in_string(&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_content *html = (html_content*) c; |
browser_drag_type drag_type = browser_window_get_drag_type(bw); |
if (drag_type == DRAGGING_SELECTION && !mouse) { |
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); |
browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); |
} |
switch (drag_type) { |
case DRAGGING_SELECTION: { |
struct box *box; |
int dir = -1; |
int dx, dy; |
if (selection_dragging_start(&html->sel)) |
dir = 1; |
box = box_pick_text_box(html, x, y, dir, &dx, &dy); |
if (box) { |
int pixel_offset; |
size_t idx; |
plot_font_style_t fstyle; |
font_plot_style_from_css(box->style, &fstyle); |
nsfont.font_position_in_string(&fstyle, |
box->text, box->length, |
dx, &idx, &pixel_offset); |
selection_track(&html->sel, mouse, |
box->byte_offset + idx); |
} |
} |
break; |
default: |
html_mouse_action(c, bw, mouse, x, y); |
break; |
} |
} |
/** |
* 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; |
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 *next_box; |
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; |
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 (!mouse && html->scrollbar != NULL) { |
/* drag end: scrollbar */ |
html_overflow_scroll_drag_end(html->scrollbar, mouse, x, y); |
} |
if (html->scrollbar != NULL) { |
struct html_scrollbar_data *data = |
scrollbar_get_data(html->scrollbar); |
box = data->box; |
box_coords(box, &box_x, &box_y); |
if (scrollbar_is_horizontal(html->scrollbar)) { |
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_action(html->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; |
status = scrollbar_mouse_action(html->scrollbar, mouse, |
scroll_mouse_x, scroll_mouse_y); |
} |
msg_data.explicit_status_text = status; |
content_broadcast(c, CONTENT_MSG_STATUS, msg_data); |
return; |
} |
/* Content related drags handled by now */ |
browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); |
/* 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 |
*/ |
while ((next_box = box_at_point(box, x, y, &box_x, &box_y)) != NULL) { |
box = next_box; |
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; |
} |
} |
/* 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 */ |
if (scrollbar) { |
status = scrollbar_mouse_action(scrollbar, mouse, |
scroll_mouse_x, scroll_mouse_y); |
pointer = BROWSER_POINTER_DEFAULT; |
} else if (gadget) { |
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) |
gui_create_form_select_menu(bw, gadget); |
break; |
case GADGET_CHECKBOX: |
status = messages_get("FormCheckbox"); |
if (mouse & BROWSER_MOUSE_CLICK_1) { |
gadget->selected = !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(html, gadget); |
break; |
case GADGET_IMAGE: |
if (mouse & BROWSER_MOUSE_CLICK_1) { |
gadget->data.image.mx = x - gadget_box_x; |
gadget->data.image.my = y - gadget_box_y; |
} |
/* drop 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_TEXTAREA: |
status = messages_get("FormTextarea"); |
pointer = get_pointer_shape(gadget_box, false); |
if (mouse & (BROWSER_MOUSE_PRESS_1 | |
BROWSER_MOUSE_PRESS_2)) { |
if (text_box && selection_root(&html->sel) != |
gadget_box) |
selection_init(&html->sel, gadget_box); |
textinput_textarea_click(c, mouse, |
gadget_box, |
gadget_box_x, |
gadget_box_y, |
x - gadget_box_x, |
y - gadget_box_y); |
} |
if (text_box) { |
int pixel_offset; |
size_t idx; |
font_plot_style_from_css(text_box->style, |
&fstyle); |
nsfont.font_position_in_string(&fstyle, |
text_box->text, |
text_box->length, |
x - gadget_box_x - text_box->x, |
&idx, |
&pixel_offset); |
selection_click(&html->sel, mouse, |
text_box->byte_offset + idx); |
if (selection_dragging(&html->sel)) { |
browser_window_set_drag_type(bw, |
DRAGGING_SELECTION, |
NULL); |
status = messages_get("Selecting"); |
} |
} |
else if (mouse & BROWSER_MOUSE_PRESS_1) |
selection_clear(&html->sel, true); |
break; |
case GADGET_TEXTBOX: |
case GADGET_PASSWORD: |
status = messages_get("FormTextbox"); |
pointer = get_pointer_shape(gadget_box, false); |
if ((mouse & BROWSER_MOUSE_PRESS_1) && |
!(mouse & (BROWSER_MOUSE_MOD_1 | |
BROWSER_MOUSE_MOD_2))) { |
textinput_input_click(c, |
gadget_box, |
gadget_box_x, |
gadget_box_y, |
x - gadget_box_x, |
y - gadget_box_y); |
} |
if (text_box) { |
int pixel_offset; |
size_t idx; |
if (mouse & (BROWSER_MOUSE_DRAG_1 | |
BROWSER_MOUSE_DRAG_2)) |
selection_init(&html->sel, gadget_box); |
font_plot_style_from_css(text_box->style, |
&fstyle); |
nsfont.font_position_in_string(&fstyle, |
text_box->text, |
text_box->length, |
x - gadget_box_x - text_box->x, |
&idx, |
&pixel_offset); |
selection_click(&html->sel, mouse, |
text_box->byte_offset + idx); |
if (selection_dragging(&html->sel)) |
browser_window_set_drag_type(bw, |
DRAGGING_SELECTION, |
NULL); |
} |
else if (mouse & BROWSER_MOUSE_PRESS_1) |
selection_clear(&html->sel, true); |
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"); |
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 (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 (title) { |
snprintf(status_buffer, sizeof status_buffer, "%s: %s", |
nsurl_access(url), title); |
status = status_buffer; |
} else |
status = nsurl_access(url); |
pointer = get_pointer_shape(url_box, imagemap); |
if (mouse & BROWSER_MOUSE_CLICK_1 && |
mouse & BROWSER_MOUSE_MOD_1) { |
/* force download of link */ |
browser_window_go_post(bw, nsurl_access(url), 0, 0, |
false, |
nsurl_access(content_get_url(c)), |
true, true, 0); |
} else if (mouse & BROWSER_MOUSE_CLICK_2 && |
mouse & BROWSER_MOUSE_MOD_1) { |
msg_data.savelink.url = nsurl_access(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) { |
struct box *layout = html->layout; |
if (mouse && (mouse < BROWSER_MOUSE_MOD_1) && |
selection_root(&html->sel) != layout) { |
selection_init(&html->sel, layout); |
} |
if (text_box) { |
int pixel_offset; |
size_t idx; |
font_plot_style_from_css(text_box->style, |
&fstyle); |
nsfont.font_position_in_string(&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 */ |
if (selection_dragging(&html->sel)) { |
browser_window_set_drag_type(bw, |
DRAGGING_SELECTION, |
NULL); |
status = messages_get( |
"Selecting"); |
} |
done = true; |
} |
} else if (mouse & BROWSER_MOUSE_PRESS_1) |
selection_clear(&html->sel, 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 */ |
browser_window_remove_caret(bw); |
} |
} |
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) || |
(mouse & BROWSER_MOUSE_CLICK_2)) { |
js_fire_event(html->jscontext, "click", html->document, node); |
} |
/* 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_go(browser_window_find_target(bw, target, mouse), |
nsurl_access(url), |
nsurl_access(content_get_url(c)), true); |
break; |
case ACTION_NONE: |
break; |
} |
} |
/** |
* 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; |
switch(scrollbar_data->msg) { |
case SCROLLBAR_MSG_MOVED: |
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 |
}; |
browser_window_set_drag_type(html->bw, |
DRAGGING_CONTENT_SCROLLBAR, &rect); |
html->scrollbar = scrollbar_data->scrollbar; |
} |
break; |
case SCROLLBAR_MSG_SCROLL_FINISHED: |
html->scrollbar = NULL; |
browser_window_set_drag_type(html->bw, |
DRAGGING_NONE, NULL); |
msg_data.pointer = BROWSER_POINTER_AUTO; |
content_broadcast(data->c, CONTENT_MSG_POINTER, |
msg_data); |
break; |
} |
} |
/** |
* End overflow scroll scrollbar drags |
* |
* \param scroll 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); |
} |
} |
/contrib/network/netsurf/netsurf/render/html_internal.h |
---|
0,0 → 1,193 |
/* |
* Copyright 2004 James Bursa <bursa@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Content for text/html (private data). |
*/ |
#ifndef NETSURF_RENDER_HTML_INTERNAL_H_ |
#define NETSURF_RENDER_HTML_INTERNAL_H_ |
#include "content/content_protected.h" |
#include "desktop/selection.h" |
#include "render/html.h" |
#include <dom/functypes.h> |
/** Data specific to CONTENT_HTML. */ |
typedef struct html_content { |
struct content base; |
dom_hubbub_parser *parser; /**< Parser object handle */ |
/** 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; |
/** Content has been aborted in the LOADING state */ |
bool aborted; |
/** 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 font_functions *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; |
/** Scrollbar capturing all mouse events, updated to any active HTML |
* scrollbar, or NULL when no scrollbar drags active */ |
struct scrollbar *scrollbar; |
/** Open core-handled form SELECT menu, |
* or NULL if none currently open. */ |
struct form_control *visible_select_menu; |
/** Selection state */ |
struct selection sel; |
/** Context for free text search, or NULL if none */ |
struct search_context *search; |
} html_content; |
bool html_fetch_object(html_content *c, nsurl *url, struct box *box, |
content_type permitted_types, |
int available_width, int available_height, |
bool background); |
void html_set_status(html_content *c, const char *extra); |
void html__redraw_a_box(html_content *html, struct box *box); |
struct browser_window *html_get_browser_window(struct content *c); |
struct search_context *html_get_search(struct content *c); |
void html_set_search(struct content *c, struct search_context *s); |
/** |
* Complete conversion of an HTML document |
* |
* \param htmlc Content to convert |
*/ |
void html_finish_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_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); |
void html_overflow_scroll_callback(void *client_data, |
struct scrollbar_msg_data *scrollbar_data); |
/* in render/html_script.c */ |
dom_hubbub_error html_process_script(void *ctx, dom_node *node); |
void html_free_scripts(html_content *html); |
bool html_scripts_exec(html_content *c); |
/* 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); |
/* 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 |
/contrib/network/netsurf/netsurf/render/html_redraw.c |
---|
0,0 → 1,2537 |
/* |
* Copyright 2004-2008 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2004-2007 John M Bell <jmb202@ecs.soton.ac.uk> |
* Copyright 2004-2007 Richard Wilson <info@tinct.net> |
* Copyright 2005-2006 Adrian Lees <adrianl@users.sourceforge.net> |
* Copyright 2006 Rob Kendrick <rjek@netsurf-browser.org> |
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> |
* Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Redraw of a CONTENT_HTML (implementation). |
*/ |
#include <assert.h> |
#include <stdbool.h> |
#include <stdlib.h> |
#include <string.h> |
#include <math.h> |
#include <dom/dom.h> |
#include "utils/config.h" |
#include "content/content_protected.h" |
#include "css/css.h" |
#include "css/utils.h" |
#include "desktop/plotters.h" |
#include "desktop/selection.h" |
#include "desktop/options.h" |
#include "desktop/print.h" |
#include "desktop/scrollbar.h" |
#include "image/bitmap.h" |
#include "render/box.h" |
#include "render/font.h" |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "render/layout.h" |
#include "render/search.h" |
#include "utils/log.h" |
#include "utils/messages.h" |
#include "utils/utils.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 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) |
{ |
const struct plotter_table *plot = ctx->plot; |
bool highlighted = false; |
plot_font_style_t plot_fstyle = *fstyle; |
/* 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; |
} |
if (!nsfont.font_width(fstyle, utf8_text, start_idx, |
&startx)) |
startx = 0; |
if (!nsfont.font_width(fstyle, utf8_text, endtxt_idx, |
&endx)) |
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 && |
!plot->text(x, y + (int)(height * 0.75 * scale), |
utf8_text, start_idx, |
&plot_fstyle)) |
return false; |
/* decide whether highlighted portion is to be |
* white-on-black or black-on-white */ |
if ((fstyle->background & 0x808080) == 0x808080) |
pstyle_fill_hback = plot_style_fill_black; |
/* highlighted portion */ |
if (!plot->rectangle(x + startx, y, x + endx, |
y + height * scale, |
pstyle_fill_hback)) |
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; |
if (!plot->clip(&r)) |
return false; |
clip_changed = true; |
} else { |
text_visible = false; |
} |
} |
fstyle_hback.background = |
pstyle_fill_hback->fill_colour; |
fstyle_hback.foreground = |
pstyle_fill_hback->fill_colour ^ 0xffffff; |
if (text_visible && |
!plot->text(x, y + (int)(height * 0.75 * scale), |
utf8_text, endtxt_idx, |
&fstyle_hback)) |
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; |
if (!plot->clip(&r)) |
return false; |
clip_changed = true; |
if (!plot->text(x, y + (int) |
(height * 0.75 * scale), |
utf8_text, utf8_len, |
&plot_fstyle)) |
return false; |
} |
} |
if (clip_changed && |
!plot->clip(clip)) |
return false; |
} |
} |
if (!highlighted) { |
if (!plot->text(x, y + (int) (height * 0.75 * scale), |
utf8_text, utf8_len, |
&plot_fstyle)) |
return false; |
} |
return true; |
} |
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, |
}; |
/** |
* 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 ctx current redraw context |
* \return true if successful, false otherwise |
*/ |
static bool 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) |
{ |
const struct plotter_table *plot = ctx->plot; |
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; |
if (c == NS_TRANSPARENT) |
return true; |
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: |
if (!plot->line((p[0] + p[2]) / 2, |
(p[1] + p[3]) / 2, |
(p[4] + p[6]) / 2, |
(p[5] + p[7]) / 2, |
&plot_style_bdr)) |
return false; |
break; |
case CSS_BORDER_STYLE_SOLID: |
/* fall through to default */ |
default: |
if (rectangular || thickness == 1) { |
int x0, y0, x1, y1; |
if (side == TOP || side == RIGHT) { |
x0 = p[2]; y0 = p[3]; |
x1 = p[6]; y1 = p[7]; |
x1 = ((side == TOP) && (p[4] - p[6] != 0)) ? |
x1 + p[4] - p[6] : x1; |
} else { |
x0 = p[6]; y0 = p[7]; |
x1 = p[2]; y1 = p[3]; |
y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ? |
y1 + p[1] - p[3] : y1; |
} |
/* find intersection of clip rectangle and border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
&plot_style_fillbdr)) |
return false; |
} |
} else { |
if (!plot->polygon(p, 4, &plot_style_fillbdr)) |
return false; |
} |
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]; |
if (!plot->polygon(z, 4, &plot_style_fillbdr)) |
return false; |
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]; |
if (!plot->polygon(z, 4, &plot_style_fillbdr)) |
return false; |
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 */ |
int x0, y0, x1, y1; |
/* First part */ |
if (side == TOP || side == RIGHT) { |
x0 = (p[0] + p[2]) / 2; y0 = (p[1] + p[3]) / 2; |
x1 = p[6]; y1 = p[7]; |
} else { |
x0 = p[6]; y0 = p[7]; |
x1 = (p[0] + p[2]) / 2; y1 = (p[1] + p[3]) / 2; |
} |
/* find intersection of clip rectangle and border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
plot_style_bdr_in)) |
return false; |
} |
/* Second part */ |
if (side == TOP || side == RIGHT) { |
x0 = p[2]; y0 = p[3]; |
x1 = (p[6] + p[4]) / 2; y1 = (p[7] + p[5]) / 2; |
} else { |
x0 = (p[6] + p[4]) / 2; y0 = (p[7] + p[5]) / 2; |
x1 = p[2]; y1 = p[3]; |
} |
/* find intersection of clip rectangle and border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
plot_style_bdr_out)) |
return false; |
} |
} else if (thickness == 1) { |
/* Border made up from one part which can be plotted |
* as a rectangle */ |
int x0, y0, x1, y1; |
if (side == TOP || side == RIGHT) { |
x0 = p[2]; y0 = p[3]; |
x1 = p[6]; y1 = p[7]; |
x1 = ((side == TOP) && (p[4] - p[6] != 0)) ? |
x1 + p[4] - p[6] : x1; |
/* find intersection of clip rectangle and |
* border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
plot_style_bdr_in)) |
return false; |
} |
} else { |
x0 = p[6]; y0 = p[7]; |
x1 = p[2]; y1 = p[3]; |
y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ? |
y1 + p[1] - p[3] : y1; |
/* find intersection of clip rectangle and |
* border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
plot_style_bdr_out)) |
return false; |
} |
} |
} 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]; |
if (!plot->polygon(z, 4, plot_style_bdr_in)) |
return false; |
z[0] = p[2]; |
z[1] = p[3]; |
z[6] = p[4]; |
z[7] = p[5]; |
if (!plot->polygon(z, 4, plot_style_bdr_out)) |
return false; |
} |
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 */ |
int x0, y0, x1, y1; |
/* First part */ |
if (side == TOP || side == RIGHT) { |
x0 = (p[0] + p[2]) / 2; y0 = (p[1] + p[3]) / 2; |
x1 = p[6]; y1 = p[7]; |
} else { |
x0 = p[6]; y0 = p[7]; |
x1 = (p[0] + p[2]) / 2; y1 = (p[1] + p[3]) / 2; |
} |
/* find intersection of clip rectangle and border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
plot_style_bdr_in)) |
return false; |
} |
/* Second part */ |
if (side == TOP || side == RIGHT) { |
x0 = p[2]; y0 = p[3]; |
x1 = (p[6] + p[4]) / 2; y1 = (p[7] + p[5]) / 2; |
} else { |
x0 = (p[6] + p[4]) / 2; y0 = (p[7] + p[5]) / 2; |
x1 = p[2]; y1 = p[3]; |
} |
/* find intersection of clip rectangle and border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
plot_style_bdr_out)) |
return false; |
} |
} else if (thickness == 1) { |
/* Border made up from one part which can be plotted |
* as a rectangle */ |
int x0, y0, x1, y1; |
if (side == TOP || side == RIGHT) { |
x0 = p[2]; y0 = p[3]; |
x1 = p[6]; y1 = p[7]; |
x1 = ((side == TOP) && (p[4] - p[6] != 0)) ? |
x1 + p[4] - p[6] : x1; |
/* find intersection of clip rectangle and |
* border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
plot_style_bdr_in)) |
return false; |
} |
} else { |
x0 = p[6]; y0 = p[7]; |
x1 = p[2]; y1 = p[3]; |
y1 = ((side == LEFT) && (p[1] - p[3] != 0)) ? |
y1 + p[1] - p[3] : y1; |
/* find intersection of clip rectangle and |
* border */ |
x0 = (clip->x0 > x0) ? clip->x0 : x0; |
y0 = (clip->y0 > y0) ? clip->y0 : y0; |
x1 = (clip->x1 < x1) ? clip->x1 : x1; |
y1 = (clip->y1 < y1) ? clip->y1 : y1; |
if ((x0 < x1) && (y0 < y1)) { |
/* valid clip rectangles only */ |
if (!plot->rectangle(x0, y0, x1, y1, |
plot_style_bdr_out)) |
return false; |
} |
} |
} 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]; |
if (!plot->polygon(z, 4, plot_style_bdr_in)) |
return false; |
z[0] = p[2]; |
z[1] = p[3]; |
z[6] = p[4]; |
z[7] = p[5]; |
if (!plot->polygon(z, 4, plot_style_bdr_out)) |
return false; |
} |
break; |
} |
return true; |
} |
/** |
* 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 scale scale for redraw |
* \param ctx current redraw context |
* \return true if successful, false otherwise |
*/ |
static 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; |
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); |
if (!html_redraw_border_plot(side, z, col, |
box->border[side].style, |
box->border[side].width * scale, |
square_end_1 && square_end_2, |
clip, ctx)) |
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); |
if (!html_redraw_border_plot(side, z, col, |
box->border[side].style, |
box->border[side].width * scale, |
square_end_1 && square_end_2, |
clip, ctx)) |
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); |
if (!html_redraw_border_plot(side, z, col, |
box->border[side].style, |
box->border[side].width * scale, |
square_end_1 && square_end_2, |
clip, ctx)) |
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); |
if (!html_redraw_border_plot(side, z, col, |
box->border[side].style, |
box->border[side].width * scale, |
square_end_1 && square_end_2, |
clip, ctx)) |
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 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 |
*/ |
static 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; |
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; |
} |
if (!html_redraw_border_plot(LEFT, z, col, |
box->border[LEFT].style, |
left, square_end_1 && square_end_2, |
clip, ctx)) |
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; |
} |
if (!html_redraw_border_plot(RIGHT, z, col, |
box->border[RIGHT].style, |
right, square_end_1 && square_end_2, |
clip, ctx)) |
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; |
} |
if (!html_redraw_border_plot(TOP, z, col, |
box->border[TOP].style, |
top, square_end_1 && square_end_2, |
clip, ctx)) |
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; |
} |
if (!html_redraw_border_plot(BOTTOM, z, col, |
box->border[BOTTOM].style, |
bottom, square_end_1 && square_end_2, |
clip, ctx)) |
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) |
{ |
const struct plotter_table *plot = ctx->plot; |
double z = width * 0.15; |
if (z == 0) |
z = 1; |
if (!(plot->rectangle(x, y, x + width, y + height, |
plot_style_fill_wbasec) && |
plot->line(x, y, x + width, y, plot_style_stroke_darkwbasec) && |
plot->line(x, y, x, y + height, plot_style_stroke_darkwbasec) && |
plot->line(x + width, y, x + width, y + height, |
plot_style_stroke_lightwbasec) && |
plot->line(x, y + height, x + width, y + height, |
plot_style_stroke_lightwbasec))) |
return false; |
if (selected) { |
if (width < 12 || height < 12) { |
/* render a solid box instead of a tick */ |
if (!plot->rectangle(x + z + z, y + z + z, |
x + width - z, y + height - z, |
plot_style_fill_wblobc)) |
return false; |
} else { |
/* render a tick, as it'll fit comfortably */ |
if (!(plot->line(x + width - z, |
y + z, |
x + (z * 3), |
y + height - z, |
plot_style_stroke_wblobc) && |
plot->line(x + (z * 3), |
y + height - z, |
x + z + z, |
y + (height / 2), |
plot_style_stroke_wblobc))) |
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) |
{ |
const struct plotter_table *plot = ctx->plot; |
/* plot background of radio button */ |
if (!plot->disc(x + width * 0.5, |
y + height * 0.5, |
width * 0.5 - 1, |
plot_style_fill_wbasec)) |
return false; |
/* plot dark arc */ |
if (!plot->arc(x + width * 0.5, |
y + height * 0.5, |
width * 0.5 - 1, |
45, |
225, |
plot_style_fill_darkwbasec)) |
return false; |
/* plot light arc */ |
if (!plot->arc(x + width * 0.5, |
y + height * 0.5, |
width * 0.5 - 1, |
225, |
45, |
plot_style_fill_lightwbasec)) |
return false; |
if (selected) { |
/* plot selection blob */ |
if (!plot->disc(x + width * 0.5, |
y + height * 0.5, |
width * 0.3 - 1, |
plot_style_fill_wblobc)) |
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 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 struct redraw_context *ctx) |
{ |
int text_width; |
const char *text; |
size_t length; |
plot_font_style_t fstyle; |
font_plot_style_from_css(box->style, &fstyle); |
fstyle.background = background_colour; |
if (box->gadget->value) |
text = box->gadget->value; |
else |
text = messages_get("Form_Drop"); |
length = strlen(text); |
if (!nsfont.font_width(&fstyle, text, length, &text_width)) |
return false; |
text_width *= scale; |
if (width < text_width + 8) |
x = x + width - text_width - 4; |
else |
x = x + 4; |
return ctx->plot->text(x, y + height * 0.75, text, length, &fstyle); |
} |
/** |
* Plot background images. |
* |
* \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 ::box) |
* \param ctx current redraw context |
* \return true if successful, false otherwise |
* |
* The reason for the presence of ::background is the backwards compatibility |
* mess that is backgrounds on <body>. The background will be drawn relative |
* to ::box, using the background information contained within ::background. |
*/ |
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 struct redraw_context *ctx) |
{ |
const struct plotter_table *plot = ctx->plot; |
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, |
}; |
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(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(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); |
/* <td> attributes override <tr> */ |
/* 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) |
if (!plot->rectangle(r.x0, r.y0, r.x1, r.y1, |
&pstyle_fill_bg)) |
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; |
if (!plot->clip(&r)) |
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; |
if (!content_redraw(background->background, |
&bg_data, &r, ctx)) |
return false; |
} |
} |
/* only <tr> 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 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 struct redraw_context *ctx) |
{ |
const struct plotter_table *plot = ctx->plot; |
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, |
}; |
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(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(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) |
if (!plot->rectangle(r.x0, r.y0, r.x1, r.y1, |
&pstyle_fill_bg)) |
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; |
if (!plot->clip(&r)) |
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; |
if (!content_redraw(box->background, &bg_data, &r, ctx)) |
return false; |
} |
} |
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) |
{ |
const struct plotter_table *plot = ctx->plot; |
struct box *c; |
plot_style_t plot_style_box = { |
.stroke_type = PLOT_OP_TYPE_SOLID, |
.stroke_colour = colour, |
}; |
for (c = box->next; |
c && c != box->inline_end; |
c = c->next) { |
if (c->type != BOX_TEXT) |
continue; |
if (!plot->line((x + c->x) * scale, |
(y + c->y + c->height * ratio) * scale, |
(x + c->x + c->width) * scale, |
(y + c->y + c->height * ratio) * scale, |
&plot_style_box)) |
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) |
{ |
const struct plotter_table *plot = ctx->plot; |
struct box *c; |
plot_style_t plot_style_box = { |
.stroke_type = PLOT_OP_TYPE_SOLID, |
.stroke_colour = colour, |
}; |
/* draw through text descendants */ |
for (c = box->children; c; c = c->next) { |
if (c->type == BOX_TEXT) { |
if (!plot->line((x + c->x) * scale, |
(y + c->y + c->height * ratio) * scale, |
(x + c->x + c->width) * scale, |
(y + c->y + c->height * ratio) * scale, |
&plot_style_box)) |
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 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(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; |
int x_scrolled, y_scrolled; |
struct box *bg_box = NULL; |
bool has_x_scroll, has_y_scroll; |
css_computed_clip_rect css_rect; |
if (html_redraw_printing && (box->flags & PRINTED)) |
return true; |
/* 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 && css_computed_overflow(box->style) != |
CSS_OVERFLOW_VISIBLE) { |
/* box contents clipped to box size */ |
r.x0 = x - border_left; |
r.y0 = y - border_top; |
r.x1 = x + padding_width + border_right; |
r.y1 = y + padding_height + border_bottom; |
} else { |
/* box contents can hang out of the box; use descendant box */ |
if (scale == 1.0) { |
r.x0 = x + box->descendant_x0; |
r.y0 = y + box->descendant_y0; |
r.x1 = x + box->descendant_x1 + 1; |
r.y1 = y + box->descendant_y1 + 1; |
} else { |
r.x0 = x + box->descendant_x0 * scale; |
r.y0 = y + box->descendant_y0 * scale; |
r.x1 = x + box->descendant_x1 * scale + 1; |
r.y1 = y + box->descendant_y1 * scale + 1; |
} |
if (!box->parent) { |
/* root element */ |
int margin_left, margin_right; |
int margin_top, margin_bottom; |
if (scale == 1.0) { |
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; |
} |
r.x0 = x - border_left - margin_left < r.x0 ? |
x - border_left - margin_left : r.x0; |
r.y0 = y - border_top - margin_top < r.y0 ? |
y - border_top - margin_top : r.y0; |
r.x1 = x + padding_width + border_right + |
margin_right > r.x1 ? |
x + padding_width + border_right + |
margin_right : r.x1; |
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 ((plot->group_start) && (!plot->group_start("hidden box"))) |
return false; |
if (!html_redraw_box_children(html, box, x_parent, y_parent, |
&r, scale, current_background_color, ctx)) |
return false; |
return ((!plot->group_end) || (plot->group_end())); |
} |
if ((plot->group_start) && (!plot->group_start("vis box"))) |
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( |
css_rect.left, css_rect.lunit, |
box->style)); |
if (css_rect.top_auto == false) |
r.y0 = y - border_top + FIXTOINT(nscss_len2px( |
css_rect.top, css_rect.tunit, |
box->style)); |
if (css_rect.right_auto == false) |
r.x1 = x - border_left + FIXTOINT(nscss_len2px( |
css_rect.right, css_rect.runit, |
box->style)); |
if (css_rect.bottom_auto == false) |
r.y1 = y - border_top + FIXTOINT(nscss_len2px( |
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; |
/* no point trying to draw 0-width/height boxes */ |
if (r.x0 == r.x1 || r.y0 == r.y1) |
/* not an error */ |
return ((!plot->group_end) || (plot->group_end())); |
/* clip to it */ |
if (!plot->clip(&r)) |
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 ((!plot->group_end) || (plot->group_end())); |
/* clip to it */ |
if (!plot->clip(&r)) |
return false; |
} else { |
/* clip box is fine, clip to it */ |
r = *clip; |
if (!plot->clip(&r)) |
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)) { |
/* 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, ctx)) |
return false; |
/* restore previous graphics window */ |
if (!plot->clip(&r)) |
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) && |
(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, ctx)) |
return false; |
/* restore previous graphics window */ |
if (!plot->clip(&r)) |
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, ctx)) |
return false; |
/* restore previous graphics window */ |
if (!plot->clip(&r)) |
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 */ |
if (!plot->rectangle(x + padding_left, |
y + padding_top, |
x + padding_left + width, |
y + padding_top + height, |
plot_style_content_edge)) |
return false; |
/* Padding edge -- red */ |
if (!plot->rectangle(x, y, |
x + padding_width, y + padding_height, |
plot_style_padding_edge)) |
return false; |
/* Margin edge -- yellow */ |
if (!plot->rectangle( |
x - border_left - margin_left, |
y - border_top - margin_top, |
x + padding_width + border_right + |
margin_right, |
y + padding_height + border_bottom + |
margin_bottom, |
plot_style_margin_edge)) |
return false; |
} |
/* clip to the padding edge for objects, or boxes with overflow hidden |
* or scroll */ |
if ((box->style && css_computed_overflow(box->style) != |
CSS_OVERFLOW_VISIBLE) || box->object || |
box->flags & IFRAME) { |
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 ((!plot->group_end) || (plot->group_end())); |
if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || |
box->type == BOX_TABLE_CELL || box->object) { |
if (!plot->clip(&r)) |
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; |
if (!plot->rectangle(x + padding_left, |
y + padding_top, |
x + padding_left + width - 1, |
y + padding_top + height - 1, |
plot_style_broken_object)) |
return false; |
if (!nsfont.font_width(plot_fstyle_broken_object, obj, |
sizeof(obj) - 1, &obj_width)) |
obj_x += 1; |
else |
obj_x += width / 2 - obj_width / 2; |
if (!plot->text(obj_x, y + padding_top + (int) |
(height * 0.75), |
obj, sizeof(obj) - 1, |
plot_fstyle_broken_object)) |
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, ctx)) |
return false; |
} 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 (!plot->clip(clip)) |
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 && |
(css_computed_overflow(box->style) == |
CSS_OVERFLOW_SCROLL || |
css_computed_overflow(box->style) == |
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 (!plot->clip(clip)) |
return false; |
return ((!plot->group_end) || (plot->group_end())); |
} |
/** |
* 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(clip); |
if (html->background_colour != NS_TRANSPARENT) |
pstyle_fill_bg.fill_colour = html->background_colour; |
result &= ctx->plot->rectangle(clip->x0, clip->y0, |
clip->x1, clip->y1, |
&pstyle_fill_bg); |
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; |
} |
/contrib/network/netsurf/netsurf/render/html_script.c |
---|
0,0 → 1,602 |
/* |
* Copyright 2012 Vincent Sanders <vince@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Content for text/html scripts (implementation). |
*/ |
#include <assert.h> |
#include <ctype.h> |
#include <stdint.h> |
#include <stdbool.h> |
#include <string.h> |
#include <strings.h> |
#include <stdlib.h> |
#include "utils/config.h" |
#include "utils/corestrings.h" |
#include "utils/log.h" |
#include "utils/messages.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; |
} |
/* attempt defer and async script execution |
* |
* execute scripts using algorithm found in: |
* http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#the-script-element |
* |
*/ |
bool html_scripts_exec(html_content *c) |
{ |
unsigned int i; |
struct html_script *s; |
script_handler_t *script_handler; |
if (c->jscontext == NULL) |
return false; |
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 true; |
} |
/* 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: |
LOG(("script %d done '%s'", i, |
nsurl_access(hlcache_handle_get_url(script)))); |
parent->base.active--; |
LOG(("%d fetches active", parent->base.active)); |
break; |
case CONTENT_MSG_ERROR: |
LOG(("script %s failed: %s", |
nsurl_access(hlcache_handle_get_url(script)), |
event->data.error)); |
hlcache_handle_release(script); |
s->data.handle = NULL; |
parent->base.active--; |
LOG(("%d fetches active", parent->base.active)); |
content_add_error(&parent->base, "?", 0); |
break; |
case CONTENT_MSG_STATUS: |
break; |
default: |
assert(0); |
} |
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_LOADING: |
break; |
case CONTENT_MSG_READY: |
break; |
case CONTENT_MSG_DONE: |
LOG(("script %d done '%s'", i, |
nsurl_access(hlcache_handle_get_url(script)))); |
parent->base.active--; |
LOG(("%d fetches active", parent->base.active)); |
break; |
case CONTENT_MSG_ERROR: |
LOG(("script %s failed: %s", |
nsurl_access(hlcache_handle_get_url(script)), |
event->data.error)); |
hlcache_handle_release(script); |
s->data.handle = NULL; |
parent->base.active--; |
LOG(("%d fetches active", parent->base.active)); |
content_add_error(&parent->base, "?", 0); |
break; |
case CONTENT_MSG_STATUS: |
break; |
default: |
assert(0); |
} |
/* if there are no active fetches remaining begin post parse |
* conversion |
*/ |
if (parent->base.active == 0) { |
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_LOADING: |
break; |
case CONTENT_MSG_READY: |
break; |
case CONTENT_MSG_DONE: |
LOG(("script %d done '%s'", i, |
nsurl_access(hlcache_handle_get_url(script)))); |
parent->base.active--; |
LOG(("%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) { |
/* 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) { |
LOG(("unpause returned 0x%x", err)); |
} |
break; |
case CONTENT_MSG_ERROR: |
LOG(("script %s failed: %s", |
nsurl_access(hlcache_handle_get_url(script)), |
event->data.error)); |
hlcache_handle_release(script); |
s->data.handle = NULL; |
parent->base.active--; |
LOG(("%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) { |
LOG(("unpause returned 0x%x", err)); |
} |
break; |
case CONTENT_MSG_STATUS: |
break; |
default: |
assert(0); |
} |
/* if there are no active fetches remaining begin post parse |
* conversion |
*/ |
if (parent->base.active == 0) { |
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; |
union content_msg_data msg_data; |
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) { |
msg_data.error = messages_get("NoMemory"); |
content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data); |
return DOM_HUBBUB_NOMEM; |
} |
LOG(("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); |
msg_data.error = messages_get("NoMemory"); |
content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data); |
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; |
LOG(("Fetch failed with error %d",ns_error)); |
} else { |
/* update base content active fetch count */ |
c->base.active++; |
LOG(("%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) |
{ |
union content_msg_data msg_data; |
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); |
msg_data.error = messages_get("NoMemory"); |
content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data); |
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 */ |
if (c->jscontext == NULL) { |
union content_msg_data msg_data; |
msg_data.jscontext = &c->jscontext; |
content_broadcast(&c->base, CONTENT_MSG_GETCTX, msg_data); |
LOG(("javascript context %p ", c->jscontext)); |
if (c->jscontext == NULL) { |
/* no context and it could not be created, abort */ |
return DOM_HUBBUB_OK; |
} |
} |
LOG(("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; |
} |
void html_free_scripts(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); |
} |
/contrib/network/netsurf/netsurf/render/imagemap.c |
---|
0,0 → 1,804 |
/* |
* Copyright 2004 John M Bell <jmb202@ecs.soton.ac.uk> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/* |
* Much of this shamelessly copied from utils/messages.c |
*/ |
#include <assert.h> |
#include <stdbool.h> |
#include <string.h> |
#include <strings.h> |
#include <dom/dom.h> |
#include "content/content_protected.h" |
#include "content/hlcache.h" |
#include "render/box.h" |
#include "render/html_internal.h" |
#include "render/imagemap.h" |
#include "utils/corestrings.h" |
#include "utils/log.h" |
#include "utils/utils.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 */ |
}; |
static bool imagemap_add(html_content *c, dom_string *key, |
struct mapentry *list); |
static bool imagemap_create(html_content *c); |
static bool imagemap_extract_map(dom_node *node, html_content *c, |
struct mapentry **entry); |
static bool imagemap_addtolist(dom_node *n, nsurl *base_url, |
struct mapentry **entry, dom_string *tagtype); |
static void imagemap_freelist(struct mapentry *list); |
static unsigned int imagemap_hash(const char *key); |
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); |
/** |
* 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 |
*/ |
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; |
} |
/** |
* Create hashtable of imagemaps |
* |
* \param c The containing content |
* \return true on success, false otherwise |
*/ |
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; |
} |
/** |
* 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) { |
LOG(("Imagemap: %s", map->key)); |
for (entry = map->list; entry; entry = entry->next) { |
switch (entry->type) { |
case IMAGEMAP_DEFAULT: |
LOG(("\tDefault: %s", nsurl_access( |
entry->url))); |
break; |
case IMAGEMAP_RECT: |
LOG(("\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: |
LOG(("\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: |
LOG(("\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; |
} |
} |
} |
/** |
* Extract all imagemaps from a document tree |
* |
* \param c The content |
* \param map_str A dom_string which is "map" |
* \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) { |
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 <map>...</map> block |
*/ |
if ((entry != NULL) && |
(imagemap_add(c, name, entry) == false)) { |
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; |
} |
/** |
* 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(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 |
*/ |
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); |
} |
/** |
* Adds an imagemap entry to the list |
* |
* \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 |
* \return false on memory exhaustion, true otherwise |
*/ |
bool |
imagemap_addtolist(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 <area nohref="anything" /> */ |
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(dom_string_data(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('\0', ","); |
} |
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('\0', ","); |
} |
break; |
case IMAGEMAP_POLY: |
new_map->bounds.poly.xcoords = NULL; |
new_map->bounds.poly.ycoords = NULL; |
while (val != NULL) { |
x = atoi(val); |
val = strtok('\0', ","); |
if (val == NULL) |
break; |
y = atoi(val); |
xcoords = realloc(new_map->bounds.poly.xcoords, |
num * sizeof(float)); |
if (xcoords == NULL) { |
goto bad_out; |
} |
ycoords = realloc(new_map->bounds.poly.ycoords, |
num * sizeof(float)); |
if (ycoords == NULL) { |
goto bad_out; |
} |
new_map->bounds.poly.xcoords = xcoords; |
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('\0', ","); |
} |
new_map->bounds.poly.num = num - 1; |
break; |
default: |
break; |
} |
} |
new_map->next = NULL; |
if (entry && *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->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); |
if (new_map != NULL) |
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; |
} |
/** |
* Free list of imagemap entries |
* |
* \param list Pointer to head of list |
*/ |
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); |
} |
} |
/** |
* Retrieve url associated with imagemap entry |
* |
* \param h 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; |
} |
/** |
* Hash function |
* |
* \param key The key to hash |
* \return The hashed value |
*/ |
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; |
} |
/** |
* 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 |
*/ |
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; |
} |
/contrib/network/netsurf/netsurf/render/imagemap.h |
---|
0,0 → 1,38 |
/* |
* Copyright 2004 John M Bell <jmb202@ecs.soton.ac.uk> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
#ifndef _NETSURF_RENDER_IMAGEMAP_H_ |
#define _NETSURF_RENDER_IMAGEMAP_H_ |
#include <dom/dom.h> |
#include "utils/nsurl.h" |
struct html_content; |
struct hlcache_handle; |
void imagemap_destroy(struct html_content *c); |
void imagemap_dump(struct html_content *c); |
nserror imagemap_extract(struct html_content *c); |
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 |
/contrib/network/netsurf/netsurf/render/layout.c |
---|
0,0 → 1,5077 |
/* |
* Copyright 2005 Richard Wilson <info@tinct.net> |
* Copyright 2006 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \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 <assert.h> |
#include <limits.h> |
#include <stdbool.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
#include <math.h> |
#include <dom/dom.h> |
#include "css/css.h" |
#include "css/utils.h" |
#include "content/content_protected.h" |
#include "desktop/options.h" |
#include "desktop/scrollbar.h" |
#include "render/box.h" |
#include "render/font.h" |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "render/layout.h" |
#include "render/table.h" |
#include "utils/log.h" |
#include "utils/talloc.h" |
#include "utils/utils.h" |
/* Define to enable layout debugging */ |
#undef LAYOUT_DEBUG |
#define AUTO INT_MIN |
/* Fixed point value percentage of an integer, to an integer */ |
#define FPCT_OF_INT_TOINT(a, b) FIXTOINT(FMUL(FDIV(a, F_100), INTTOFIX(b))) |
static bool layout_block_context(struct box *block, int viewport_height, |
html_content *content); |
static void layout_minmax_block(struct box *block, |
const struct font_functions *font_func); |
static struct box* layout_next_margin_block(struct box *box, struct box *block, |
int viewport_height, int *max_pos_margin, int *max_neg_margin); |
static bool layout_block_object(struct box *block); |
static void layout_get_object_dimensions(struct box *box, |
int *width, int *height, int min_width, int max_width); |
static void layout_block_find_dimensions(int available_width, |
int viewport_height, int lm, int rm, |
struct box *box); |
static bool layout_apply_minmax_height(struct box *box, struct box *container); |
static void layout_block_add_scrollbar(struct box *box, int which); |
static int layout_solve_width(struct box *box, int available_width, int width, |
int lm, int rm, int max_width, int min_width); |
static void layout_float_find_dimensions(int available_width, |
const css_computed_style *style, struct box *box); |
static void layout_find_dimensions(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 margin[4], int padding[4], struct box_border border[4]); |
static void layout_tweak_form_dimensions(struct box *box, bool percentage, |
int available_width, bool setwidth, int *dimension); |
static int layout_clear(struct box *fl, enum css_clear_e clear); |
static void find_sides(struct box *fl, int y0, int y1, |
int *x0, int *x1, struct box **left, struct box **right); |
static void layout_minmax_inline_container(struct box *inline_container, |
bool *has_height, const struct font_functions *font_func); |
static int line_height(const css_computed_style *style); |
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); |
static struct box *layout_minmax_line(struct box *first, int *min, int *max, |
bool first_line, bool *line_has_height, |
const struct font_functions *font_func); |
static int layout_text_indent(const css_computed_style *style, int width); |
static bool layout_float(struct box *b, int width, html_content *content); |
static void place_float_below(struct box *c, int width, int cx, int y, |
struct box *cont); |
static bool layout_table(struct box *box, int available_width, |
html_content *content); |
static void layout_move_children(struct box *box, int x, int y); |
static void calculate_mbp_width(const css_computed_style *style, |
unsigned int side, bool margin, bool border, bool padding, |
int *fixed, float *frac); |
static void layout_lists(struct box *box, |
const struct font_functions *font_func); |
static void layout_position_relative(struct box *root, struct box *fp, |
int fx, int fy); |
static void layout_compute_relative_offset(struct box *box, int *x, int *y); |
static bool layout_position_absolute(struct box *box, |
struct box *containing_block, |
int cx, int cy, |
html_content *content); |
static bool layout_absolute(struct box *box, struct box *containing_block, |
int cx, int cy, |
html_content *content); |
static void layout_compute_offsets(struct box *box, |
struct box *containing_block, |
int *top, int *right, int *bottom, int *left); |
/** |
* Calculate positions of boxes in a document. |
* |
* \param doc content of type CONTENT_HTML |
* \param width available width |
* \param height available height |
* \return true on success, false on memory exhaustion |
*/ |
bool layout_document(html_content *content, int width, int height) |
{ |
bool ret; |
struct box *doc = content->layout; |
const struct font_functions *font_func = content->font_func; |
layout_minmax_block(doc, font_func); |
layout_block_find_dimensions(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 <html> and <body> 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); |
layout_position_absolute(doc, doc, 0, 0, content); |
layout_position_relative(doc, doc, 0, 0); |
layout_calculate_descendant_bboxes(doc); |
return ret; |
} |
/** |
* 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. |
*/ |
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->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); |
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(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) { |
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(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)); |
/* 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 && |
css_computed_overflow(box->style) != |
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(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 && |
css_computed_overflow(box->style) != |
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; |
} |
#ifdef LAYOUT_DEBUG |
LOG(("box %p, cx %i, cy %i", box, cx, cy)); |
#endif |
/* 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(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(block, NULL); |
} |
return true; |
} |
/** |
* Calculate minimum and maximum width of a block. |
* |
* \param block box of type BLOCK, INLINE_BLOCK, or TABLE_CELL |
* \post block->min_width and block->max_width filled in, |
* 0 <= block->min_width <= block->max_width |
*/ |
void layout_minmax_block(struct box *block, |
const struct font_functions *font_func) |
{ |
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; |
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); |
} |
/* 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(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(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); |
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); |
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); |
if (child_has_height && |
child == |
child->parent->children) { |
block->flags |= MAKE_HEIGHT; |
} |
break; |
case BOX_TABLE: |
layout_minmax_table(child, font_func); |
/* 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); |
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(width, wunit, block->style)); |
} |
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 (block->gadget && wtype == CSS_WIDTH_SET && |
(block->gadget->type == GADGET_SUBMIT || |
block->gadget->type == GADGET_RESET || |
block->gadget->type == GADGET_BUTTON)) { |
/* some gadgets with specified width already include border and |
* padding, so just get margin */ |
calculate_mbp_width(block->style, LEFT, true, false, false, |
&extra_fixed, &extra_frac); |
calculate_mbp_width(block->style, RIGHT, true, false, false, |
&extra_fixed, &extra_frac); |
} else { |
calculate_mbp_width(block->style, LEFT, true, true, true, |
&extra_fixed, &extra_frac); |
calculate_mbp_width(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); |
} |
/** |
* Find next block that current margin collapses to. |
* |
* \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. |
*/ |
struct box* layout_next_margin_block(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(box->parent->width, |
viewport_height, box, |
box->style, |
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(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(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(box->parent->width, |
viewport_height, box, |
box->style, |
NULL, NULL, NULL, NULL, |
box->margin, box->padding, |
box->border); |
} |
} |
} |
return NULL; |
} |
/** |
* 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 |
*/ |
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); |
#ifdef LAYOUT_DEBUG |
LOG(("block %p, object %s, width %i", block, |
hlcache_handle_get_url(block->object), block->width)); |
#endif |
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; |
} |
/** |
* 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. |
* |
* See CSS 2.1 sections 10.3 and 10.6. |
*/ |
void layout_get_object_dimensions(struct box *box, int *width, int *height, |
int min_width, int max_width) |
{ |
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; |
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) |
*height = (*width * intrinsic_height) / |
intrinsic_width; |
} 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; |
} |
} |
/** |
* Compute dimensions of box, margins, paddings, and borders for a block-level |
* element. |
* |
* \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. |
*/ |
void layout_block_find_dimensions(int available_width, int viewport_height, |
int lm, int rm, struct box *box) |
{ |
int width, max_width, min_width; |
int 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(available_width, viewport_height, box, style, |
&width, &height, &max_width, &min_width, |
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); |
} |
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; |
} |
/** |
* Manimpulate box height according to CSS min-height and max-height properties |
* |
* \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 |
*/ |
bool layout_apply_minmax_height(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(value, unit, |
box->style)); |
if (h < box->height) { |
box->height = h; |
updated = true; |
} |
} |
} |
/* min-height */ |
if (css_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(value, unit, |
box->style)); |
if (h > box->height) { |
box->height = h; |
updated = true; |
} |
} |
} |
} |
return updated; |
} |
/** |
* 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. |
*/ |
void layout_block_add_scrollbar(struct box *box, int which) |
{ |
enum css_overflow_e overflow; |
assert(box->type == BOX_BLOCK && (which == RIGHT || which == BOTTOM)); |
if (box->style == NULL) |
return; |
overflow = css_computed_overflow(box->style); |
if (overflow == CSS_OVERFLOW_SCROLL || overflow == CSS_OVERFLOW_AUTO || |
(box->object && content_get_type(box->object) == |
CONTENT_HTML)) { |
/* make space for scrollbars, unless height/width are 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 == BOTTOM && box->height != AUTO && |
(overflow == CSS_OVERFLOW_SCROLL || |
box_hscrollbar_present(box))) { |
box->padding[BOTTOM] += SCROLLBAR_WIDTH; |
} |
if (which == RIGHT && box->width != AUTO && |
htype == CSS_HEIGHT_SET && |
(overflow == CSS_OVERFLOW_SCROLL || |
box_vscrollbar_present(box))) { |
box->width -= SCROLLBAR_WIDTH; |
box->padding[RIGHT] += SCROLLBAR_WIDTH; |
} |
} |
} |
/** |
* 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. |
*/ |
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) { |
/* 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; |
width = available_width - |
(box->margin[LEFT] + box->border[LEFT].width + |
box->padding[LEFT] + box->padding[RIGHT] + |
box->border[RIGHT].width + box->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) |
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 floating |
* element using shrink-to-fit. Also used for inline-blocks. |
* |
* \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. |
*/ |
void layout_float_find_dimensions(int available_width, |
const css_computed_style *style, struct box *box) |
{ |
int width, height, max_width, min_width; |
int *margin = box->margin; |
int *padding = box->padding; |
struct box_border *border = box->border; |
int scrollbar_width = |
(css_computed_overflow(style) == CSS_OVERFLOW_SCROLL || |
css_computed_overflow(style) == CSS_OVERFLOW_AUTO) ? |
SCROLLBAR_WIDTH : 0; |
layout_find_dimensions(available_width, -1, box, style, &width, &height, |
&max_width, &min_width, margin, padding, border); |
if (margin[LEFT] == AUTO) |
margin[LEFT] = 0; |
if (margin[RIGHT] == AUTO) |
margin[RIGHT] = 0; |
padding[RIGHT] += scrollbar_width; |
padding[BOTTOM] += scrollbar_width; |
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); |
} 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(size, unit, |
box->style)); |
} |
if (box->gadget->type == GADGET_FILE && |
height == AUTO) { |
size = FLTTOFIX(1.5); |
height = FIXTOINT(nscss_len2px(size, unit, |
box->style)); |
} |
} |
if (box->gadget->type == GADGET_TEXTAREA) { |
if (width == AUTO) { |
size = INTTOFIX(10); |
width = FIXTOINT(nscss_len2px(size, unit, |
box->style)); |
} else { |
width -= scrollbar_width; |
} |
if (height == AUTO) { |
size = INTTOFIX(4); |
height = FIXTOINT(nscss_len2px(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(box->style, LEFT, true, true, true, |
&fixed, &frac); |
calculate_mbp_width(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; |
} |
box->width = width; |
box->height = height; |
if (margin[TOP] == AUTO) |
margin[TOP] = 0; |
if (margin[BOTTOM] == AUTO) |
margin[BOTTOM] = 0; |
} |
/** |
* Calculate width, height, and thickness of margins, paddings, and borders. |
* |
* \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 margin[4] filled with margins, may be NULL |
* \param padding[4] filled with paddings, may be NULL |
* \param border[4] filled with border widths, may be NULL |
*/ |
void layout_find_dimensions(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 margin[4], int padding[4], struct box_border border[4]) |
{ |
struct box *containing_block = NULL; |
unsigned int i; |
bool percentage; |
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(value, unit, |
style)); |
} |
} else { |
*width = AUTO; |
} |
/* specified gadget widths include borders and padding in some |
* cases */ |
if (box->gadget && *width != AUTO) { |
percentage = unit == CSS_UNIT_PCT; |
layout_tweak_form_dimensions(box, percentage, |
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(value, unit, |
style)); |
} |
} else { |
*height = AUTO; |
} |
/* specified gadget heights include borders and padding in |
* some cases */ |
if (box->gadget && *height != AUTO) { |
percentage = unit == CSS_UNIT_PCT; |
layout_tweak_form_dimensions(box, percentage, |
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(value, unit, |
style)); |
} |
} else { |
/* Inadmissible */ |
*max_width = -1; |
} |
/* specified gadget widths include borders and padding in some |
* cases */ |
if (box->gadget && *max_width != -1) { |
percentage = unit == CSS_UNIT_PCT; |
layout_tweak_form_dimensions(box, percentage, |
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 = css_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(value, unit, |
style)); |
} |
} else { |
/* Inadmissible */ |
*min_width = 0; |
} |
/* specified gadget widths include borders and padding in some |
* cases */ |
if (box->gadget && *min_width != 0) { |
percentage = unit == CSS_UNIT_PCT; |
layout_tweak_form_dimensions(box, percentage, |
available_width, true, min_width); |
} |
} |
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; |
switch (i) { |
case TOP: |
type = css_computed_margin_top(style, |
&value, &unit); |
break; |
case RIGHT: |
type = css_computed_margin_right(style, |
&value, &unit); |
break; |
case BOTTOM: |
type = css_computed_margin_bottom(style, |
&value, &unit); |
break; |
case LEFT: |
type = css_computed_margin_left(style, |
&value, &unit); |
break; |
} |
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(value, |
unit, style)); |
} |
} else { |
margin[i] = AUTO; |
} |
} |
if (padding) { |
css_fixed value = 0; |
css_unit unit = CSS_UNIT_PX; |
switch (i) { |
case TOP: |
css_computed_padding_top(style, &value, &unit); |
break; |
case RIGHT: |
css_computed_padding_right(style, &value, |
&unit); |
break; |
case BOTTOM: |
css_computed_padding_bottom(style, &value, |
&unit); |
break; |
case LEFT: |
css_computed_padding_left(style, &value, &unit); |
break; |
} |
if (unit == CSS_UNIT_PCT) { |
padding[i] = FPCT_OF_INT_TOINT(value, |
available_width); |
} else { |
padding[i] = FIXTOINT(nscss_len2px(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; |
switch (i) { |
case TOP: |
css_computed_border_top_width(style, &value, |
&unit); |
bstyle = css_computed_border_top_style(style); |
css_computed_border_top_color(style, &color); |
break; |
case RIGHT: |
css_computed_border_right_width(style, &value, |
&unit); |
bstyle = css_computed_border_right_style(style); |
css_computed_border_right_color(style, &color); |
break; |
case BOTTOM: |
css_computed_border_bottom_width(style, &value, |
&unit); |
bstyle = css_computed_border_bottom_style( |
style); |
css_computed_border_bottom_color(style, &color); |
break; |
case LEFT: |
css_computed_border_left_width(style, &value, |
&unit); |
bstyle = css_computed_border_left_style(style); |
css_computed_border_left_color(style, &color); |
break; |
} |
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(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; |
} |
} |
} |
/** |
* Under some circumstances, specified dimensions for form elements include |
* borders and padding. |
* |
* \param box gadget to adjust dimensions of |
* \param percentage whether the gadget has its dimension specified as a |
* percentage |
* \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. |
*/ |
void layout_tweak_form_dimensions(struct box *box, bool percentage, |
int available_width, bool setwidth, int *dimension) |
{ |
int fixed = 0; |
float frac = 0; |
assert(box && box->gadget); |
/* specified gadget widths include borders and padding in some |
* cases */ |
if (percentage || box->gadget->type == GADGET_SUBMIT || |
box->gadget->type == GADGET_RESET || |
box->gadget->type == GADGET_BUTTON) { |
calculate_mbp_width(box->style, setwidth ? LEFT : TOP, |
false, true, true, &fixed, &frac); |
calculate_mbp_width(box->style, setwidth ? RIGHT : BOTTOM, |
false, true, true, &fixed, &frac); |
*dimension -= frac * available_width + fixed; |
*dimension = *dimension > 0 ? *dimension : 0; |
} |
} |
/** |
* 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 |
*/ |
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 |
*/ |
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; |
#ifdef LAYOUT_DEBUG |
LOG(("y0 %i, y1 %i, x0 %i, x1 %i", y0, y1, *x0, *x1)); |
#endif |
*left = *right = 0; |
for (; fl; fl = fl->next_float) { |
fy0 = fl->y; |
fy1 = fl->y + fl->height; |
if (y0 < fy1 && fy0 <= y1) { |
if (fl->type == BOX_FLOAT_LEFT) { |
fx1 = fl->x + fl->width; |
if (*x0 < fx1) { |
*x0 = fx1; |
*left = fl; |
} |
} else if (fl->type == BOX_FLOAT_RIGHT) { |
fx0 = fl->x; |
if (fx0 < *x1) { |
*x1 = fx0; |
*right = fl; |
} |
} |
} |
} |
#ifdef LAYOUT_DEBUG |
LOG(("x0 %i, x1 %i, left %p, right %p", *x0, *x1, *left, *right)); |
#endif |
} |
/** |
* Layout lines of text or inline boxes with floats. |
* |
* \param box inline container |
* \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 |
*/ |
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); |
#ifdef LAYOUT_DEBUG |
LOG(("inline_container %p, width %i, cont %p, cx %i, cy %i", |
inline_container, width, cont, cx, cy)); |
#endif |
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; ) { |
#ifdef LAYOUT_DEBUG |
LOG(("c %p", c)); |
#endif |
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; |
} |
/** |
* Calculate minimum and maximum width of an inline container. |
* |
* \param inline_container box of type INLINE_CONTAINER |
* \post inline_container->min_width and inline_container->max_width filled in, |
* 0 <= inline_container->min_width <= inline_container->max_width |
*/ |
void layout_minmax_inline_container(struct box *inline_container, |
bool *has_height, const struct font_functions *font_func) |
{ |
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); |
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 line height from a style. |
*/ |
int line_height(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(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(lhvalue, lhunit, style); |
} |
return FIXTOINT(line_height); |
} |
/** |
* 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 font_functions *font_func = content->font_func; |
if (space_width == 0) { |
/* Currently split_box has no space. */ |
/* Get the space width because the split_box will need it */ |
/* Don't set it in split_box yet, or it will get cloned. */ |
font_func->font_width(fstyle, " ", 1, &space_width); |
} else if (space_width == UNKNOWN_WIDTH) { |
/* Split_box has a space but its width is unknown. */ |
/* Get the space width because the split_box will need it */ |
/* Set it in split_box, so it gets cloned. */ |
font_func->font_width(fstyle, " ", 1, &space_width); |
split_box->space = space_width; |
} |
/* 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 */ |
if (split_box->parent->parent->gadget != NULL) { |
/* Inside a form text input / textarea, special case */ |
/* TODO: Move text inputs to core textarea widget and remove |
* this */ |
c2->text = talloc_strndup(content->bctx, |
split_box->text + new_length + 1, |
split_box->length - (new_length + 1)); |
if (!c2->text) |
return false; |
} else { |
c2->text += new_length + 1; |
} |
/* 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 - (new_length + 1); |
/* 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; |
return true; |
} |
/** |
* 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 |
*/ |
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 font_functions *font_func = content->font_func; |
plot_font_style_t fstyle; |
#ifdef LAYOUT_DEBUG |
LOG(("first %p, first->text '%.*s', width %i, y %i, cx %i, cy %i", |
first, (int) first->length, first->text, *width, |
*y, cx, cy)); |
#endif |
/* 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(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(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() */ |
#ifdef LAYOUT_DEBUG |
LOG(("x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0)); |
#endif |
for (x = 0, b = first; x <= x1 - x0 && b != 0; b = b->next) { |
int min_width, max_width; |
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); |
#ifdef LAYOUT_DEBUG |
LOG(("pass 1: b %p, x %i", b, x)); |
#endif |
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(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(*width, -1, b, b->style, 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->font_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(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->font_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->font_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->font_width(&fstyle, b->text, |
b->length, &b->width); |
b->flags |= MEASURED; |
} |
x += b->width; |
if (b->space == UNKNOWN_WIDTH) { |
font_func->font_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(*width, -1, b, b->style, |
&b->width, &b->height, &max_width, &min_width, |
NULL, NULL, NULL); |
if (b->object && !(b->flags & REPLACE_DIM)) { |
layout_get_object_dimensions(b, &b->width, &b->height, |
min_width, max_width); |
} 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(INTTOFIX(1), |
CSS_UNIT_EM, b->style)); |
if (b->height == AUTO) |
b->height = FIXTOINT(nscss_len2px(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(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 */ |
#ifdef LAYOUT_DEBUG |
LOG(("x0 %i, x1 %i, x1 - x0 %i", x0, x1, x1 - x0)); |
#endif |
for (x = x_previous = 0, b = first; x <= x1 - x0 && b; b = b->next) { |
#ifdef LAYOUT_DEBUG |
LOG(("pass 2: b %p, x %i", b, x)); |
#endif |
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(b->style, |
&fstyle); |
/** \todo handle errors */ |
font_func->font_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 */ |
#ifdef LAYOUT_DEBUG |
LOG(("float %p", b)); |
#endif |
d = b->children; |
d->float_children = 0; |
b->float_container = d->float_container = cont; |
if (!layout_float(d, *width, content)) |
return false; |
#ifdef LAYOUT_DEBUG |
LOG(("%p : %d %d", d, d->margin[TOP], |
d->border[TOP].width)); |
#endif |
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) { |
/* + 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; |
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; |
} |
if (cont->float_children == b) { |
#ifdef LAYOUT_DEBUG |
LOG(("float %p already placed", b)); |
#endif |
box_dump(stderr, cont, 0); |
assert(0); |
} |
b->next_float = cont->float_children; |
cont->float_children = b; |
split_box = 0; |
} |
} |
if (x1 - x0 < x && split_box) { |
/* the last box went over the end */ |
unsigned int i; |
size_t space = 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 ((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) { |
/* skip leading spaces, otherwise code gets fooled into |
* thinking it's all one long word */ |
for (i = 0; i != split_box->length && |
split_box->text[i] == ' '; i++) |
; |
/* find end of word */ |
for (; i != split_box->length && |
split_box->text[i] != ' '; i++) |
; |
if (i != split_box->length) |
space = i; |
} |
/* space != 0 implies split_box->text != 0 */ |
if (space == 0 || no_wrap) |
w = split_box->width; |
else { |
font_plot_style_from_css(split_box->style, &fstyle); |
/** \todo handle errors */ |
font_func->font_width(&fstyle, split_box->text, |
space, &w); |
} |
#ifdef LAYOUT_DEBUG |
LOG(("splitting: split_box %p \"%.*s\", space %zu, w %i, " |
"left %p, right %p, inline_count %u", |
split_box, (int) split_box->length, |
split_box->text, space, w, |
left, right, inline_count)); |
#endif |
if ((space == 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 (space == 0 || no_wrap) { |
/* 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, space, w)) |
return false; |
b = split_box->next; |
} |
x += space_before + w; |
#ifdef LAYOUT_DEBUG |
LOG(("forcing")); |
#endif |
} else if ((space == 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) { |
#ifdef LAYOUT_DEBUG |
LOG(("cy %i, left->y %i, left->height %i", |
cy, left->y, left->height)); |
#endif |
used_height = left->y + left->height - cy + 1; |
#ifdef LAYOUT_DEBUG |
LOG(("used_height %i", used_height)); |
#endif |
} |
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; |
#ifdef LAYOUT_DEBUG |
LOG(("moving below float")); |
#endif |
} else if (space == 0 || x1 - x0 <= x + space_before + w) { |
/* first word of box doesn't fit so leave box for next |
* line */ |
b = split_box; |
#ifdef LAYOUT_DEBUG |
LOG(("leaving for next line")); |
#endif |
} else { |
/* fit as many words as possible */ |
assert(space != 0); |
font_plot_style_from_css(split_box->style, &fstyle); |
/** \todo handle errors */ |
font_func->font_split(&fstyle, |
split_box->text, split_box->length, |
x1 - x0 - x - space_before, &space, &w); |
#ifdef LAYOUT_DEBUG |
LOG(("'%.*s' %i %zu %i", (int) split_box->length, |
split_box->text, x1 - x0, space, w)); |
#endif |
if (space == 0) |
space = 1; |
if (space != split_box->length) { |
if (!layout_text_box_split(content, &fstyle, |
split_box, space, w)) |
return false; |
b = split_box->next; |
} |
x += space_before + w; |
#ifdef LAYOUT_DEBUG |
LOG(("fitting words")); |
#endif |
} |
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; |
} |
/** |
* 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 |
* \return first box in next line, or 0 if no more lines |
* \post 0 <= *line_min <= *line_max |
*/ |
struct box *layout_minmax_line(struct box *first, |
int *line_min, int *line_max, bool first_line, |
bool *line_has_height, const struct font_functions *font_func) |
{ |
int min = 0, max = 0, width, height, fixed; |
float frac; |
size_t i, j; |
struct box *b; |
plot_font_style_t fstyle; |
assert(first->parent); |
assert(first->parent->parent); |
assert(first->parent->parent->style); |
*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; |
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); |
#ifdef LAYOUT_DEBUG |
LOG(("%p: min %i, max %i", b, min, max)); |
#endif |
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); |
else |
layout_minmax_table(b->children, font_func); |
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); |
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(b->style, &fstyle); |
if (b->type == BOX_INLINE && !b->object && |
!(b->flags & REPLACE_DIM) && |
!(b->flags & IFRAME)) { |
fixed = frac = 0; |
calculate_mbp_width(b->style, LEFT, true, true, true, |
&fixed, &frac); |
if (!b->inline_end) |
calculate_mbp_width(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(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->font_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 */ |
if (!b->text) |
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->font_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->font_width(&fstyle, b->text, |
b->length, &b->width); |
b->flags |= MEASURED; |
} |
} |
max += b->width; |
if (b->next) { |
if (b->space == UNKNOWN_WIDTH) { |
font_func->font_width(&fstyle, " ", 1, |
&b->space); |
} |
max += b->space; |
} |
/* min = widest word */ |
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.) */ |
i = 0; |
do { |
for (j = i; j != b->length && |
b->text[j] != ' '; j++) |
; |
font_func->font_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); |
if (wtype == CSS_WIDTH_SET) { |
if (unit == CSS_UNIT_PCT) { |
/* |
b->width = FPCT_OF_INT_TOINT(value, width); |
*/ |
width = AUTO; |
} else { |
width = FIXTOINT(nscss_len2px(value, unit, |
b->style)); |
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(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); |
} |
fixed = frac = 0; |
calculate_mbp_width(b->style, LEFT, true, true, true, |
&fixed, &frac); |
calculate_mbp_width(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; |
calculate_mbp_width(b->style, LEFT, true, true, true, |
&fixed, &frac); |
calculate_mbp_width(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(INTTOFIX(1), |
CSS_UNIT_EM, b->style)); |
} |
if (min < width) |
min = width; |
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( |
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; |
#ifdef LAYOUT_DEBUG |
LOG(("line_min %i, line_max %i", min, max)); |
#endif |
assert(b != first); |
assert(0 <= *line_min); |
assert(*line_min <= *line_max); |
return b; |
} |
/** |
* Calculate the text-indent length. |
* |
* \param style style of block |
* \param width width of containing block |
* \return length of indent |
*/ |
int layout_text_indent(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(value, unit, style)); |
} |
} |
/** |
* 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 |
*/ |
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(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 |
*/ |
void place_float_below(struct box *c, int width, int cx, int y, |
struct box *cont) |
{ |
int x0, x1, yy = y; |
struct box *left; |
struct box *right; |
#ifdef LAYOUT_DEBUG |
LOG(("c %p, width %i, cx %i, y %i, cont %p", c, width, cx, y, cont)); |
#endif |
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; |
} |
/** |
* 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 |
*/ |
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(available_width, -1, table, style, 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) { |
assert(c->style); |
table_used_border_for_cell(c); |
layout_find_dimensions(available_width, -1, |
c, c->style, 0, 0, 0, 0, 0, |
c->padding, c->border); |
if (css_computed_overflow(c->style) == |
CSS_OVERFLOW_SCROLL || |
css_computed_overflow(c->style) == |
CSS_OVERFLOW_AUTO) { |
c->padding[RIGHT] += SCROLLBAR_WIDTH; |
c->padding[BOTTOM] += 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(h, hu, style)); |
border_spacing_v = FIXTOINT(nscss_len2px(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(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(value, unit, style)); |
} |
} |
/* calculate width required by cells */ |
for (i = 0; i != columns; i++) { |
#ifdef LAYOUT_DEBUG |
LOG(("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)); |
#endif |
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; |
#ifdef LAYOUT_DEBUG |
LOG(("required_width %i", required_width)); |
#endif |
} |
required_width += (columns + 1 - positioned_columns) * |
border_spacing_h; |
#ifdef LAYOUT_DEBUG |
LOG(("width %i, min %i, max %i, auto %i, required %i", |
table_width, table->min_width, table->max_width, |
auto_width, required_width)); |
#endif |
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(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->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(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; |
} |
/** |
* Calculate minimum and maximum width of a table. |
* |
* \param table box of type TABLE |
* \post table->min_width and table->max_width filled in, |
* 0 <= table->min_width <= table->max_width |
*/ |
void layout_minmax_table(struct box *table, |
const struct font_functions *font_func) |
{ |
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 = table->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; |
/* 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(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); |
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); |
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); |
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(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(table->style, LEFT, true, true, true, |
&extra_fixed, &extra_frac); |
calculate_mbp_width(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); |
} |
/** |
* 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 |
*/ |
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; |
} |
} |
/** |
* Determine width of margin, borders, and padding on one side of a box. |
* |
* \param style style to measure |
* \param size 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 |
*/ |
void calculate_mbp_width(const css_computed_style *style, unsigned int side, |
bool margin, bool border, bool padding, |
int *fixed, float *frac) |
{ |
typedef uint8_t (*len_func)(const css_computed_style *style, |
css_fixed *length, css_unit *unit); |
static len_func margin_funcs[4] = { |
css_computed_margin_top, |
css_computed_margin_right, |
css_computed_margin_bottom, |
css_computed_margin_left |
}; |
static len_func padding_funcs[4] = { |
css_computed_padding_top, |
css_computed_padding_right, |
css_computed_padding_bottom, |
css_computed_padding_left |
}; |
static struct { |
len_func width; |
uint8_t (*style)(const css_computed_style *style); |
} border_funcs[4] = { |
{ css_computed_border_top_width, |
css_computed_border_top_style }, |
{ css_computed_border_right_width, |
css_computed_border_right_style }, |
{ css_computed_border_bottom_width, |
css_computed_border_bottom_style }, |
{ css_computed_border_left_width, |
css_computed_border_left_style } |
}; |
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(value, unit, |
style)); |
} |
} |
} |
/* border */ |
if (border) { |
if (border_funcs[side].style(style) != |
CSS_BORDER_STYLE_NONE) { |
border_funcs[side].width(style, &value, &unit); |
*fixed += FIXTOINT(nscss_len2px(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(value, unit, style)); |
} |
} |
} |
/** |
* Layout list markers. |
*/ |
void layout_lists(struct box *box, |
const struct font_functions *font_func) |
{ |
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(marker->style) - |
marker->height) / 2; |
} else if (marker->text) { |
if (marker->width == UNKNOWN_WIDTH) { |
font_plot_style_from_css(marker->style, |
&fstyle); |
font_func->font_width(&fstyle, |
marker->text, |
marker->length, |
&marker->width); |
marker->flags |= MEASURED; |
} |
marker->x = -marker->width; |
marker->y = 0; |
marker->height = line_height(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); |
} |
} |
/** |
* Adjust positions of relatively positioned boxes. |
* |
* \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" |
*/ |
void layout_position_relative(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(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(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; |
} |
} |
} |
} |
/** |
* Compute a box's relative offset as per CSS 2.1 9.4.3 |
* |
* \param box Box to compute relative offsets for. |
* \param x Receives relative offset in x. |
* \param y Receives relative offset in y. |
*/ |
void layout_compute_relative_offset(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(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 if (bottom == AUTO) |
bottom = -top; |
else |
bottom = -top; |
#ifdef LAYOUT_DEBUG |
LOG(("left %i, right %i, top %i, bottom %i", left, right, top, bottom)); |
#endif |
*x = left; |
*y = top; |
} |
/** |
* 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 |
*/ |
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; |
} |
/** |
* 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 |
*/ |
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(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(available_width, -1, box, box->style, |
&width, &height, &max_width, &min_width, |
margin, padding, border); |
box->float_container = NULL; |
/* 10.3.7 */ |
#ifdef LAYOUT_DEBUG |
LOG(("%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)); |
#endif |
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 (min_width > 0 && 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 (min_width > 0 && 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 (min_width > 0 && 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; |
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 (min_width > 0 && 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 (min_width > 0 && 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]; |
} |
} |
#ifdef LAYOUT_DEBUG |
LOG(("%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)); |
#endif |
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 */ |
#ifdef LAYOUT_DEBUG |
LOG(("%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)); |
#endif |
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]; |
} |
} |
#ifdef LAYOUT_DEBUG |
LOG(("%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)); |
#endif |
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(box, containing_block); |
return true; |
} |
/** |
* Compute box offsets for a relatively or absolutely positioned box with |
* respect to a box. |
* |
* \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. |
*/ |
void layout_compute_offsets(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(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(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(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(value, unit, |
box->style)); |
} |
} else { |
*bottom = AUTO; |
} |
} |
/** |
* Find a box's bounding box relative to itself, i.e. the box's border edge box |
* |
* \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(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; |
} |
/** |
* Apply changes to box descendant_[xy][01] values due to given child. |
* |
* \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(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); |
if (child->style == NULL || |
(child->style && css_computed_overflow(child->style) == |
CSS_OVERFLOW_VISIBLE && html_object == false)) { |
/* get child's descendant bbox relative to box */ |
child_desc_x0 = child_x + child->descendant_x0; |
child_desc_y0 = child_y + child->descendant_y0; |
child_desc_x1 = child_x + child->descendant_x1; |
child_desc_y1 = child_y + child->descendant_y1; |
} else { |
/* child's descendants don't matter; use child's border edge */ |
layout_get_box_bbox(child, &child_desc_x0, &child_desc_y0, |
&child_desc_x1, &child_desc_y1); |
/* get the bbox relative to box */ |
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 box tree of boxes to update |
*/ |
void layout_calculate_descendant_bboxes(struct box *box) |
{ |
struct box *child; |
assert((box->width != UNKNOWN_WIDTH) && (box->height != AUTO)); |
/* assert((box->width >= 0) && (box->height >= 0)); */ |
/* Initialise box's descendant box to border edge box */ |
layout_get_box_bbox(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(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(child); |
if (box->style && css_computed_overflow(box->style) == |
CSS_OVERFLOW_HIDDEN) |
continue; |
layout_update_descendant_bbox(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(child); |
layout_update_descendant_bbox(box, child, 0, 0); |
} |
if (box->list_marker) { |
child = box->list_marker; |
layout_calculate_descendant_bboxes(child); |
layout_update_descendant_bbox(box, child, 0, 0); |
} |
} |
/contrib/network/netsurf/netsurf/render/layout.h |
---|
0,0 → 1,39 |
/* |
* Copyright 2003 James Bursa <bursa@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \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; |
bool layout_document(struct html_content *content, int width, int height); |
bool layout_inline_container(struct box *box, int width, |
struct box *cont, int cx, int cy, struct html_content *content); |
void layout_calculate_descendant_bboxes(struct box *box); |
void layout_minmax_table(struct box *table, |
const struct font_functions *font_func); |
#endif |
/contrib/network/netsurf/netsurf/render/list.c |
---|
0,0 → 1,483 |
/* |
* Copyright 2005 Richard Wilson <info@tinct.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* HTML lists (implementation). |
*/ |
#include <assert.h> |
#include <ctype.h> |
#include <stdlib.h> |
#include <string.h> |
#include <strings.h> |
#include "css/css.h" |
#include "render/list.h" |
#include "utils/log.h" |
struct list_counter { |
char *name; /** Counter name */ |
struct list_counter_state *first; /** First counter state */ |
struct list_counter_state *state; /** Current counter state */ |
struct list_counter *next; /** Next counter */ |
}; |
struct list_counter_state { |
int count; /** Current count */ |
struct list_counter_state *parent; /** Parent counter, or NULL */ |
struct list_counter_state *next; /** Next counter, or NULL */ |
}; |
static struct list_counter *list_counter_pool = NULL; |
static char list_counter_workspace[16]; |
static const char *list_counter_roman[] = { "I", "IV", "V", "IX", |
"X", "XL", "L", "XC", |
"C", "CD", "D", "CM", |
"M"}; |
static const int list_counter_decimal[] = { 1, 4, 5, 9, |
10, 40, 50, 90, |
100, 400, 500, 900, |
1000}; |
#define ROMAN_DECIMAL_CONVERSIONS (sizeof(list_counter_decimal) \ |
/ sizeof(list_counter_decimal[0])) |
static struct list_counter *render_list_find_counter(const char *name); |
static char *render_list_encode_counter(struct list_counter_state *state, |
enum css_list_style_type_e style); |
static char *render_list_encode_roman(int value); |
/* |
static void render_list_counter_output(char *name); |
*/ |
/** |
* Finds a counter from the current pool, or adds a new one. |
* |
* \param name the name of the counter to find |
* \return the counter, or NULL if it couldn't be found/created. |
*/ |
static struct list_counter *render_list_find_counter(const char *name) { |
struct list_counter *counter; |
assert(name); |
/* find a current counter */ |
for (counter = list_counter_pool; counter; counter = counter->next) |
if (!strcasecmp(name, counter->name)) |
return counter; |
/* create a new counter */ |
counter = calloc(1, sizeof(struct list_counter)); |
if (!counter) { |
LOG(("No memory for calloc()")); |
return NULL; |
} |
counter->name = malloc(strlen(name) + 1); |
if (!counter->name) { |
LOG(("No memory for malloc()")); |
free(counter); |
return NULL; |
} |
strcpy(counter->name, name); |
counter->next = list_counter_pool; |
list_counter_pool = counter; |
return counter; |
} |
/** |
* Removes all counters from the current pool. |
*/ |
void render_list_destroy_counters(void) { |
struct list_counter *counter = list_counter_pool; |
struct list_counter *next_counter; |
struct list_counter_state *state; |
struct list_counter_state *next_state; |
while (counter) { |
next_counter = counter->next; |
free(counter->name); |
state = counter->first; |
free(counter); |
counter = next_counter; |
while (state) { |
next_state = state->next; |
free(state); |
state = next_state; |
} |
} |
list_counter_pool = NULL; |
} |
/** |
* Resets a counter in accordance with counter-reset (CSS 2.1/12.4). |
* |
* \param name the name of the counter to reset |
* \param value the value to reset the counter to |
* \return true on success, false on failure. |
*/ |
bool render_list_counter_reset(const char *name, int value) { |
struct list_counter *counter; |
struct list_counter_state *state; |
struct list_counter_state *link; |
assert(name); |
counter = render_list_find_counter(name); |
if (!counter) |
return false; |
state = calloc(1, sizeof(struct list_counter_state)); |
if (!state) { |
LOG(("No memory for calloc()")); |
return false; |
} |
state->count = value; |
state->parent = counter->state; |
counter->state = state; |
if (!counter->first) { |
counter->first = state; |
} else { |
for (link = counter->first; link->next; link = link->next); |
link->next = state; |
} |
/* render_list_counter_output(name); |
*/ return true; |
} |
/** |
* Increments a counter in accordance with counter-increment (CSS 2.1/12.4). |
* |
* \param name the name of the counter to reset |
* \param value the value to increment the counter by |
* \return true on success, false on failure. |
*/ |
bool render_list_counter_increment(const char *name, int value) { |
struct list_counter *counter; |
assert(name); |
counter = render_list_find_counter(name); |
if (!counter) |
return false; |
/* if no counter-reset used, it is assumed the counter has been reset |
* to 0 by the root element. */ |
if (!counter->state) { |
if (counter->first) { |
counter->state = counter->first; |
counter->state->count = 0; |
} else { |
render_list_counter_reset(name, 0); |
} |
} |
if (counter->state) |
counter->state->count += value; |
/* render_list_counter_output(name); |
*/ return counter->state != NULL; |
} |
/** |
* Ends the scope of a counter. |
* |
* \param name the name of the counter to end the scope for |
* \return true on success, false on failure. |
*/ |
bool render_list_counter_end_scope(const char *name) { |
struct list_counter *counter; |
assert(name); |
counter = render_list_find_counter(name); |
if ((!counter) || (!counter->state)) |
return false; |
counter->state = counter->state->parent; |
/* render_list_counter_output(name); |
*/ return true; |
} |
/** |
* Returns a textual representation of a counter for counter() or counters() |
* (CSS 2.1/12.2). |
* |
* \param css_counter the counter to convert |
* \return a textual representation of the counter, or NULL on failure |
*/ |
char *render_list_counter(const css_computed_content_item *css_counter) { |
struct list_counter *counter; |
struct list_counter_state *state; |
char *compound = NULL; |
char *merge, *extend; |
lwc_string *name = NULL, *sep = NULL; |
uint8_t style; |
assert(css_counter); |
if (css_counter->type == CSS_COMPUTED_CONTENT_COUNTER) { |
name = css_counter->data.counter.name; |
style = css_counter->data.counter.style; |
} else { |
assert(css_counter->type == CSS_COMPUTED_CONTENT_COUNTERS); |
name = css_counter->data.counters.name; |
sep = css_counter->data.counters.sep; |
style = css_counter->data.counters.style; |
} |
counter = render_list_find_counter(lwc_string_data(name)); |
if (!counter) { |
LOG(("Failed to find/create counter for conversion")); |
return NULL; |
} |
/* handle counter() first */ |
if (sep == NULL) |
return render_list_encode_counter(counter->state, style); |
/* loop through all states for counters() */ |
for (state = counter->first; state; state = state->next) { |
merge = render_list_encode_counter(state, style); |
if (!merge) { |
free(compound); |
return NULL; |
} |
if (!compound) { |
compound = merge; |
} else { |
extend = realloc(compound, strlen(compound) + |
strlen(merge) + 1); |
if (!extend) { |
LOG(("No memory for realloc()")); |
free(compound); |
free(merge); |
return NULL; |
} |
compound = extend; |
strcat(compound, merge); |
} |
if (state->next) { |
merge = realloc(compound, strlen(compound) + |
lwc_string_length(sep) + 1); |
if (!merge) { |
LOG(("No memory for realloc()")); |
free(compound); |
return NULL; |
} |
compound = merge; |
strcat(compound, lwc_string_data(sep)); |
} |
} |
return compound; |
} |
/** |
* Returns a textual representation of a counter state in a specified style. |
* |
* \param state the counter state to represent |
* \param style the counter style to use |
* \return a textual representation of the counter state, or NULL on failure |
*/ |
static char *render_list_encode_counter(struct list_counter_state *state, |
enum css_list_style_type_e style) { |
char *result = NULL; |
int i; |
/* no counter state means that the counter is currently out of scope */ |
if (!state) { |
result = malloc(1); |
if (!result) |
return NULL; |
result[0] = '\0'; |
return result; |
} |
/* perform the relevant encoding to upper case where applicable */ |
switch (style) { |
case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: |
case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: |
if (state->count <= 0) |
result = calloc(1, 1); |
else |
result = malloc(2); |
if (!result) |
return NULL; |
if (state->count > 0) { |
result[0] = 'A' + (state->count - 1) % 26; |
result[1] = '\0'; |
} |
break; |
case CSS_LIST_STYLE_TYPE_DISC: |
case CSS_LIST_STYLE_TYPE_CIRCLE: |
case CSS_LIST_STYLE_TYPE_SQUARE: |
result = malloc(2); |
if (!result) |
return NULL; |
result[0] = '?'; |
result[1] = '\0'; |
break; |
case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: |
case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: |
result = render_list_encode_roman(state->count); |
if (!result) |
return NULL; |
break; |
case CSS_LIST_STYLE_TYPE_DECIMAL: |
snprintf(list_counter_workspace, |
sizeof list_counter_workspace, |
"%i", state->count); |
result = malloc(strlen(list_counter_workspace) + 1); |
if (!result) |
return NULL; |
strcpy(result, list_counter_workspace); |
break; |
case CSS_LIST_STYLE_TYPE_NONE: |
result = malloc(1); |
if (!result) |
return NULL; |
result[0] = '\0'; |
break; |
default: |
break; |
} |
/* perform case conversion */ |
if ((style == CSS_LIST_STYLE_TYPE_LOWER_ALPHA) || |
(style == CSS_LIST_STYLE_TYPE_LOWER_ROMAN)) |
for (i = 0; result[i]; i++) |
result[i] = tolower(result[i]); |
return result; |
} |
/** |
* Encodes a value in roman numerals. |
* For values that cannot be represented (ie <=0) an empty string is returned. |
* |
* \param value the value to represent |
* \return a string containing the representation, or NULL on failure |
*/ |
static char *render_list_encode_roman(int value) { |
int i, overflow, p = 0; |
char temp[10]; |
char *result; |
/* zero and below is returned as an empty string and not erred as |
* if it is counters() will fail to complete other scopes. */ |
if (value <= 0) { |
result = malloc(1); |
if (!result) |
return NULL; |
result[0] = '\0'; |
return result; |
} |
/* we only calculate 1->999 and then add a M for each thousand */ |
overflow = value / 1000; |
value = value % 1000; |
/* work backwards through the array */ |
for (i = ROMAN_DECIMAL_CONVERSIONS - 1; i >= 0; i--) { |
while (value >= list_counter_decimal[0]) { |
if (value - list_counter_decimal[i] < |
list_counter_decimal[0] - 1) |
break; |
value -= list_counter_decimal[i]; |
temp[p++] = list_counter_roman[i][0]; |
if (i & 0x1) |
temp[p++] = list_counter_roman[i][1]; |
} |
} |
temp[p] = '\0'; |
/* create a copy for the caller including thousands */ |
result = malloc(p + overflow + 1); |
if (!result) |
return NULL; |
for (i = 0; i < overflow; i++) |
result[i] = 'M'; |
strcpy(&result[overflow], temp); |
return result; |
} |
void render_list_test(void) { |
/* example given in CSS2.1/12.4.1 */ |
/* render_list_counter_reset("item", 0); |
render_list_counter_increment("item", 1); |
render_list_counter_increment("item", 1); |
render_list_counter_reset("item", 0); |
render_list_counter_increment("item", 1); |
render_list_counter_increment("item", 1); |
render_list_counter_increment("item", 1); |
render_list_counter_reset("item", 0); |
render_list_counter_increment("item", 1); |
render_list_counter_end_scope("item"); |
render_list_counter_reset("item", 0); |
render_list_counter_increment("item", 1); |
render_list_counter_end_scope("item"); |
render_list_counter_increment("item", 1); |
render_list_counter_end_scope("item"); |
render_list_counter_increment("item", 1); |
render_list_counter_increment("item", 1); |
render_list_counter_end_scope("item"); |
render_list_counter_reset("item", 0); |
render_list_counter_increment("item", 1); |
render_list_counter_increment("item", 1); |
render_list_counter_end_scope("item"); |
*/ |
} |
/* |
static void render_list_counter_output(char *name) { |
struct list_counter *counter; |
char *result; |
struct css_counter css_counter; |
assert(name); |
counter = render_list_find_counter(name); |
if (!counter) { |
fprintf(stderr, "Unable to create counter '%s'\n", name); |
return; |
} |
css_counter.name = name; |
css_counter.style = CSS_LIST_STYLE_TYPE_LOWER_ALPHA; |
css_counter.separator = NULL; |
result = render_list_counter(&css_counter); |
if (!result) { |
fprintf(stderr, "Failed to output counter('%s')\n", name); |
} else { |
fprintf(stderr, "counter('%s') is '%s'\n", name, result); |
free(result); |
} |
css_counter.separator = "."; |
result = render_list_counter(&css_counter); |
if (!result) { |
fprintf(stderr, "Failed to output counters('%s', '.')\n", name); |
} else { |
fprintf(stderr, "counters('%s', '.') is '%s'\n", name, result); |
free(result); |
} |
} |
*/ |
/contrib/network/netsurf/netsurf/render/list.h |
---|
0,0 → 1,38 |
/* |
* Copyright 2005 Richard Wilson <info@tinct.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* HTML lists (interface). |
*/ |
#ifndef _NETSURF_RENDER_LIST_H_ |
#define _NETSURF_RENDER_LIST_H_ |
#include <stdbool.h> |
#include "css/css.h" |
void render_list_destroy_counters(void); |
bool render_list_counter_reset(const char *name, int value); |
bool render_list_counter_increment(const char *name, int value); |
bool render_list_counter_end_scope(const char *name); |
char *render_list_counter(const css_computed_content_item *css_counter); |
void render_list_test(void); |
#endif |
/contrib/network/netsurf/netsurf/render/make.render |
---|
0,0 → 1,25 |
CFLAGS += -O2 |
NETSURF_FB_FRONTEND := sdl |
NETSURF_FB_FONTLIB := internal |
NETSURF_FRAMEBUFFER_BIN := $(PREFIX)/bin/ |
# Default resource install path |
NETSURF_FRAMEBUFFER_RESOURCES := $(PREFIX)/share/netsurf/ |
# Default framebuffer search path |
NETSURF_FB_RESPATH := $${HOME}/.netsurf/:$${NETSURFRES}:$(NETSURF_FRAMEBUFFER_RESOURCES):./framebuffer/res |
# freetype compiled in font serch path |
NETSURF_FB_FONTPATH := /usr/share/fonts/truetype/ttf-dejavu:/usr/share/fonts/truetype/msttcorefonts |
OBJS := box.o box_construct.o box_normalise.o \ |
font.o form.o \ |
html.o html_script.o html_interaction.o html_redraw.o \ |
html_forms.o imagemap.o layout.o list.o search.o table.o \ |
textinput.o textplain.o |
OUTFILE = TEST.o |
CFLAGS += -I ../include/ -I ../ -I../../ -I./ -I/home/sourcerer/kos_src/newenginek/kolibri/include |
include $(MENUETDEV)/makefiles/Makefile_for_o_lib |
/contrib/network/netsurf/netsurf/render/search.c |
---|
0,0 → 1,723 |
/* |
* Copyright 2004 John M Bell <jmb202@ecs.soton.ac.uk> |
* Copyright 2005 Adrian Lees <adrianl@users.sourceforge.net> |
* Copyright 2009 Mark Benjamin <netsurf-browser.org.MarkBenjamin@dfgh.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Free text search (core) |
*/ |
#include "utils/config.h" |
#include <ctype.h> |
#include <string.h> |
#include <dom/dom.h> |
#include "content/content.h" |
#include "content/hlcache.h" |
#include "desktop/gui.h" |
#include "desktop/selection.h" |
#include "render/box.h" |
#include "render/html.h" |
#include "render/html_internal.h" |
#include "render/search.h" |
#include "render/textplain.h" |
#include "utils/config.h" |
#include "utils/log.h" |
#include "utils/messages.h" |
#include "utils/url.h" |
#include "utils/utils.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 { |
struct search_callbacks callbacks; |
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; |
}; |
/** |
* create a search_context |
* \param h the hlcache_handle the search_context is connected to |
* \param callbacks the callbacks to modify appearance according to results |
* \param p the pointer to send to the callbacks |
* \return true for success |
*/ |
struct search_context * search_create_context(hlcache_handle *h, |
struct search_callbacks callbacks) |
{ |
struct search_context *context; |
struct list_entry *search_head; |
struct content *c = hlcache_handle_get_content(h); |
if (h == NULL) |
return NULL; |
if (content_get_type(h) != CONTENT_HTML && |
content_get_type(h) != CONTENT_TEXTPLAIN) { |
return NULL; |
} |
context = malloc(sizeof(struct search_context)); |
if (context == NULL) { |
warn_user("NoMemory", 0); |
return NULL; |
} |
search_head = malloc(sizeof(struct list_entry)); |
if (search_head == NULL) { |
warn_user("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 = (content_get_type(h) == CONTENT_HTML) ? true : false; |
context->callbacks = callbacks; |
if (context->is_html) { |
html_set_search(context->c, context); |
} else { |
textplain_set_search(context->c, context); |
} |
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 |
* \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) { |
warn_user("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 |
* \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 wheteher to perform a case sensitive search |
* \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 |
*/ |
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; |
} |
/* LOG(("do_search '%s' - '%s' (%p, %p) %p (%d, %d) %d", |
search_data.string, string, search_data.content, c, search_data.found->next, |
search_data.prev_case_sens, case_sens, forwards)); */ |
/* 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'; |
} |
if ((context->callbacks.gui != NULL) && |
(context->callbacks.gui->hourglass != NULL)) |
context->callbacks.gui->hourglass(true, |
context->callbacks.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); |
if ((context->callbacks.gui != NULL) && |
(context->callbacks.gui->hourglass != |
NULL)) |
context->callbacks.gui->hourglass(false, |
context->callbacks.gui_p); |
return; |
} |
if ((context->callbacks.gui != NULL) && |
(context->callbacks.gui->hourglass != NULL)) |
context->callbacks.gui->hourglass(false, |
context->callbacks.gui_p); |
context->prev_case_sens = case_sensitive; |
/* LOG(("%d %p %p (%p, %p)", new, search_data.found->next, search_data.current, |
search_data.current->prev, search_data.current->next)); */ |
/* 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; |
} |
} |
if (context->callbacks.gui == NULL) |
return; |
if (context->callbacks.gui->status != NULL) |
context->callbacks.gui->status((context->current != NULL), |
context->callbacks.gui_p); |
search_show_all(showall, context); |
if (context->callbacks.gui->back_state != NULL) |
context->callbacks.gui->back_state((context->current != NULL) && |
(context->current->prev != NULL), |
context->callbacks.gui_p); |
if (context->callbacks.gui->forward_state != NULL) |
context->callbacks.gui->forward_state( |
(context->current != NULL) && |
(context->current->next != NULL), |
context->callbacks.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); |
} |
/** |
* Begins/continues the search process |
* Note that this may be called many times for a single search. |
* |
* \param bw the browser_window to search in |
* \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) |
{ |
int string_len; |
int i = 0; |
if ((context == NULL) || (context->callbacks.gui == NULL)) { |
warn_user("SearchError", 0); |
return; |
} |
if (context->callbacks.gui->add_recent != NULL) |
context->callbacks.gui->add_recent(string, |
context->callbacks.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); |
if (context->callbacks.gui->status != NULL) |
context->callbacks.gui->status(true, |
context->callbacks.gui_p); |
if (context->callbacks.gui->back_state != NULL) |
context->callbacks.gui->back_state(false, |
context->callbacks.gui_p); |
if (context->callbacks.gui->forward_state != NULL) |
context->callbacks.gui->forward_state(false, |
context->callbacks.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); |
} |
/** |
* Determines whether any portion of the given text box should be |
* selected because it matches the current search string. |
* |
* \param bw browser window |
* \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 |
* \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) |
{ |
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; |
} |
/** |
* 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) |
{ |
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); |
} else { |
a->sel = selection_create(context->c, false); |
if (!a->sel) |
continue; |
selection_init(a->sel, NULL); |
} |
selection_set_start(a->sel, a->start_idx); |
selection_set_end(a->sel, a->end_idx); |
} |
} |
} |
/** |
* Ends the search process, invalidating all state |
* freeing the list of found boxes |
*/ |
void search_destroy_context(struct search_context *context) |
{ |
assert(context != NULL); |
if (context->callbacks.invalidate != NULL) { |
context->callbacks.invalidate(context, context->callbacks.p); |
} |
if (context->c != NULL) { |
if (context->is_html) |
html_set_search(context->c, NULL); |
else |
textplain_set_search(context->c, NULL); |
} |
if ((context->string != NULL) && (context->callbacks.gui != NULL) && |
(context->callbacks.gui->add_recent != NULL)) { |
context->callbacks.gui->add_recent(context->string, |
context->callbacks.gui_p); |
free(context->string); |
} |
free_matches(context); |
free(context); |
} |
/contrib/network/netsurf/netsurf/render/search.h |
---|
0,0 → 1,58 |
/* |
* Copyright 2009 Mark Benjamin <netsurf-browser.org.MarkBenjamin@dfgh.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
#ifndef _NETSURF_RENDER_SEARCH_H_ |
#define _NETSURF_RENDER_SEARCH_H_ |
#include <ctype.h> |
#include <string.h> |
#include "desktop/search.h" |
struct search_context; |
/** |
* Called when a search context is destroyed |
* \param context search context being invalidated |
* \param p pointer for client data |
*/ |
typedef void (*search_invalidate_callback)(struct search_context *context, |
void *p); |
struct search_callbacks { |
struct gui_search_callbacks *gui; |
void *gui_p; /* private gui owned data */ |
search_invalidate_callback invalidate; |
void *p; /* private client data */ |
}; |
struct search_context * search_create_context(struct hlcache_handle *h, |
struct search_callbacks callbacks); |
void search_destroy_context(struct search_context *context); |
void search_step(struct search_context *context, search_flags_t flags, |
const char * string); |
void search_show_all(bool all, struct search_context *context); |
bool search_term_highlighted(struct content *c, |
unsigned start_offset, unsigned end_offset, |
unsigned *start_idx, unsigned *end_idx, |
struct search_context *context); |
#endif |
/contrib/network/netsurf/netsurf/render/table.c |
---|
0,0 → 1,999 |
/* |
* Copyright 2005 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2005 Richard Wilson <info@tinct.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Table processing and layout (implementation). |
*/ |
#include <assert.h> |
#include <dom/dom.h> |
#include "css/css.h" |
#include "css/utils.h" |
#include "render/box.h" |
#include "render/table.h" |
#include "utils/log.h" |
#include "utils/talloc.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(struct box *cell); |
static void table_used_top_border_for_cell(struct box *cell); |
static void table_used_right_border_for_cell(struct box *cell); |
static void table_used_bottom_border_for_cell(struct box *cell); |
static bool table_border_is_more_eyecatching(const struct border *a, |
box_type a_src, const struct border *b, box_type b_src); |
static void table_cell_top_process_table(struct box *table, struct border *a, |
box_type *a_src); |
static bool table_cell_top_process_group(struct box *cell, struct box *group, |
struct border *a, box_type *a_src); |
static bool table_cell_top_process_row(struct box *cell, struct box *row, |
struct border *a, box_type *a_src); |
/** |
* Determine the column width types for a table. |
* |
* \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(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(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(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++) |
LOG(("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 cell Table cell to consider |
* |
* \post \a cell's border array is populated |
*/ |
void table_used_border_for_cell(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(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(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(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(width, unit, cell->style)); |
} else { |
/* Left border */ |
table_used_left_border_for_cell(cell); |
/* Top border */ |
table_used_top_border_for_cell(cell); |
/* Right border */ |
table_used_right_border_for_cell(cell); |
/* Bottom border */ |
table_used_bottom_border_for_cell(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 cell Table cell to consider |
*/ |
void table_used_left_border_for_cell(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(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 */ |
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(b.width, b.unit, prev->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_CELL; |
if (table_border_is_more_eyecatching(&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(b.width, b.unit, row->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW; |
if (table_border_is_more_eyecatching(&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(b.width, b.unit, group->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW_GROUP; |
if (table_border_is_more_eyecatching(&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(b.width, b.unit, table->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE; |
if (table_border_is_more_eyecatching(&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(a.width, a.unit, cell->style)); |
} |
/** |
* Calculate used values of border-top-{style,color,width} |
* |
* \param cell Table cell to consider |
*/ |
void table_used_top_border_for_cell(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(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(b.width, b.unit, row->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW; |
if (table_border_is_more_eyecatching(&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(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(b.width, b.unit, group->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW_GROUP; |
if (table_border_is_more_eyecatching(&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(group->parent, &a, &a_src); |
} else { |
/* Process previous group(s) */ |
while (table_cell_top_process_group(cell, group->prev, |
&a, &a_src) == false) { |
if (group->prev->prev == NULL) { |
/* Top border of table */ |
table_cell_top_process_table( |
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(a.width, a.unit, cell->style)); |
} |
/** |
* Calculate used values of border-right-{style,color,width} |
* |
* \param cell Table cell to consider |
*/ |
void table_used_right_border_for_cell(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(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(b.width, b.unit, row->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW; |
if (table_border_is_more_eyecatching(&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(b.width, b.unit, group->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW_GROUP; |
if (table_border_is_more_eyecatching(&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(b.width, b.unit, table->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE; |
if (table_border_is_more_eyecatching(&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(a.width, a.unit, cell->style)); |
} |
/** |
* Calculate used values of border-bottom-{style,color,width} |
* |
* \param cell Table cell to consider |
*/ |
void table_used_bottom_border_for_cell(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(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(b.width, b.unit, row->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW; |
if (table_border_is_more_eyecatching(&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(b.width, b.unit, group->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW_GROUP; |
if (table_border_is_more_eyecatching(&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(b.width, b.unit, table->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE; |
if (table_border_is_more_eyecatching(&a, a_src, &b, b_src)) { |
a = b; |
a_src = b_src; |
} |
} |
/* 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(a.width, a.unit, cell->style)); |
} |
/** |
* Determine if a border style is more eyecatching than another |
* |
* \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 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(a->width, a->unit, NULL); |
bwidth = nscss_len2px(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++; |
case CSS_BORDER_STYLE_SOLID: impact++; |
case CSS_BORDER_STYLE_DASHED: impact++; |
case CSS_BORDER_STYLE_DOTTED: impact++; |
case CSS_BORDER_STYLE_RIDGE: impact++; |
case CSS_BORDER_STYLE_OUTSET: impact++; |
case CSS_BORDER_STYLE_GROOVE: impact++; |
case CSS_BORDER_STYLE_INSET: impact++; |
default: |
break; |
} |
switch (b->style) { |
case CSS_BORDER_STYLE_DOUBLE: impact--; |
case CSS_BORDER_STYLE_SOLID: impact--; |
case CSS_BORDER_STYLE_DASHED: impact--; |
case CSS_BORDER_STYLE_DOTTED: impact--; |
case CSS_BORDER_STYLE_RIDGE: impact--; |
case CSS_BORDER_STYLE_OUTSET: impact--; |
case CSS_BORDER_STYLE_GROOVE: impact--; |
case CSS_BORDER_STYLE_INSET: impact--; |
default: |
break; |
} |
if (impact < 0) |
return true; |
else if (impact > 0) |
return false; |
/* 4a -- sort by origin */ |
impact = 0; |
switch (a_src) { |
case BOX_TABLE_CELL: impact++; |
case BOX_TABLE_ROW: impact++; |
case BOX_TABLE_ROW_GROUP: impact++; |
/** \todo COL/COL_GROUP */ |
case BOX_TABLE: impact++; |
default: |
break; |
} |
switch (b_src) { |
case BOX_TABLE_CELL: impact--; |
case BOX_TABLE_ROW: impact--; |
case BOX_TABLE_ROW_GROUP: impact--; |
/** \todo COL/COL_GROUP */ |
case BOX_TABLE: impact--; |
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 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(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(b.width, b.unit, table->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE; |
if (table_border_is_more_eyecatching(a, *a_src, &b, b_src)) { |
*a = b; |
*a_src = b_src; |
} |
} |
/** |
* Process a group |
* |
* \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(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(b.width, b.unit, group->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW_GROUP; |
if (table_border_is_more_eyecatching(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(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(b.width, b.unit, group->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW_GROUP; |
if (table_border_is_more_eyecatching(a, *a_src, &b, b_src)) { |
*a = b; |
*a_src = b_src; |
} |
return false; |
} |
return true; |
} |
/** |
* Process a row |
* |
* \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(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(b.width, b.unit, row->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW; |
if (table_border_is_more_eyecatching(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(b.width, b.unit, row->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_ROW; |
if (table_border_is_more_eyecatching(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 < |
cell->start_column) |
continue; |
/* Ignore cells to the right */ |
if (c->start_column >= cell->start_column + |
cell->columns) |
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(b.width, b.unit, |
c->style); |
b.unit = CSS_UNIT_PX; |
b_src = BOX_TABLE_CELL; |
if (table_border_is_more_eyecatching(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; |
} |
/contrib/network/netsurf/netsurf/render/table.h |
---|
0,0 → 1,34 |
/* |
* Copyright 2005 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2005 Richard Wilson <info@tinct.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Table processing and layout (interface). |
*/ |
#ifndef _NETSURF_RENDER_TABLE_H_ |
#define _NETSURF_RENDER_TABLE_H_ |
#include <stdbool.h> |
struct box; |
bool table_calculate_column_types(struct box *table); |
void table_used_border_for_cell(struct box *cell); |
#endif |
/contrib/network/netsurf/netsurf/render/textinput.c |
---|
0,0 → 1,2213 |
/* |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2004 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk> |
* Copyright 2004 John Tytgat <joty@netsurf-browser.org> |
* Copyright 2005 Adrian Lees <adrianl@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* HTML form text input handling (implementation) |
*/ |
#include <assert.h> |
#include <ctype.h> |
#include <string.h> |
#include <dom/dom.h> |
#include "desktop/browser.h" |
#include "desktop/gui.h" |
#include "desktop/mouse.h" |
#include "desktop/scrollbar.h" |
#include "desktop/selection.h" |
#include "desktop/textinput.h" |
#include "render/box.h" |
#include "render/font.h" |
#include "render/form.h" |
#include "render/html_internal.h" |
#include "render/layout.h" |
#include "render/textinput.h" |
#include "utils/log.h" |
#include "utils/talloc.h" |
#include "utils/utf8.h" |
#include "utils/utils.h" |
/* Define to enable textinput debug */ |
#undef TEXTINPUT_DEBUG |
static bool textinput_textbox_delete(struct content *c, |
struct box *text_box, unsigned char_offset, |
unsigned utf8_len); |
/* Textarea callbacks */ |
static bool textinput_textarea_callback(struct browser_window *bw, |
uint32_t key, void *p1, void *p2); |
static void textinput_textarea_move_caret(struct browser_window *bw, |
void *p1, void *p2); |
static bool textinput_textarea_paste_text(struct browser_window *bw, |
const char *utf8, unsigned utf8_len, bool last, |
void *p1, void *p2); |
/* Text input callbacks */ |
static bool textinput_input_callback(struct browser_window *bw, |
uint32_t key, void *p1, void *p2); |
static void textinput_input_move_caret(struct browser_window *bw, |
void *p1, void *p2); |
static bool textinput_input_paste_text(struct browser_window *bw, |
const char *utf8, unsigned utf8_len, bool last, |
void *p1, void *p2); |
#define SPACE_LEN(b) ((b->space == 0) ? 0 : 1) |
static struct textinput_buffer { |
char *buffer; |
size_t buffer_len; |
size_t length; |
} textinput_buffer; |
/** |
* Given the x,y co-ordinates of a point within a textarea, return the |
* TEXT box pointer, and the character and pixel offsets within that |
* box at which the caret should be positioned. (eg. for mouse clicks, |
* drag-and-drop insertions etc) |
* |
* \param textarea the textarea being considered |
* \param x x ordinate of point |
* \param y y ordinate of point |
* \param pchar_offset receives the char offset within the TEXT box |
* \param ppixel_offset receives the pixel offset within the TEXT box |
* \return pointer to TEXT box |
*/ |
static struct box *textinput_textarea_get_position(struct box *textarea, |
int x, int y, int *pchar_offset, int *ppixel_offset) |
{ |
/* A textarea is an INLINE_BLOCK containing a single |
* INLINE_CONTAINER, which contains the text as runs of TEXT |
* separated by BR. There is at least one TEXT. The first and |
* last boxes are TEXT. Consecutive BR may not be present. These |
* constraints are satisfied by using a 0-length TEXT for blank |
* lines. */ |
struct box *inline_container, *text_box; |
plot_font_style_t fstyle; |
size_t char_offset = 0; |
inline_container = textarea->children; |
if (inline_container->y + inline_container->height < y) { |
/* below the bottom of the textarea: place caret at end */ |
text_box = inline_container->last; |
assert(text_box->type == BOX_TEXT); |
assert(text_box->text); |
font_plot_style_from_css(text_box->style, &fstyle); |
/** \todo handle errors */ |
nsfont.font_position_in_string(&fstyle, text_box->text, |
text_box->length, |
(unsigned int)(x - text_box->x), |
&char_offset, ppixel_offset); |
} else { |
/* find the relevant text box */ |
y -= inline_container->y; |
for (text_box = inline_container->children; |
text_box && |
text_box->y + text_box->height < y; |
text_box = text_box->next) |
; |
for (; text_box && text_box->type != BOX_BR && |
text_box->y <= y && |
text_box->x + text_box->width < x; |
text_box = text_box->next) |
; |
if (!text_box) { |
/* past last text box */ |
text_box = inline_container->last; |
assert(text_box->type == BOX_TEXT); |
assert(text_box->text); |
font_plot_style_from_css(text_box->style, &fstyle); |
nsfont.font_position_in_string(&fstyle, |
text_box->text, |
text_box->length, |
textarea->width, |
&char_offset, |
ppixel_offset); |
} else { |
/* in a text box */ |
if (text_box->type == BOX_BR) |
text_box = text_box->prev; |
else if (y < text_box->y && text_box->prev) { |
if (text_box->prev->type == BOX_BR) { |
assert(text_box->prev->prev); |
text_box = text_box->prev->prev; |
} |
else |
text_box = text_box->prev; |
} |
assert(text_box->type == BOX_TEXT); |
assert(text_box->text); |
font_plot_style_from_css(text_box->style, &fstyle); |
nsfont.font_position_in_string(&fstyle, |
text_box->text, |
text_box->length, |
(unsigned int)(x - text_box->x), |
&char_offset, |
ppixel_offset); |
} |
} |
*pchar_offset = char_offset; |
assert(text_box); |
return text_box; |
} |
/** |
* Delete some text from a box, or delete the box in its entirety |
* |
* \param c html content |
* \param b box |
* \param offset start offset of text to be deleted (in bytes) |
* \param length length of text to be deleted |
* \return true iff successful |
*/ |
static bool textinput_delete_handler(struct content *c, struct box *b, |
int offset, size_t length) |
{ |
size_t text_length = b->length + SPACE_LEN(b); |
/* only remove if its not the first box */ |
if (offset <= 0 && length >= text_length && b->prev != NULL) { |
/* remove the entire box */ |
box_unlink_and_free(b); |
return true; |
} else |
return textinput_textbox_delete(c, b, offset, |
min(length, text_length - offset)); |
} |
/** |
* Remove the selected text from a text box and gadget (if applicable) |
* |
* \param c The content containing the selection |
* \param s The selection to be removed |
*/ |
static void textinput_delete_selection(struct content *c, struct selection *s) |
{ |
size_t start_offset, end_offset; |
struct box *text_box; |
struct box *end_box; |
struct box *next; |
size_t sel_len; |
int beginning = 0; |
assert(s->defined); |
text_box = selection_get_start(s, &start_offset); |
end_box = selection_get_end(s, &end_offset); |
sel_len = s->end_idx - s->start_idx; |
/* Clear selection so that deletion from textboxes proceeds */ |
selection_clear(s, true); |
/* handle first box */ |
textinput_delete_handler(c, text_box, start_offset, sel_len); |
if (text_box == end_box) |
return; |
for (text_box = text_box->next; text_box != end_box; text_box = next) { |
next = text_box->next; |
box_unlink_and_free(text_box); |
} |
textinput_delete_handler(c, end_box, beginning, end_offset); |
} |
/** |
* Insert a number of chars into a text box |
* |
* \param c html_content |
* \param text_box text box |
* \param char_offset offset (bytes) at which to insert text |
* \param utf8 UTF-8 text to insert |
* \param utf8_len length (bytes) of UTF-8 text to insert |
* \return true iff successful |
*/ |
static bool textinput_textbox_insert(struct content *c, |
struct box *text_box, unsigned char_offset, const char *utf8, |
unsigned utf8_len) |
{ |
html_content *html = (html_content *)c; |
char *text; |
struct box *input = text_box->parent->parent; |
bool hide; |
if (html->bw && html->sel.defined) |
textinput_delete_selection(c, &html->sel); |
/* insert into form gadget (text and password inputs only) */ |
if (input->gadget && (input->gadget->type == GADGET_TEXTBOX || |
input->gadget->type == GADGET_PASSWORD) && |
input->gadget->value) { |
size_t form_offset = input->gadget->caret_form_offset; |
char *value = realloc(input->gadget->value, |
input->gadget->length + utf8_len + 1); |
if (!value) { |
warn_user("NoMemory", 0); |
return true; |
} |
input->gadget->value = value; |
memmove(input->gadget->value + form_offset + utf8_len, |
input->gadget->value + form_offset, |
input->gadget->length - form_offset); |
memcpy(input->gadget->value + form_offset, utf8, utf8_len); |
input->gadget->length += utf8_len; |
input->gadget->value[input->gadget->length] = 0; |
} |
hide = (input->gadget && input->gadget->type == GADGET_PASSWORD); |
if (hide) { |
/* determine the number of '*'s to be inserted */ |
const char *eutf8 = utf8 + utf8_len; |
utf8_len = 0; |
while (utf8 < eutf8) { |
utf8 += utf8_next(utf8, eutf8 - utf8, 0); |
utf8_len++; |
} |
} |
/* insert in text box */ |
text = talloc_realloc(html->bctx, text_box->text, |
char, |
text_box->length + SPACE_LEN(text_box) + utf8_len + 1); |
if (!text) { |
warn_user("NoMemory", 0); |
return false; |
} |
text_box->text = text; |
if (text_box->space != 0 && |
char_offset == text_box->length + SPACE_LEN(text_box)) { |
if (hide) |
text_box->space = 0; |
else { |
unsigned int last_off = utf8_prev(utf8, utf8_len); |
if (utf8[last_off] != ' ') |
text_box->space = 0; |
else |
utf8_len = last_off; |
} |
text_box->text[text_box->length++] = ' '; |
} else { |
memmove(text_box->text + char_offset + utf8_len, |
text_box->text + char_offset, |
text_box->length - char_offset); |
} |
if (hide) |
memset(text_box->text + char_offset, '*', utf8_len); |
else |
memcpy(text_box->text + char_offset, utf8, utf8_len); |
text_box->length += utf8_len; |
/* nothing should assume that the text is terminated, |
* but just in case */ |
text_box->text[text_box->length] = 0; |
text_box->width = UNKNOWN_WIDTH; |
return true; |
} |
/** |
* Calculates the form_offset from the box_offset |
* |
* \param input The root box containing both the textbox and gadget |
* \param text_box The textbox containing the caret |
* \param char_offset The caret offset within text_box |
* \return the translated form_offset |
*/ |
static size_t textinput_get_form_offset(struct box* input, struct box* text_box, |
size_t char_offset) |
{ |
int uchars; |
unsigned int offset; |
for (uchars = 0, offset = 0; offset < char_offset; uchars++) { |
if ((text_box->text[offset] & 0x80) == 0x00) { |
offset++; |
continue; |
} |
assert((text_box->text[offset] & 0xC0) == 0xC0); |
for (++offset; offset < char_offset && |
(text_box->text[offset] & 0xC0) == 0x80; |
offset++) |
/* do nothing */; |
} |
/* uchars is the number of real Unicode characters at the left |
* side of the caret. |
*/ |
for (offset = 0; uchars > 0 && offset < input->gadget->length; |
uchars--) { |
if ((input->gadget->value[offset] & 0x80) == 0x00) { |
offset++; |
continue; |
} |
assert((input->gadget->value[offset] & 0xC0) == 0xC0); |
for (++offset; offset < input->gadget->length && |
(input->gadget->value[offset] & 0xC0) == 0x80; |
offset++) |
/* do nothing */; |
} |
assert(uchars == 0); |
return offset; |
} |
/** |
* Delete a number of chars from a text box |
* |
* \param c html content |
* \param text_box text box |
* \param char_offset offset within text box (bytes) of first char to delete |
* \param utf8_len length (bytes) of chars to be deleted |
* \return true on success, false otherwise |
* |
* ::char_offset and ::utf8_len are only considered when there is no selection. |
* If there is a selection, the entire selected area is deleted. |
*/ |
bool textinput_textbox_delete(struct content *c, struct box *text_box, |
unsigned char_offset, unsigned utf8_len) |
{ |
html_content *html = (html_content *)c; |
unsigned next_offset = char_offset + utf8_len; |
struct box *form = text_box->parent->parent; |
if (html->bw && html->sel.defined) { |
textinput_delete_selection(c, &html->sel); |
return true; |
} |
/* delete from form gadget (text and password inputs only) */ |
if (form->gadget && (form->gadget->type == GADGET_TEXTBOX || |
form->gadget->type == GADGET_PASSWORD) && |
form->gadget->value) { |
size_t form_offset = textinput_get_form_offset(form, text_box, |
char_offset); |
size_t next_offset = textinput_get_form_offset(form, text_box, |
char_offset + utf8_len); |
memmove(form->gadget->value + form_offset, |
form->gadget->value + next_offset, |
form->gadget->length - next_offset); |
form->gadget->length -= (next_offset - form_offset); |
form->gadget->value[form->gadget->length] = 0; |
} |
/* delete from visible textbox */ |
if (next_offset <= text_box->length + SPACE_LEN(text_box)) { |
/* handle removal of trailing space */ |
if (text_box->space != 0 && next_offset > text_box->length) { |
if (char_offset > 0) { |
/* is the trailing character still a space? */ |
int tmp = utf8_prev(text_box->text, char_offset); |
if (isspace(text_box->text[tmp])) |
char_offset = tmp; |
else |
text_box->space = 0; |
} else { |
text_box->space = 0; |
} |
text_box->length = char_offset; |
} else { |
memmove(text_box->text + char_offset, |
text_box->text + next_offset, |
text_box->length - next_offset); |
text_box->length -= utf8_len; |
} |
/* nothing should assume that the text is terminated, |
* but just in case */ |
text_box->text[text_box->length] = 0; |
text_box->width = UNKNOWN_WIDTH; |
return true; |
} |
return false; |
} |
/** |
* Locate the first inline box at the start of this line |
* |
* \param text_box text box from which to start searching |
*/ |
static struct box *textinput_line_start(struct box *text_box) |
{ |
while (text_box->prev && text_box->prev->type == BOX_TEXT) |
text_box = text_box->prev; |
return text_box; |
} |
/** |
* Locate the last inline box in this line |
* |
* \param text_box text box from which to start searching |
*/ |
static struct box *textinput_line_end(struct box *text_box) |
{ |
while (text_box->next && text_box->next->type == BOX_TEXT) |
text_box = text_box->next; |
return text_box; |
} |
/** |
* Backtrack to the start of the previous line, if there is one. |
*/ |
static struct box *textinput_line_above(struct box *text_box) |
{ |
struct box *prev; |
text_box = textinput_line_start(text_box); |
prev = text_box->prev; |
while (prev && prev->type == BOX_BR) |
prev = prev->prev; |
return prev ? textinput_line_start(prev) : text_box; |
} |
/** |
* Advance to the start of the next line, if there is one. |
*/ |
static struct box *textinput_line_below(struct box *text_box) |
{ |
struct box *next; |
text_box = textinput_line_end(text_box); |
next = text_box->next; |
while (next && next->type == BOX_BR) |
next = next->next; |
return next ? next : text_box; |
} |
/** |
* Add some text to the buffer, optionally appending a trailing space. |
* |
* \param text text to be added |
* \param length length of text in bytes |
* \param space indicates whether a trailing space should be appended |
* \param fstyle The font style |
* \return true if successful |
*/ |
static bool textinput_add_to_buffer(const char *text, size_t length, bool space, |
const plot_font_style_t *fstyle) |
{ |
size_t new_length = textinput_buffer.length + length + (space ? 1 : 0) + 1; |
if (new_length > textinput_buffer.buffer_len) { |
size_t new_alloc = new_length + (new_length / 4); |
char *new_buff; |
new_buff = realloc(textinput_buffer.buffer, new_alloc); |
if (new_buff == NULL) |
return false; |
textinput_buffer.buffer = new_buff; |
textinput_buffer.buffer_len = new_alloc; |
} |
memcpy(textinput_buffer.buffer + textinput_buffer.length, text, length); |
textinput_buffer.length += length; |
if (space) |
textinput_buffer.buffer[textinput_buffer.length++] = ' '; |
textinput_buffer.buffer[textinput_buffer.length] = '\0'; |
return true; |
} |
/** |
* Empty the buffer, called prior to textinput_add_to_buffer sequence |
* |
* \return true iff successful |
*/ |
static bool textinput_empty_buffer(void) |
{ |
const size_t init_size = 1024; |
if (textinput_buffer.buffer_len == 0) { |
textinput_buffer.buffer = malloc(init_size); |
if (textinput_buffer.buffer == NULL) |
return false; |
textinput_buffer.buffer_len = init_size; |
} |
textinput_buffer.length = 0; |
return true; |
} |
/** |
* Cut a range of text from a text box, |
* possibly placing it on the global clipboard. |
* |
* \param c html content |
* \param start_box text box at start of range |
* \param start_idx index (bytes) within start box |
* \param end_box text box at end of range |
* \param end_idx index (bytes) within end box |
* \param clipboard whether to place text on the clipboard |
* \return true iff successful |
*/ |
static bool textinput_textarea_cut(struct content *c, |
struct box *start_box, unsigned start_idx, |
struct box *end_box, unsigned end_idx, |
bool clipboard) |
{ |
struct box *box = start_box; |
bool success = true; |
bool del = false; /* caller expects start_box to persist */ |
if (textinput_empty_buffer() == false) { |
return false; |
} |
while (box && box != end_box) { |
/* read before deletion, in case the whole box goes */ |
struct box *next = box->next; |
if (box->type == BOX_BR) { |
if (clipboard && !textinput_add_to_buffer("\n", 1, |
false, plot_style_font)) { |
return false; |
} |
box_unlink_and_free(box); |
} else { |
/* append box text to clipboard and then delete it */ |
if (clipboard && |
!textinput_add_to_buffer(box->text + start_idx, |
box->length - start_idx, |
SPACE_LEN(box), plot_style_font)) { |
return false; |
} |
if (del) { |
if (!textinput_delete_handler(c, box, |
start_idx, |
(box->length + SPACE_LEN(box)) - |
start_idx) && clipboard) { |
return false; |
} |
} else { |
textinput_textbox_delete(c, box, start_idx, |
(box->length + SPACE_LEN(box)) - |
start_idx); |
} |
} |
del = true; |
start_idx = 0; |
box = next; |
} |
/* and the last box */ |
if (box) { |
if (clipboard && !textinput_add_to_buffer(box->text + start_idx, |
end_idx - start_idx, end_idx > box->length, |
plot_style_font)) { |
success = false; |
} else { |
if (del) { |
if (!textinput_delete_handler(c, box, |
start_idx, end_idx - start_idx)) |
success = false; |
} else { |
textinput_textbox_delete(c, box, start_idx, |
end_idx - start_idx); |
} |
} |
} |
if (clipboard) { |
gui_set_clipboard(textinput_buffer.buffer, |
textinput_buffer.length, NULL, 0); |
} |
return true; |
} |
/** |
* Break a text box into two |
* |
* \param c html content |
* \param text_box text box to be split |
* \param char_offset offset (in bytes) at which text box is to be split |
*/ |
static struct box *textinput_textarea_insert_break(struct content *c, |
struct box *text_box, size_t char_offset) |
{ |
html_content *html = (html_content *)c; |
struct box *new_br, *new_text; |
char *text; |
text = talloc_array(html->bctx, char, text_box->length + 1); |
if (!text) { |
warn_user("NoMemory", 0); |
return NULL; |
} |
new_br = box_create(NULL, text_box->style, false, 0, 0, text_box->title, |
0, html->bctx); |
new_text = talloc(html->bctx, struct box); |
if (!new_text) { |
warn_user("NoMemory", 0); |
return NULL; |
} |
new_br->type = BOX_BR; |
box_insert_sibling(text_box, new_br); |
memcpy(new_text, text_box, sizeof (struct box)); |
new_text->flags |= CLONE; |
new_text->text = text; |
memcpy(new_text->text, text_box->text + char_offset, |
text_box->length - char_offset); |
new_text->length = text_box->length - char_offset; |
text_box->length = char_offset; |
text_box->width = new_text->width = UNKNOWN_WIDTH; |
box_insert_sibling(new_br, new_text); |
return new_text; |
} |
/** |
* Reflow textarea preserving width and height |
* |
* \param c html content |
* \param textarea text area box |
* \param inline_container container holding text box |
*/ |
static void textinput_textarea_reflow(struct content *c, |
struct box *textarea, struct box *inline_container) |
{ |
int width = textarea->width; |
int height = textarea->height; |
assert(c != NULL); |
if (!layout_inline_container(inline_container, width, |
textarea, 0, 0, (struct html_content *) c)) |
warn_user("NoMemory", 0); |
textarea->width = width; |
textarea->height = height; |
layout_calculate_descendant_bboxes(textarea); |
box_handle_scrollbars(c, textarea, |
box_hscrollbar_present(textarea), |
box_vscrollbar_present(textarea)); |
} |
/** |
* Move to the start of the word containing the given character position, |
* or the start of the preceding word if already at the start of this one. |
* |
* \param text UTF-8 text string |
* \param poffset offset of caret within string (updated on exit) |
* \param pchars receives the number of characters skipped |
* \return true iff the start of a word was found before/at the string start |
*/ |
static bool textinput_word_left(const char *text, |
size_t *poffset, size_t *pchars) |
{ |
size_t offset = *poffset; |
bool success = false; |
size_t nchars = 0; |
/* Skip any spaces immediately prior to the offset */ |
while (offset > 0) { |
offset = utf8_prev(text, offset); |
nchars++; |
if (!isspace(text[offset])) break; |
} |
/* Now skip all non-space characters */ |
while (offset > 0) { |
size_t prev = utf8_prev(text, offset); |
success = true; |
if (isspace(text[prev])) |
break; |
offset = prev; |
nchars++; |
} |
*poffset = offset; |
if (pchars) *pchars = nchars; |
return success; |
} |
/** |
* Move to the start of the first word following the given character position. |
* |
* \param text UTF-8 text string |
* \param len length of string in bytes |
* \param poffset offset of caret within string (updated on exit) |
* \param pchars receives the number of characters skipped |
* \return true iff the start of a word was found before the string end |
*/ |
static bool textinput_word_right(const char *text, size_t len, |
size_t *poffset, size_t *pchars) |
{ |
size_t offset = *poffset; |
bool success = false; |
size_t nchars = 0; |
/* Skip all non-space characters after the offset */ |
while (offset < len) { |
if (isspace(text[offset])) break; |
offset = utf8_next(text, len, offset); |
nchars++; |
} |
/* Now skip all space characters */ |
while (offset < len) { |
offset = utf8_next(text, len, offset); |
nchars++; |
if (offset < len && !isspace(text[offset])) { |
success = true; |
break; |
} |
} |
*poffset = offset; |
if (pchars) *pchars = nchars; |
return success; |
} |
/** |
* Adjust scroll offsets so that the caret is visible |
* |
* \param c html content where click ocurred |
* \param textarea textarea box |
* \return true if a change in scroll offsets has occurred |
*/ |
static bool textinput_ensure_caret_visible(struct content *c, |
struct box *textarea) |
{ |
html_content *html = (html_content *)c; |
int cx, cy; |
int scrollx, scrolly; |
assert(textarea->gadget); |
scrollx = scrollbar_get_offset(textarea->scroll_x); |
scrolly = scrollbar_get_offset(textarea->scroll_y); |
/* Calculate the caret coordinates */ |
cx = textarea->gadget->caret_pixel_offset + |
textarea->gadget->caret_text_box->x; |
cy = textarea->gadget->caret_text_box->y; |
/* Ensure they are visible */ |
if (textarea->scroll_x == NULL) { |
scrollx = 0; |
} else if (cx - scrollbar_get_offset(textarea->scroll_x) < 0) { |
scrollx = cx; |
} else if (cx > scrollbar_get_offset(textarea->scroll_x) + |
textarea->width) { |
scrollx = cx - textarea->width; |
} |
if (textarea->scroll_y == NULL) { |
scrolly = 0; |
} else if (cy - scrollbar_get_offset(textarea->scroll_y) < 0) { |
scrolly = cy; |
} else if (cy + textarea->gadget->caret_text_box->height > |
scrollbar_get_offset(textarea->scroll_y) + |
textarea->height) { |
scrolly = (cy + textarea->gadget->caret_text_box->height) - |
textarea->height; |
} |
if ((scrollx == scrollbar_get_offset(textarea->scroll_x)) && |
(scrolly == scrollbar_get_offset(textarea->scroll_y))) |
return false; |
if (textarea->scroll_x != NULL) { |
html->scrollbar = textarea->scroll_x; |
scrollbar_set(textarea->scroll_x, scrollx, false); |
html->scrollbar = NULL; |
} |
if (textarea->scroll_y != NULL) { |
html->scrollbar = textarea->scroll_x; |
scrollbar_set(textarea->scroll_y, scrolly, false); |
html->scrollbar = NULL; |
} |
return true; |
} |
/** |
* Paste a block of text into a textarea at the |
* current caret position. |
* |
* \param bw browser window |
* \param utf8 pointer to block of text |
* \param utf8_len length (bytes) of text block |
* \param last true iff this is the last chunk (update screen too) |
* \param p1 pointer to textarea |
* \param p2 html content with the text area box |
* \return true iff successful |
*/ |
bool textinput_textarea_paste_text(struct browser_window *bw, |
const char *utf8, unsigned utf8_len, bool last, |
void *p1, void *p2) |
{ |
struct box *textarea = p1; |
struct content *c = p2; |
struct box *inline_container = |
textarea->gadget->caret_inline_container; |
struct box *text_box = textarea->gadget->caret_text_box; |
size_t char_offset = textarea->gadget->caret_box_offset; |
int pixel_offset = textarea->gadget->caret_pixel_offset; |
const char *ep = utf8 + utf8_len; |
const char *p = utf8; |
bool success = true; |
bool update = last; |
while (p < ep) { |
struct box *new_text; |
unsigned utf8_len; |
while (p < ep) { |
if (*p == '\n' || *p == '\r') break; |
p++; |
} |
utf8_len = p - utf8; |
if (!textinput_textbox_insert(c, text_box, char_offset, |
utf8, utf8_len)) |
return false; |
char_offset += utf8_len; |
if (p == ep) |
break; |
new_text = textinput_textarea_insert_break(c, text_box, |
char_offset); |
if (!new_text) { |
/* we still need to update the screen */ |
update = true; |
success = false; |
break; |
} |
/* place caret at start of new text box */ |
text_box = new_text; |
char_offset = 0; |
/* handle CR/LF and LF/CR terminations */ |
if ((*p == '\n' && p[1] == '\r') || |
(*p == '\r' && p[1] == '\n')) |
p++; |
utf8 = ++p; |
} |
// textarea->gadget->caret_inline_container = inline_container; |
textarea->gadget->caret_text_box = text_box; |
textarea->gadget->caret_box_offset = char_offset; |
if (update) { |
int box_x, box_y; |
plot_font_style_t fstyle; |
/* reflow textarea preserving width and height */ |
textinput_textarea_reflow(c, textarea, inline_container); |
/* reflowing may have broken our caret offset |
* this bit should hopefully continue to work if |
* textarea_reflow is fixed to update the caret itself */ |
char_offset = textarea->gadget->caret_box_offset; |
text_box = textarea->gadget->caret_text_box; |
while ((char_offset > text_box->length + SPACE_LEN(text_box)) && |
(text_box->next) && |
(text_box->next->type == BOX_TEXT)) { |
#ifdef TEXTINPUT_DEBUG |
LOG(("Caret out of range: Was %d in boxlen %d " |
"space %d", char_offset, |
text_box->length, SPACE_LEN(text_box))); |
#endif |
char_offset -= text_box->length + SPACE_LEN(text_box); |
text_box = text_box->next; |
} |
/* not sure if this will happen or not... |
* but won't stick an assert here as we can recover from it */ |
if (char_offset > text_box->length) { |
#ifdef TEXTINPUT_DEBUG |
LOG(("Caret moved beyond end of line: " |
"Was %d in boxlen %d", char_offset, |
text_box->length)); |
#endif |
char_offset = text_box->length; |
} |
textarea->gadget->caret_text_box = text_box; |
textarea->gadget->caret_box_offset = char_offset; |
font_plot_style_from_css(text_box->style, &fstyle); |
nsfont.font_width(&fstyle, text_box->text, |
char_offset, &pixel_offset); |
textarea->gadget->caret_pixel_offset = pixel_offset; |
box_coords(textarea, &box_x, &box_y); |
box_x += scrollbar_get_offset(textarea->scroll_x); |
box_y += scrollbar_get_offset(textarea->scroll_y); |
textinput_ensure_caret_visible(c, textarea); |
box_x -= scrollbar_get_offset(textarea->scroll_x); |
box_y -= scrollbar_get_offset(textarea->scroll_y); |
browser_window_place_caret(bw, |
box_x + inline_container->x + text_box->x + |
pixel_offset, |
box_y + inline_container->y + text_box->y, |
text_box->height, |
textinput_textarea_callback, |
textinput_textarea_paste_text, |
textinput_textarea_move_caret, |
textarea, c); |
html__redraw_a_box((html_content *)c, textarea); |
} |
return success; |
} |
/** |
* Move caret to new position after reformatting |
* |
* \param bw browser window |
* \param p1 pointer textarea box |
* \param p2 html content with the text area box |
* \return none |
*/ |
void textinput_textarea_move_caret(struct browser_window *bw, |
void *p1, void *p2) |
{ |
struct box *textarea = p1; |
struct content *c = p2; |
struct box *inline_container = textarea->gadget->caret_inline_container; |
struct box *text_box = textarea->gadget->caret_text_box; |
size_t char_offset = textarea->gadget->caret_box_offset; |
int pixel_offset; |
int box_x, box_y; |
plot_font_style_t fstyle; |
font_plot_style_from_css(text_box->style, &fstyle); |
box_coords(textarea, &box_x, &box_y); |
box_x -= scrollbar_get_offset(textarea->scroll_x); |
box_y -= scrollbar_get_offset(textarea->scroll_y); |
nsfont.font_width(&fstyle, text_box->text, |
char_offset, &pixel_offset); |
browser_window_place_caret(bw, |
box_x + inline_container->x + text_box->x + |
pixel_offset, |
box_y + inline_container->y + text_box->y, |
text_box->height, |
textinput_textarea_callback, |
textinput_textarea_paste_text, |
textinput_textarea_move_caret, |
textarea, c); |
} |
/** |
* Update display to reflect modified input field |
* |
* \param bw browser window |
* \param input input field |
* \param form_offset |
* \param box_offset offset of caret within text box |
* \param to_textarea caret is to be moved to a textarea |
* \param redraw force redraw even if field hasn't scrolled |
*/ |
static void textinput_input_update_display(struct content *c, struct box *input, |
unsigned box_offset, bool to_textarea, bool redraw) |
{ |
struct box *text_box = input->children->children; |
unsigned pixel_offset; |
int box_x, box_y; |
int dx; |
plot_font_style_t fstyle; |
html_content *html = (html_content *)c; |
font_plot_style_from_css(text_box->style, &fstyle); |
if (redraw) |
nsfont.font_width(&fstyle, text_box->text, text_box->length, |
&text_box->width); |
box_coords(input, &box_x, &box_y); |
nsfont.font_width(&fstyle, text_box->text, box_offset, |
(int *) &pixel_offset); |
/* Shift text box horizontally, so caret is visible */ |
dx = text_box->x; |
text_box->x = 0; |
if (input->width < text_box->width && |
input->width / 2 < (int) pixel_offset) { |
/* Make caret appear in centre of text input */ |
text_box->x = input->width / 2 - pixel_offset; |
/* Clamp if we've shifted too far left */ |
if (text_box->x < input->width - text_box->width) |
text_box->x = input->width - text_box->width; |
} |
dx -= text_box->x; |
input->gadget->caret_pixel_offset = pixel_offset; |
if (to_textarea) { |
/* moving to textarea so need to set these up */ |
input->gadget->caret_inline_container = input->children; |
input->gadget->caret_text_box = text_box; |
} |
input->gadget->caret_box_offset = box_offset; |
browser_window_place_caret(html->bw, |
box_x + input->children->x + |
text_box->x + pixel_offset, |
box_y + input->children->y + text_box->y, |
text_box->height, |
/* use the appropriate callback */ |
to_textarea ? textinput_textarea_callback |
: textinput_input_callback, |
to_textarea ? textinput_textarea_paste_text |
: textinput_input_paste_text, |
to_textarea ? textinput_textarea_move_caret |
: textinput_input_move_caret, |
input, c); |
if (dx || redraw) |
html__redraw_a_box(html, input); |
} |
/** |
* Key press callback for text areas. |
* |
* \param bw The browser window containing the text area |
* \param key The ucs4 character codepoint |
* \param p1 The text area box |
* \param p2 The html content with the text area box |
* \return true if the keypress is dealt with, false otherwise. It can |
* return true even if it ran out of memory; this just means that |
* it would have claimed it if it could. |
*/ |
bool textinput_textarea_callback(struct browser_window *bw, uint32_t key, |
void *p1, void *p2) |
{ |
struct box *textarea = p1; |
struct content *c = p2; |
html_content *html = (html_content *)c; |
struct box *inline_container = |
textarea->gadget->caret_inline_container; |
struct box *text_box = textarea->gadget->caret_text_box; |
struct box *new_text; |
size_t char_offset = textarea->gadget->caret_box_offset; |
int pixel_offset = textarea->gadget->caret_pixel_offset; |
int box_x, box_y; |
char utf8[6]; |
unsigned int utf8_len; |
bool scrolled, reflow = false; |
bool selection_exists = html->sel.defined; |
plot_font_style_t fstyle; |
/* box_dump(textarea, 0); */ |
#ifdef TEXTINPUT_DEBUG |
LOG(("key %i at %i in '%.*s'", key, char_offset, |
(int) text_box->length, text_box->text)); |
#endif |
box_coords(textarea, &box_x, &box_y); |
box_x -= scrollbar_get_offset(textarea->scroll_x); |
box_y -= scrollbar_get_offset(textarea->scroll_y); |
if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) { |
/* normal character insertion */ |
utf8_len = utf8_from_ucs4(key, utf8); |
if (!textinput_textbox_insert(c, text_box, char_offset, |
utf8, utf8_len)) |
return true; |
char_offset += utf8_len; |
reflow = true; |
} else switch (key) { |
case KEY_DELETE_LEFT: |
if (selection_exists) { |
/* Have a selection; delete it */ |
textinput_textbox_delete(c, text_box, 0, 0); |
} else if (char_offset == 0) { |
/* at the start of a text box */ |
struct box *prev; |
if (text_box->prev && text_box->prev->type == BOX_BR) { |
/* previous box is BR: remove it */ |
box_unlink_and_free(text_box->prev); |
} |
/* This needs to be after the BR removal, as that may |
* result in no previous box existing */ |
if (!text_box->prev) |
/* at very beginning of text area: ignore */ |
return true; |
/* delete space by merging with previous text box */ |
prev = text_box->prev; |
assert(prev->type == BOX_TEXT); |
assert(prev->text); |
char_offset = prev->length; /* caret at join */ |
if (!textinput_textbox_insert(c, prev, prev->length, |
text_box->text, text_box->length)) |
return true; |
box_unlink_and_free(text_box); |
/* place caret at join (see above) */ |
text_box = prev; |
} else { |
/* delete a character */ |
size_t prev_offset = char_offset; |
size_t new_offset = |
utf8_prev(text_box->text, char_offset); |
if (textinput_textbox_delete(c, text_box, new_offset, |
prev_offset - new_offset)) |
char_offset = new_offset; |
} |
reflow = true; |
break; |
case KEY_DELETE_LINE_START: |
{ |
struct box *start_box = textinput_line_start(text_box); |
/* Clear the selection, if one exists */ |
if (selection_exists) |
selection_clear(&html->sel, false); |
textinput_textarea_cut(c, start_box, 0, text_box, |
char_offset, false); |
text_box = start_box; |
char_offset = 0; |
reflow = true; |
} |
break; |
case KEY_DELETE_LINE_END: |
{ |
struct box *end_box = textinput_line_end(text_box); |
/* Clear the selection, if one exists */ |
if (selection_exists) |
selection_clear(&html->sel, false); |
if (end_box != text_box || |
char_offset < text_box->length + SPACE_LEN(text_box)) { |
/* there's something at the end of the line to delete */ |
textinput_textarea_cut(c, text_box, |
char_offset, end_box, |
end_box->length + SPACE_LEN(end_box), |
false); |
reflow = true; |
break; |
} |
} |
/* no break */ |
case KEY_DELETE_RIGHT: /* delete to right */ |
if (selection_exists) { |
/* Delete selection */ |
textinput_textbox_delete(c, text_box, 0, 0); |
} else if (char_offset >= text_box->length) { |
/* at the end of a text box */ |
struct box *next; |
if (text_box->next && text_box->next->type == BOX_BR) { |
/* next box is a BR: remove it */ |
box_unlink_and_free(text_box->next); |
} |
/* This test is after the BR removal, as that may |
* result in no subsequent box being present */ |
if (!text_box->next) |
/* at very end of text area: ignore */ |
return true; |
/* delete space by merging with next text box */ |
next = text_box->next; |
assert(next->type == BOX_TEXT); |
assert(next->text); |
if (!textinput_textbox_insert(c, text_box, |
text_box->length, |
next->text, next->length)) |
return true; |
box_unlink_and_free(next); |
/* leave caret at join */ |
} else { |
/* delete a character */ |
size_t next_offset = utf8_next(text_box->text, |
text_box->length, char_offset); |
textinput_textbox_delete(c, text_box, char_offset, |
next_offset - char_offset); |
} |
reflow = true; |
break; |
case KEY_NL: |
case KEY_CR: /* paragraph break */ |
if (selection_exists) { |
/* If we have a selection, then delete it, |
* so it's replaced by the break */ |
textinput_textbox_delete(c, text_box, 0, 0); |
} |
new_text = textinput_textarea_insert_break(c, text_box, |
char_offset); |
if (!new_text) |
return true; |
/* place caret at start of new text box */ |
text_box = new_text; |
char_offset = 0; |
reflow = true; |
break; |
case KEY_CUT_LINE: |
{ |
struct box *start_box = textinput_line_start(text_box); |
struct box *end_box = textinput_line_end(text_box); |
/* Clear the selection, if one exists */ |
if (selection_exists) |
selection_clear(&html->sel, false); |
textinput_textarea_cut(c, start_box, 0, end_box, |
end_box->length, false); |
text_box = start_box; |
char_offset = 0; |
reflow = true; |
} |
break; |
case KEY_PASTE: |
{ |
char *buff = NULL; |
size_t buff_len; |
bool success; |
gui_get_clipboard(&buff, &buff_len); |
if (buff == NULL) |
return false; |
success = browser_window_paste_text(bw, buff, buff_len, true); |
free(buff); |
return success; |
} |
case KEY_CUT_SELECTION: |
{ |
size_t start_idx, end_idx; |
struct box *start_box = |
selection_get_start(&html->sel, &start_idx); |
struct box *end_box = selection_get_end(&html->sel, &end_idx); |
if (start_box && end_box) { |
selection_clear(&html->sel, false); |
textinput_textarea_cut(c, start_box, start_idx, |
end_box, end_idx, true); |
text_box = start_box; |
char_offset = start_idx; |
reflow = true; |
} |
} |
break; |
case KEY_RIGHT: |
if (selection_exists) { |
/* In selection, move caret to end */ |
text_box = selection_get_end(&html->sel, &char_offset); |
} else if (char_offset < text_box->length) { |
/* Within-box movement */ |
char_offset = utf8_next(text_box->text, |
text_box->length, char_offset); |
} else { |
/* Between-box movement */ |
if (!text_box->next) |
/* at end of text area: ignore */ |
return true; |
text_box = text_box->next; |
if (text_box->type == BOX_BR) |
text_box = text_box->next; |
char_offset = 0; |
} |
break; |
case KEY_LEFT: |
if (selection_exists) { |
/* In selection, move caret to start */ |
text_box = selection_get_start(&html->sel, &char_offset); |
} else if (char_offset > 0) { |
/* Within-box movement */ |
char_offset = utf8_prev(text_box->text, char_offset); |
} else { |
/* Between-box movement */ |
if (!text_box->prev) |
/* at start of text area: ignore */ |
return true; |
text_box = text_box->prev; |
if (text_box->type == BOX_BR) |
text_box = text_box->prev; |
char_offset = text_box->length; |
} |
break; |
case KEY_UP: |
selection_clear(&html->sel, true); |
textinput_textarea_click(c, BROWSER_MOUSE_CLICK_1, |
textarea, |
box_x, box_y, |
text_box->x + pixel_offset, |
inline_container->y + text_box->y - 1); |
return true; |
case KEY_DOWN: |
selection_clear(&html->sel, true); |
textinput_textarea_click(c, BROWSER_MOUSE_CLICK_1, |
textarea, |
box_x, box_y, |
text_box->x + pixel_offset, |
inline_container->y + text_box->y + |
text_box->height + 1); |
return true; |
case KEY_LINE_START: |
text_box = textinput_line_start(text_box); |
char_offset = 0; |
break; |
case KEY_LINE_END: |
text_box = textinput_line_end(text_box); |
char_offset = text_box->length; |
break; |
case KEY_TEXT_START: |
assert(text_box->parent); |
/* place caret at start of first box */ |
text_box = text_box->parent->children; |
char_offset = 0; |
break; |
case KEY_TEXT_END: |
assert(text_box->parent); |
/* place caret at end of last box */ |
text_box = text_box->parent->last; |
char_offset = text_box->length; |
break; |
case KEY_WORD_LEFT: |
{ |
bool start_of_word; |
/* if there is a selection, caret should stay at beginning */ |
if (selection_exists) |
break; |
start_of_word = (char_offset <= 0 || |
isspace(text_box->text[char_offset - 1])); |
while (!textinput_word_left(text_box->text, |
&char_offset, NULL)) { |
struct box *prev = NULL; |
assert(char_offset == 0); |
if (start_of_word) { |
/* find the preceding non-BR box */ |
prev = text_box->prev; |
if (prev && prev->type == BOX_BR) |
prev = prev->prev; |
} |
if (!prev) { |
/* just stay at the start of this box */ |
break; |
} |
assert(prev->type == BOX_TEXT); |
text_box = prev; |
char_offset = prev->length; |
} |
} |
break; |
case KEY_WORD_RIGHT: |
{ |
bool in_word; |
/* if there is a selection, caret should move to the end */ |
if (selection_exists) { |
text_box = selection_get_end(&html->sel, &char_offset); |
break; |
} |
in_word = (char_offset < text_box->length && |
!isspace(text_box->text[char_offset])); |
while (!textinput_word_right(text_box->text, text_box->length, |
&char_offset, NULL)) { |
struct box *next = text_box->next; |
/* find the next non-BR box */ |
if (next && next->type == BOX_BR) |
next = next->next; |
if (!next) { |
/* just stay at the end of this box */ |
char_offset = text_box->length; |
break; |
} |
assert(next->type == BOX_TEXT); |
text_box = next; |
char_offset = 0; |
if (in_word && text_box->length > 0 && |
!isspace(text_box->text[0])) { |
/* just stay at the start of this box */ |
break; |
} |
} |
} |
break; |
case KEY_PAGE_UP: |
{ |
int nlines = (textarea->height / text_box->height) - 1; |
while (nlines-- > 0) |
text_box = textinput_line_above(text_box); |
if (char_offset > text_box->length) |
char_offset = text_box->length; |
} |
break; |
case KEY_PAGE_DOWN: |
{ |
int nlines = (textarea->height / text_box->height) - 1; |
while (nlines-- > 0) |
text_box = textinput_line_below(text_box); |
/* vague attempt to keep the caret at the same horizontal |
* position, given that the code currently cannot support it |
* being beyond the end of a line */ |
if (char_offset > text_box->length) |
char_offset = text_box->length; |
} |
break; |
default: |
return false; |
} |
/* |
box_dump(textarea, 0); |
for (struct box *t = inline_container->children; t; t = t->next) { |
assert(t->type == BOX_TEXT); |
assert(t->text); |
assert(t->parent == inline_container); |
if (t->next) assert(t->next->prev == t); |
if (t->prev) assert(t->prev->next == t); |
if (!t->next) { |
assert(inline_container->last == t); |
break; |
} |
if (t->next->type == BOX_BR) { |
assert(t->next->next); |
t = t->next; |
} |
} */ |
if (reflow) |
textinput_textarea_reflow(c, textarea, inline_container); |
if (text_box->length + SPACE_LEN(text_box) <= char_offset) { |
if (text_box->next && text_box->next->type == BOX_TEXT) { |
/* the text box has been split when reflowing and |
the caret is in the second part */ |
char_offset -= (text_box->length + SPACE_LEN(text_box)); |
text_box = text_box->next; |
assert(text_box); |
assert(char_offset <= text_box->length); |
/* Scroll back to the left */ |
if (textarea->scroll_x != NULL) { |
box_x += scrollbar_get_offset( |
textarea->scroll_x); |
scrollbar_set(textarea->scroll_x, 0, false); |
} |
} else { |
assert(!text_box->next || |
(text_box->next && |
text_box->next->type == BOX_BR)); |
char_offset = text_box->length + SPACE_LEN(text_box); |
} |
} |
font_plot_style_from_css(text_box->style, &fstyle); |
nsfont.font_width(&fstyle, text_box->text, char_offset, &pixel_offset); |
selection_clear(&html->sel, true); |
textarea->gadget->caret_inline_container = inline_container; |
textarea->gadget->caret_text_box = text_box; |
textarea->gadget->caret_box_offset = char_offset; |
textarea->gadget->caret_pixel_offset = pixel_offset; |
box_x += scrollbar_get_offset(textarea->scroll_x); |
box_y += scrollbar_get_offset(textarea->scroll_y); |
scrolled = textinput_ensure_caret_visible(c, textarea); |
box_x -= scrollbar_get_offset(textarea->scroll_x); |
box_y -= scrollbar_get_offset(textarea->scroll_y); |
browser_window_place_caret(bw, |
box_x + inline_container->x + text_box->x + |
pixel_offset, |
box_y + inline_container->y + text_box->y, |
text_box->height, |
textinput_textarea_callback, |
textinput_textarea_paste_text, |
textinput_textarea_move_caret, |
textarea, c); |
if (scrolled || reflow) |
html__redraw_a_box(html, textarea); |
return true; |
} |
/** |
* Handle clicks in a text area by placing the caret. |
* |
* \param c html content where click occurred |
* \param mouse state of mouse buttons and modifier keys |
* \param textarea textarea box |
* \param box_x position of textarea in global document coordinates |
* \param box_y position of textarea in global document coordinates |
* \param x coordinate of click relative to textarea |
* \param y coordinate of click relative to textarea |
*/ |
void textinput_textarea_click(struct content *c, browser_mouse_state mouse, |
struct box *textarea, int box_x, int box_y, int x, int y) |
{ |
/* A textarea is an INLINE_BLOCK containing a single |
* INLINE_CONTAINER, which contains the text as runs of TEXT |
* separated by BR. There is at least one TEXT. The first and |
* last boxes are TEXT. Consecutive BR may not be present. These |
* constraints are satisfied by using a 0-length TEXT for blank |
* lines. */ |
int char_offset = 0, pixel_offset = 0; |
struct box *inline_container = textarea->children; |
struct box *text_box; |
bool scrolled; |
html_content *html = (html_content *)c; |
text_box = textinput_textarea_get_position(textarea, x, y, |
&char_offset, &pixel_offset); |
textarea->gadget->caret_inline_container = inline_container; |
textarea->gadget->caret_text_box = text_box; |
textarea->gadget->caret_box_offset = char_offset; |
textarea->gadget->caret_pixel_offset = pixel_offset; |
box_x += scrollbar_get_offset(textarea->scroll_x); |
box_y += scrollbar_get_offset(textarea->scroll_y); |
scrolled = textinput_ensure_caret_visible(c, textarea); |
box_x -= scrollbar_get_offset(textarea->scroll_x); |
box_y -= scrollbar_get_offset(textarea->scroll_y); |
browser_window_place_caret(html->bw, |
box_x + inline_container->x + text_box->x + |
pixel_offset, |
box_y + inline_container->y + text_box->y, |
text_box->height, |
textinput_textarea_callback, |
textinput_textarea_paste_text, |
textinput_textarea_move_caret, |
textarea, c); |
if (scrolled) |
html__redraw_a_box(html, textarea); |
} |
/** |
* Paste a block of text into an input field at the caret position. |
* |
* \param bw browser window |
* \param utf8 pointer to block of text |
* \param utf8_len length (bytes) of text block |
* \param last true iff this is the last chunk (update screen too) |
* \param p1 pointer to input box |
* \param p2 html content with the input box |
* \return true iff successful |
*/ |
bool textinput_input_paste_text(struct browser_window *bw, |
const char *utf8, unsigned utf8_len, bool last, |
void *p1, void *p2) |
{ |
struct box *input = p1; |
struct content *c = p2; |
struct box *text_box = input->children->children; |
size_t box_offset = input->gadget->caret_box_offset; |
unsigned int nchars = utf8_length(input->gadget->value); |
const char *ep = utf8 + utf8_len; |
const char *p = utf8; |
bool success = true; |
bool update = last; |
/* keep adding chars until we've run out or would exceed |
the maximum length of the field (in which we silently |
ignore all others) |
*/ |
while (p < ep && nchars < input->gadget->maxlength) { |
char buf[80 + 6]; |
int nbytes = 0; |
/* how many more chars can we insert in one go? */ |
while (p < ep && nbytes < 80 && |
nchars < input->gadget->maxlength && |
*p != '\n' && *p != '\r') { |
unsigned len = utf8_next(p, ep - p, 0); |
if (*p == ' ') |
nbytes += utf8_from_ucs4(160, &buf[nbytes]); |
else { |
memcpy(&buf[nbytes], p, len); |
nbytes += len; |
} |
p += len; |
nchars++; |
} |
if (!textinput_textbox_insert(c, text_box, box_offset, |
buf, nbytes)) { |
/* we still need to update the screen */ |
update = true; |
success = false; |
break; |
} |
box_offset += nbytes; |
/* Keep caret_form_offset in sync -- textbox_insert uses this |
* to determine where to insert into the gadget's value */ |
input->gadget->caret_form_offset += nbytes; |
/* handle CR/LF and LF/CR terminations */ |
if (*p == '\n') { |
p++; |
if (*p == '\r') p++; |
} |
else if (*p == '\r') { |
p++; |
if (*p == '\n') p++; |
} |
} |
if (update) |
textinput_input_update_display(c, input, box_offset, |
false, true); |
return success; |
} |
/** |
* Move caret to new position after reformatting |
* |
* \param bw browser window |
* \param p1 pointer to text input box |
* \param p2 html content with the input box |
* \return none |
*/ |
void textinput_input_move_caret(struct browser_window *bw, |
void *p1, void *p2) |
{ |
struct box *input = (struct box *)p1; |
struct content *c = p2; |
struct box *text_box = input->children->children; |
unsigned int box_offset = input->gadget->caret_box_offset; |
int pixel_offset; |
int box_x, box_y; |
plot_font_style_t fstyle; |
font_plot_style_from_css(text_box->style, &fstyle); |
box_coords(input, &box_x, &box_y); |
nsfont.font_width(&fstyle, text_box->text, box_offset, |
&pixel_offset); |
browser_window_place_caret(bw, |
box_x + input->children->x + |
text_box->x + pixel_offset, |
box_y + input->children->y + text_box->y, |
text_box->height, |
textinput_input_callback, |
textinput_input_paste_text, |
textinput_input_move_caret, |
input, c); |
} |
/** |
* Key press callback for text or password input boxes. |
* |
* \param bw The browser window containing the input box |
* \param key The UCS4 character codepoint |
* \param p1 The input box |
* \param p2 The html content with the input box |
* \return true if the keypress is dealt with, false otherwise. It can |
* return true even if it ran out of memory; this just means that |
* it would have claimed it if it could. |
*/ |
bool textinput_input_callback(struct browser_window *bw, uint32_t key, |
void *p1, void *p2) |
{ |
struct box *input = (struct box *)p1; |
struct content *c = p2; |
html_content *html = (html_content *)c; |
struct box *text_box = input->children->children; |
size_t box_offset = input->gadget->caret_box_offset; |
size_t end_offset; |
int box_x, box_y; |
struct form* form = input->gadget->form; |
bool changed = false; |
char utf8[6]; |
unsigned int utf8_len; |
bool to_textarea = false; |
bool selection_exists = html->sel.defined; |
input->gadget->caret_form_offset = |
textinput_get_form_offset(input, text_box, box_offset); |
/* update the form offset */ |
input->gadget->caret_form_offset = |
textinput_get_form_offset(input, text_box, box_offset); |
selection_get_end(&html->sel, &end_offset); |
box_coords(input, &box_x, &box_y); |
/* normal character insertion */ |
if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) { |
/* have we exceeded max length of input? */ |
utf8_len = utf8_length(input->gadget->value); |
if (utf8_len >= input->gadget->maxlength) |
return true; |
utf8_len = utf8_from_ucs4(key, utf8); |
if (!textinput_textbox_insert(c, text_box, box_offset, |
utf8, utf8_len)) |
return true; |
box_offset += utf8_len; |
changed = true; |
} else switch (key) { |
case KEY_DELETE_LEFT: |
{ |
int prev_offset, new_offset; |
if (selection_exists) { |
textinput_textbox_delete(c, text_box, 0, 0); |
} else { |
/* Can't delete left from text box start */ |
if (box_offset == 0) |
return true; |
prev_offset = box_offset; |
new_offset = utf8_prev(text_box->text, box_offset); |
if (textinput_textbox_delete(c, text_box, new_offset, |
prev_offset - new_offset)) |
box_offset = new_offset; |
} |
changed = true; |
} |
break; |
case KEY_DELETE_RIGHT: |
{ |
unsigned next_offset; |
if (selection_exists) { |
textinput_textbox_delete(c, text_box, 0, 0); |
} else { |
/* Can't delete right from text box end */ |
if (box_offset >= text_box->length) |
return true; |
/* Go to the next valid UTF-8 character */ |
next_offset = utf8_next(text_box->text, |
text_box->length, box_offset); |
textinput_textbox_delete(c, text_box, box_offset, |
next_offset - box_offset); |
} |
changed = true; |
} |
break; |
case 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 = 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; |
input = next_input->box; |
box_offset = 0; |
to_textarea = next_input->type == GADGET_TEXTAREA; |
} |
break; |
case KEY_NL: |
case KEY_CR: /* Return/Enter hit */ |
selection_clear(&html->sel, true); |
if (form) |
form_submit(content_get_url(c), bw, form, 0); |
return true; |
case 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 = 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; |
input = prev_input->box; |
box_offset = 0; |
to_textarea = prev_input->type == GADGET_TEXTAREA; |
} |
break; |
case KEY_CUT_LINE: |
/* Clear the selection, if one exists */ |
if (selection_exists) |
selection_clear(&html->sel, false); |
textinput_textarea_cut(c, text_box, 0, text_box, |
text_box->length, false); |
box_offset = 0; |
changed = true; |
break; |
case KEY_PASTE: |
{ |
char *buff = NULL; |
size_t buff_len; |
bool success; |
gui_get_clipboard(&buff, &buff_len); |
if (buff == NULL) |
return false; |
success = browser_window_paste_text(bw, buff, buff_len, true); |
free(buff); |
return success; |
} |
case KEY_CUT_SELECTION: |
{ |
size_t start_idx, end_idx; |
struct box *start_box = |
selection_get_start(&html->sel, &start_idx); |
struct box *end_box = selection_get_end(&html->sel, &end_idx); |
if (start_box && end_box) { |
selection_clear(&html->sel, false); |
textinput_textarea_cut(c, start_box, start_idx, |
end_box, end_idx, true); |
box_offset = start_idx; |
changed = true; |
} |
} |
break; |
case KEY_RIGHT: |
if (selection_exists) { |
box_offset = end_offset; |
break; |
} |
if (box_offset < text_box->length) { |
/* Go to the next valid UTF-8 character */ |
box_offset = utf8_next(text_box->text, |
text_box->length, box_offset); |
} |
break; |
case KEY_LEFT: |
/* If there is a selection, caret should remain at start */ |
if (selection_exists) |
break; |
/* Go to the previous valid UTF-8 character */ |
box_offset = utf8_prev(text_box->text, box_offset); |
break; |
case KEY_LINE_START: |
box_offset = 0; |
break; |
case KEY_LINE_END: |
box_offset = text_box->length; |
break; |
case KEY_WORD_LEFT: |
/* If there is a selection, caret should remain at start */ |
if (selection_exists) |
break; |
if (!textinput_word_left(text_box->text, &box_offset, NULL)) |
box_offset = 0; |
break; |
case KEY_WORD_RIGHT: |
if (selection_exists) { |
box_offset = end_offset; |
break; |
} |
if (!textinput_word_right(text_box->text, text_box->length, |
&box_offset, NULL)) |
box_offset = text_box->length; |
break; |
case KEY_DELETE_LINE_START: |
if (selection_exists) |
selection_clear(&html->sel, true); |
if (box_offset == 0) |
return true; |
textinput_textarea_cut(c, text_box, 0, text_box, |
box_offset, false); |
box_offset = 0; |
changed = true; |
break; |
case KEY_DELETE_LINE_END: |
if (selection_exists) |
selection_clear(&html->sel, true); |
if (box_offset >= text_box->length) |
return true; |
textinput_textarea_cut(c, text_box, box_offset, |
text_box, text_box->length, false); |
changed = true; |
break; |
default: |
return false; |
} |
selection_clear(&html->sel, true); |
textinput_input_update_display(c, input, box_offset, |
to_textarea, changed); |
return true; |
} |
/** |
* Handle clicks in a text or password input box by placing the caret. |
* |
* \param bw browser window where click occurred |
* \param input input box |
* \param box_x position of input in global document coordinates |
* \param box_y position of input in global document coordinates |
* \param x coordinate of click relative to input |
* \param y coordinate of click relative to input |
*/ |
void textinput_input_click(struct content *c, struct box *input, |
int box_x, int box_y, int x, int y) |
{ |
size_t char_offset = 0; |
int pixel_offset = 0, dx = 0; |
struct box *text_box = input->children->children; |
plot_font_style_t fstyle; |
html_content *html = (html_content *)c; |
font_plot_style_from_css(text_box->style, &fstyle); |
nsfont.font_position_in_string(&fstyle, text_box->text, |
text_box->length, x - text_box->x, |
&char_offset, &pixel_offset); |
assert(char_offset <= text_box->length); |
/* Shift the text box horizontally to ensure that the |
* caret position is visible, and ideally centred */ |
text_box->x = 0; |
if ((input->width < text_box->width) && |
(input->width / 2 < pixel_offset)) { |
dx = text_box->x; |
/* Move left so caret is centred */ |
text_box->x = input->width / 2 - pixel_offset; |
/* Clamp, so text box's right hand edge coincides |
* with the input's right hand edge */ |
if (text_box->x < input->width - text_box->width) |
text_box->x = input->width - text_box->width; |
dx -= text_box->x; |
} |
input->gadget->caret_box_offset = char_offset; |
input->gadget->caret_form_offset = |
textinput_get_form_offset(input, text_box, char_offset); |
input->gadget->caret_pixel_offset = pixel_offset; |
browser_window_place_caret(html->bw, |
box_x + input->children->x + |
text_box->x + pixel_offset, |
box_y + input->children->y + text_box->y, |
text_box->height, |
textinput_input_callback, |
textinput_input_paste_text, |
textinput_input_move_caret, |
input, c); |
if (dx) |
html__redraw_a_box(html, input); |
} |
/contrib/network/netsurf/netsurf/render/textinput.h |
---|
0,0 → 1,41 |
/* |
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> |
* Copyright 2004 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk> |
* Copyright 2004 John Tytgat <joty@netsurf-browser.org> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* HTML form text input handling (interface) |
*/ |
#ifndef _NETSURF_RENDER_TEXTINPUT_H_ |
#define _NETSURF_RENDER_TEXTINPUT_H_ |
#include <stdbool.h> |
struct box; |
struct content; |
void textinput_textarea_click(struct content *c, browser_mouse_state mouse, |
struct box *textarea, int box_x, int box_y, int x, int y); |
void textinput_input_click(struct content *c, struct box *input, |
int box_x, int box_y, int x, int y); |
#endif |
/contrib/network/netsurf/netsurf/render/textplain.c |
---|
0,0 → 1,1268 |
/* |
* Copyright 2006 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2006 Adrian Lees <adrianl@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Content for text/plain (implementation). |
*/ |
#include <assert.h> |
#include <errno.h> |
#include <stddef.h> |
#include <string.h> |
#include <strings.h> |
#include <math.h> |
#include <parserutils/input/inputstream.h> |
#include "content/content_protected.h" |
#include "content/hlcache.h" |
#include "css/css.h" |
#include "css/utils.h" |
#include "desktop/browser.h" |
#include "desktop/gui.h" |
#include "desktop/options.h" |
#include "desktop/plotters.h" |
#include "desktop/search.h" |
#include "desktop/selection.h" |
#include "render/font.h" |
#include "render/search.h" |
#include "render/textplain.h" |
#include "render/html.h" |
#include "utils/http.h" |
#include "utils/log.h" |
#include "utils/messages.h" |
#include "utils/utils.h" |
#include "utils/utf8.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; |
} 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 void textplain_fini(void); |
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); |
static nserror textplain_create_internal(textplain_content *c, |
lwc_string *charset); |
static bool textplain_process_data(struct content *c, |
const char *data, unsigned int size); |
static bool textplain_convert(struct content *c); |
static void textplain_mouse_track(struct content *c, struct browser_window *bw, |
browser_mouse_state mouse, int x, int y); |
static void textplain_mouse_action(struct content *c, struct browser_window *bw, |
browser_mouse_state mouse, int x, int y); |
static void textplain_reformat(struct content *c, int width, int height); |
static void textplain_destroy(struct content *c); |
static bool textplain_redraw(struct content *c, struct content_redraw_data *data, |
const struct rect *clip, const struct redraw_context *ctx); |
static void textplain_open(struct content *c, struct browser_window *bw, |
struct content *page, struct object_params *params); |
void textplain_close(struct content *c); |
struct selection *textplain_get_selection(struct content *c); |
struct search_context *textplain_get_search(struct content *c); |
static nserror textplain_clone(const struct content *old, |
struct content **newc); |
static content_type textplain_content_type(void); |
static parserutils_error textplain_charset_hack(const uint8_t *data, size_t len, |
uint16_t *mibenum, uint32_t *source); |
static bool textplain_drain_input(textplain_content *c, |
parserutils_inputstream *stream, parserutils_error terminator); |
static bool textplain_copy_utf8_data(textplain_content *c, |
const uint8_t *buf, size_t len); |
static int textplain_coord_from_offset(const char *text, size_t offset, |
size_t length); |
static float textplain_line_height(void); |
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, |
.redraw = textplain_redraw, |
.open = textplain_open, |
.close = textplain_close, |
.get_selection = textplain_get_selection, |
.clone = textplain_clone, |
.type = textplain_content_type, |
.no_share = true, |
}; |
static lwc_string *textplain_charset; |
static lwc_string *textplain_default_charset; |
/** |
* Initialise the text content handler |
*/ |
nserror textplain_init(void) |
{ |
lwc_error lerror; |
nserror error; |
lerror = lwc_intern_string("charset", SLEN("charset"), |
&textplain_charset); |
if (lerror != lwc_error_ok) { |
return NSERROR_NOMEM; |
} |
lerror = lwc_intern_string("Windows-1252", SLEN("Windows-1252"), |
&textplain_default_charset); |
if (lerror != lwc_error_ok) { |
lwc_string_unref(textplain_charset); |
return NSERROR_NOMEM; |
} |
error = content_factory_register_handler("text/plain", |
&textplain_content_handler); |
if (error != NSERROR_OK) { |
lwc_string_unref(textplain_default_charset); |
lwc_string_unref(textplain_charset); |
} |
return error; |
} |
/** |
* Clean up after the text content handler |
*/ |
void textplain_fini(void) |
{ |
if (textplain_default_charset != NULL) { |
lwc_string_unref(textplain_default_charset); |
textplain_default_charset = NULL; |
} |
if (textplain_charset != NULL) { |
lwc_string_unref(textplain_charset); |
textplain_charset = NULL; |
} |
} |
/** |
* Create a CONTENT_TEXTPLAIN. |
*/ |
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, textplain_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; |
} |
/* |
* Hack around bug 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. |
* |
* We avoid this by providing a callback that does precisely nothing, |
* thus preserving whatever charset information we decided on in |
* textplain_create. |
*/ |
parserutils_error textplain_charset_hack(const uint8_t *data, size_t len, |
uint16_t *mibenum, uint32_t *source) |
{ |
return PARSERUTILS_OK; |
} |
nserror textplain_create_internal(textplain_content *c, lwc_string *encoding) |
{ |
char *utf8_data; |
parserutils_inputstream *stream; |
parserutils_error error; |
union content_msg_data msg_data; |
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, ns_realloc, NULL, &stream); |
if (error == PARSERUTILS_BADENCODING) { |
/* Fall back to Windows-1252 */ |
error = parserutils_inputstream_create("Windows-1252", 0, |
textplain_charset_hack, ns_realloc, NULL, |
&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: |
msg_data.error = messages_get("NoMemory"); |
content_broadcast(&c->base, CONTENT_MSG_ERROR, msg_data); |
return NSERROR_NOMEM; |
} |
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; |
} |
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; |
} |
/** |
* Process data for CONTENT_TEXTPLAIN. |
*/ |
bool textplain_process_data(struct content *c, |
const char *data, unsigned int size) |
{ |
textplain_content *text = (textplain_content *) c; |
parserutils_inputstream *stream = text->inputstream; |
union content_msg_data msg_data; |
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: |
msg_data.error = messages_get("NoMemory"); |
content_broadcast(c, CONTENT_MSG_ERROR, msg_data); |
return false; |
} |
/** |
* Convert a CONTENT_TEXTPLAIN for display. |
*/ |
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; |
} |
/** |
* Reformat a CONTENT_TEXTPLAIN to a new width. |
*/ |
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; |
/* compute available columns (assuming monospaced font) - use 8 |
* characters for better accuracy */ |
if (!nsfont.font_width(&textplain_style, "ABCDEFGH", 8, &character_width)) |
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; |
for (i = 0, col = 0; i != utf8_data_size; i++) { |
bool term = (utf8_data[i] == '\n' || utf8_data[i] == '\r'); |
size_t next_col = col + 1; |
if (utf8_data[i] == '\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 (utf8_data[i] == ' ') |
space = i; |
} |
} |
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: |
LOG(("out of memory (line_count %lu)", line_count)); |
return; |
} |
/** |
* Destroy a CONTENT_TEXTPLAIN and free all resources it owns. |
*/ |
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); |
} |
} |
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; |
} |
content_type textplain_content_type(void) |
{ |
return CONTENT_TEXTPLAIN; |
} |
/** |
* 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 |
*/ |
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 mouse clicks and movements in a TEXTPLAIN content window. |
* |
* \param c content of type textplain |
* \param bw browser window |
* \param click type of mouse click |
* \param x coordinate of mouse |
* \param y coordinate of mouse |
*/ |
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); |
} |
/** |
* Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot). |
* |
* \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 |
* |
* x, y, clip_[xy][01] are in target coordinates. |
*/ |
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; |
const struct plotter_table *plot = ctx->plot; |
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; |
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; |
if (!plot->rectangle(clip->x0, clip->y0, clip->x1, clip->y1, |
plot_style_fill_white)) |
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; |
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; |
/* locate end of string and align to next tab position */ |
if (nsfont.font_width(&textplain_style, &text_d[offset], |
next_offset - offset, &width)) |
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) { |
int sy = y + (lineno * scaled_line_height); |
if (!plot->rectangle(tx, sy, |
ntx, sy + scaled_line_height, |
plot_style_highlight)) |
return false; |
} |
} |
offset = next_offset + 1; |
tx = ntx; |
} |
} |
return true; |
} |
/** |
* Handle a window containing a CONTENT_TEXTPLAIN being opened. |
*/ |
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); |
} |
/** |
* Handle a window containing a CONTENT_TEXTPLAIN being closed. |
*/ |
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 |
*/ |
struct selection *textplain_get_selection(struct content *c) |
{ |
textplain_content *text = (textplain_content *) c; |
return &text->sel; |
} |
/** |
* Set an TEXTPLAIN content's search context |
* |
* \param c content of type text |
* \param s search context, or NULL if none |
*/ |
void textplain_set_search(struct content *c, struct search_context *s) |
{ |
textplain_content *text = (textplain_content *) c; |
text->search = s; |
} |
/** |
* Return an TEXTPLAIN content's search context |
* |
* \param c content of type text |
* \return content's search context, or NULL if none |
*/ |
struct search_context *textplain_get_search(struct content *c) |
{ |
textplain_content *text = (textplain_content *) c; |
return text->search; |
} |
/** |
* Retrieve number of lines in content |
* |
* \param h Content to retrieve line count from |
* \return Number of lines |
*/ |
unsigned long textplain_line_count(struct content *c) |
{ |
textplain_content *text = (textplain_content *) c; |
assert(c != NULL); |
return text->physical_line_count; |
} |
/** |
* Retrieve the size (in bytes) of text data |
* |
* \param h Content to retrieve size of |
* \return Size, in bytes, of data |
*/ |
size_t textplain_size(struct content *c) |
{ |
textplain_content *text = (textplain_content *) c; |
assert(c != NULL); |
return text->utf8_data_size; |
} |
/** |
* 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 h content of type CONTENT_TEXTPLAIN |
* \param x x ordinate of point |
* \param y y ordinate of point |
* \param 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) |
{ |
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) |
nsfont.font_width(&textplain_style, text, next_offset, &width); |
if (x <= width) { |
int pixel_offset; |
size_t char_offset; |
nsfont.font_position_in_string(&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; |
} |
/** |
* Given a byte offset within the text, return the line number |
* of the line containing that offset (or -1 if offset invalid) |
* |
* \param c content of type CONTENT_TEXTPLAIN |
* \param 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) |
{ |
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; |
} |
/** |
* Convert a character offset within a line of text into the |
* horizontal co-ordinate, taking 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 |
*/ |
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); |
nsfont.font_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; |
} |
/** |
* Given a range of byte offsets within a UTF8 textplain content, |
* return a box that fully encloses the text |
* |
* \param h content of type CONTENT_TEXTPLAIN |
* \param start byte offset of start of text range |
* \param end byte offset of end |
* \param r rectangle to be completed |
*/ |
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); |
} |
/** |
* Return a pointer to the requested line of text. |
* |
* \param h content of type CONTENT_TEXTPLAIN |
* \param lineno line number |
* \param poffset receives byte offset of line start within text |
* \param 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) |
{ |
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; |
} |
/** |
* 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 h content of type CONTENT_TEXTPLAIN |
* \param start starting byte offset within UTF-8 text |
* \param end ending byte offset |
* \param 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) |
{ |
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; |
} |
/** |
* Calculate the line height, in pixels |
* |
* \return Line height, in pixels |
*/ |
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)); |
} |
/** |
* Get the browser window containing a textplain content |
* |
* \param c text/plain content |
* \return the browser window |
*/ |
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; |
} |
/contrib/network/netsurf/netsurf/render/textplain.h |
---|
0,0 → 1,52 |
/* |
* Copyright 2006 James Bursa <bursa@users.sourceforge.net> |
* Copyright 2006 Adrian Lees <adrianl@users.sourceforge.net> |
* |
* 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 <http://www.gnu.org/licenses/>. |
*/ |
/** \file |
* Content for text/plain (interface). |
*/ |
#ifndef _NETSURF_RENDER_TEXTPLAIN_H_ |
#define _NETSURF_RENDER_TEXTPLAIN_H_ |
#include <stddef.h> |
#include "desktop/mouse.h" |
struct content; |
struct hlcache_handle; |
struct http_parameter; |
struct rect; |
nserror textplain_init(void); |
/* access to lines for text selection and searching */ |
unsigned long textplain_line_count(struct content *c); |
size_t textplain_size(struct content *c); |
size_t textplain_offset_from_coords(struct content *c, int x, int y, int dir); |
void textplain_coords_from_range(struct content *c, |
unsigned start, unsigned end, struct rect *r); |
char *textplain_get_line(struct content *c, unsigned lineno, |
size_t *poffset, size_t *plen); |
int textplain_find_line(struct content *c, unsigned offset); |
char *textplain_get_raw_data(struct content *c, |
unsigned start, unsigned end, size_t *plen); |
struct browser_window *textplain_get_browser_window(struct content *c); |
void textplain_set_search(struct content *c, struct search_context *s); |
#endif |