Subversion Repositories Kolibri OS

Compare Revisions

Regard whitespace Rev 4363 → Rev 4364

/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,
&params->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,
&params->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,
&params->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,
&params->codetype) == false)
return false;
if (box_get_attribute(n, "type", params, &params->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,
&param->name) == false) {
dom_node_unref(c);
return false;
}
 
if (box_get_attribute(c, "value", param,
&param->value) == false) {
dom_node_unref(c);
return false;
}
 
if (box_get_attribute(c, "type", param,
&param->type) == false) {
dom_node_unref(c);
return false;
}
 
if (box_get_attribute(c, "valuetype", param,
&param->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,
&params->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,
&current_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,
&current_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, &current_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