netsurf/render/html_redraw.c
Daniel Silverstone 6807b4208a Remove the netsurf/ from the include paths and rationalise use of <> vs "" in includes
NetSurf includes are now done with ""s and other system includes with <>s as C intended.
The scandeps tool has been updated to only look for ""ed includes, and to verify that the
files exist in the tree before adding them to the dependency lines. The depend rule has
therefore been augmented to make sure the autogenerated files are built before it is run.

This is untested under self-hosted RISC OS builds. All else tested and works.


svn path=/trunk/netsurf/; revision=3307
2007-05-30 22:39:54 +00:00

1550 lines
44 KiB
C

/*
* This file is part of NetSurf, http://netsurf-browser.org/
* Licensed under the GNU General Public License,
* http://www.opensource.org/licenses/gpl-license
* Copyright 2004 James Bursa <bursa@users.sourceforge.net>
*/
/** \file
* Redraw of a CONTENT_HTML (implementation).
*/
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include "utils/config.h"
#include "content/content.h"
#include "css/css.h"
#include "desktop/gui.h"
#include "desktop/plotters.h"
#include "desktop/knockout.h"
#include "desktop/selection.h"
#include "desktop/textinput.h"
#include "desktop/options.h"
#include "render/box.h"
#include "render/font.h"
#include "render/form.h"
#include "render/layout.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/utils.h"
static bool html_redraw_box(struct box *box,
int x, int y,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour current_background_color);
static bool html_redraw_box_children(struct box *box,
int x_parent, int y_parent,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour current_background_color);
static bool html_redraw_text_box(struct box *box, int x, int y,
int x0, int y0, int x1, int y1,
float scale, colour current_background_color);
static bool html_redraw_caret(struct caret *caret,
colour current_background_color, float scale);
static bool html_redraw_borders(struct box *box, int x_parent, int y_parent,
int padding_width, int padding_height, float scale);
static bool html_redraw_border_plot(int i, int *p, colour c,
css_border_style style, int thickness);
static colour html_redraw_darker(colour c);
static colour html_redraw_lighter(colour c);
static colour html_redraw_aa(colour c0, colour c1);
static bool html_redraw_checkbox(int x, int y, int width, int height,
bool selected);
static bool html_redraw_radio(int x, int y, int width, int height,
bool selected);
static bool html_redraw_file(int x, int y, int width, int height,
struct box *box, float scale, colour background_colour);
static bool html_redraw_background(int x, int y, struct box *box, float scale,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
colour *background_colour);
static bool html_redraw_text_decoration(struct box *box,
int x_parent, int y_parent, float scale,
colour background_colour);
static bool html_redraw_text_decoration_inline(struct box *box, int x, int y,
float scale, colour colour, float ratio);
static bool html_redraw_text_decoration_block(struct box *box, int x, int y,
float scale, colour colour, float ratio);
static bool html_redraw_scrollbars(struct box *box, float scale,
int x, int y, int padding_width, int padding_height,
colour background_colour);
bool html_redraw_debug = false;
/**
* Draw a CONTENT_HTML using the current set of plotters (plot).
*
* \param c content of type CONTENT_HTML
* \param x coordinate for top-left of redraw
* \param y coordinate for top-left of redraw
* \param width available width (not used for HTML redraw)
* \param height available height (not used for HTML redraw)
* \param clip_x0 clip rectangle
* \param clip_y0 clip rectangle
* \param clip_x1 clip rectangle
* \param clip_y1 clip rectangle
* \param scale scale for redraw
* \param background_colour the background colour
* \return true if successful, false otherwise
*
* x, y, clip_[xy][01] are in target coordinates.
*/
bool html_redraw(struct content *c, int x, int y,
int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, unsigned long background_colour)
{
struct box *box;
bool result;
box = c->data.html.layout;
assert(box);
knockout_plot_start(&plot);
/* clear to background colour */
plot.clip(clip_x0, clip_y0, clip_x1, clip_y1);
if (c->data.html.background_colour != TRANSPARENT)
background_colour = c->data.html.background_colour;
plot.clg(background_colour);
result = html_redraw_box(box, x, y,
clip_x0, clip_y0, clip_x1, clip_y1,
scale, background_colour);
knockout_plot_end();
return result;
}
/**
* Recursively draw a box.
*
* \param box box to draw
* \param x_parent coordinate of parent box
* \param y_parent coordinate of parent box
* \param clip_x0 clip rectangle
* \param clip_y0 clip rectangle
* \param clip_x1 clip rectangle
* \param clip_y1 clip rectangle
* \param scale scale for redraw
* \param current_background_color background colour under this box
* \return true if successful, false otherwise
*
* x, y, clip_[xy][01] are in target coordinates.
*/
bool html_redraw_box(struct box *box,
int x_parent, int y_parent,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour current_background_color)
{
int x, y;
int width, height;
int padding_left, padding_top, padding_width, padding_height;
int x0, y0, x1, y1;
int x_scrolled, y_scrolled;
/* 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];
} 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;
}
/* calculate clip rectangle for this box */
if (box->style && box->style->overflow != CSS_OVERFLOW_VISIBLE) {
x0 = x;
y0 = y;
x1 = x + padding_width;
y1 = y + padding_height;
} else {
x0 = x + box->descendant_x0 * scale;
y0 = y + box->descendant_y0 * scale;
x1 = x + box->descendant_x1 * scale + 1;
y1 = y + box->descendant_y1 * scale + 1;
}
/* if visibility is hidden render children only */
if (box->style && box->style->visibility == CSS_VISIBILITY_HIDDEN) {
if ((plot.group_start) && (!plot.group_start("hidden box")))
return false;
if (!html_redraw_box_children(box, x_parent, y_parent,
x0, y0, x1, y1, scale,
current_background_color))
return false;
return ((!plot.group_end) || (plot.group_end()));
}
if ((plot.group_start) && (!plot.group_start("vis box")))
return false;
/* dotted debug outlines */
if (html_redraw_debug) {
if (!plot.rectangle(x, y, padding_width, padding_height,
1, 0x0000ff, true, false))
return false;
if (!plot.rectangle(x + padding_left, y + padding_top,
width, height, 1, 0xff0000, true, false))
return false;
if (!plot.rectangle(x - (box->border[LEFT] +
box->margin[LEFT]) * scale,
y - (box->border[TOP] +
box->margin[TOP]) * scale,
padding_width + (box->border[LEFT] +
box->margin[LEFT] + box->border[RIGHT] +
box->margin[RIGHT]) * scale,
padding_height + (box->border[TOP] +
box->margin[TOP] + box->border[BOTTOM] +
box->margin[BOTTOM]) * scale,
1, 0x00ffff, true, false))
return false;
}
/* borders */
if (box->style && box->type != BOX_TEXT && (box->border[TOP] ||
box->border[RIGHT] || box->border[BOTTOM] ||
box->border[LEFT]))
if (!html_redraw_borders(box, x_parent, y_parent,
padding_width, padding_height,
scale))
return false;
/* return if the box is completely outside the clip rectangle */
if (clip_y1 < y0 || y1 < clip_y0 || clip_x1 < x0 || x1 < clip_x0)
return ((!plot.group_end) || (plot.group_end()));
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 (x0 < clip_x0) x0 = clip_x0;
if (y0 < clip_y0) y0 = clip_y0;
if (clip_x1 < x1) x1 = clip_x1;
if (clip_y1 < y1) y1 = clip_y1;
/* no point trying to draw 0-width/height boxes */
if (x0 == x1 || y0 == y1)
/* not an error, so return true */
return true;
/* clip to it */
if (!plot.clip(x0, y0, x1, y1))
return false;
} else {
/* clip box unchanged */
x0 = clip_x0;
y0 = clip_y0;
x1 = clip_x1;
y1 = clip_y1;
}
/* background colour and image */
if ((box->style && box->type != BOX_BR && (box->type != BOX_INLINE ||
box->style != box->parent->parent->style)) &&
((box->style->background_color != TRANSPARENT) ||
(box->background))) {
/* find intersection of clip box and padding box */
int px0 = x < x0 ? x0 : x;
int py0 = y < y0 ? y0 : y;
int px1 = x + padding_width < x1 ? x + padding_width : x1;
int py1 = y + padding_height < y1 ? y + padding_height : y1;
/* valid clipping rectangles only */
if ((px0 < px1) && (py0 < py1)) {
/* plot background */
if (!html_redraw_background(x, y, box, scale,
px0, py0, px1, py1,
&current_background_color))
return false;
/* restore previous graphics window */
if (!plot.clip(x0, y0, x1, y1))
return false;
}
}
/* text decoration */
if (box->type != BOX_TEXT && box->style &&
box->style->text_decoration !=
CSS_TEXT_DECORATION_NONE)
if (!html_redraw_text_decoration(box, x_parent, y_parent,
scale, current_background_color))
return false;
if (box->object) {
x_scrolled = x - box->scroll_x * scale;
y_scrolled = y - box->scroll_y * scale;
if (!content_redraw(box->object,
x_scrolled + padding_left,
y_scrolled + padding_top,
width, height, x0, y0, x1, y1, scale,
current_background_color))
return false;
} else if (box->gadget && box->gadget->type == GADGET_CHECKBOX) {
if (!html_redraw_checkbox(x + padding_left, y + padding_top,
width, height,
box->gadget->selected))
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))
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))
return false;
} else if (box->text) {
if (!html_redraw_text_box(box, x, y, x0, y0, x1, y1,
scale, current_background_color))
return false;
} else {
if (!html_redraw_box_children(box, x_parent, y_parent,
x0, y0, x1, y1, scale,
current_background_color))
return false;
}
/* list marker */
if (box->list_marker)
if (!html_redraw_box(box->list_marker,
x_parent + box->x - box->scroll_x,
y_parent + box->y - box->scroll_y,
clip_x0, clip_y0, clip_x1, clip_y1,
scale, current_background_color))
return false;
/* scrollbars */
if (box->style && box->type != BOX_BR && box->type != BOX_INLINE &&
(box->style->overflow == CSS_OVERFLOW_SCROLL ||
box->style->overflow == CSS_OVERFLOW_AUTO))
if (!html_redraw_scrollbars(box, scale, x, y,
padding_width, padding_height,
current_background_color))
return false;
if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK ||
box->type == BOX_TABLE_CELL || box->object)
if (!plot.clip(clip_x0, clip_y0, clip_x1, clip_y1))
return false;
return ((!plot.group_end) || (plot.group_end()));
}
/**
* Draw the various children of a box.
*
* \param box box to draw
* \param x_parent coordinate of parent box
* \param y_parent coordinate of parent box
* \param clip_x0 clip rectangle
* \param clip_y0 clip rectangle
* \param clip_x1 clip rectangle
* \param clip_y1 clip rectangle
* \param scale scale for redraw
* \param current_background_color background colour under this box
* \return true if successful, false otherwise
*/
bool html_redraw_box_children(struct box *box,
int x_parent, int y_parent,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
float scale, colour current_background_color)
{
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(c,
x_parent + box->x - box->scroll_x,
y_parent + box->y - box->scroll_y,
clip_x0, clip_y0, clip_x1, clip_y1,
scale, current_background_color))
return false;
for (c = box->float_children; c; c = c->next_float)
if (!html_redraw_box(c,
x_parent + box->x - box->scroll_x,
y_parent + box->y - box->scroll_y,
clip_x0, clip_y0, clip_x1, clip_y1,
scale, current_background_color))
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 x0 current clip rectangle
* \param y0
* \param x1
* \param y1
* \param scale current scale setting (1.0 = 100%)
* \param current_background_color
* \return true iff successful and redraw should proceed
*/
bool html_redraw_text_box(struct box *box, int x, int y,
int x0, int y0, int x1, int y1,
float scale, colour current_background_color)
{
bool excluded = (box->object != NULL);
struct rect clip;
clip.x0 = x0;
clip.y0 = y0;
clip.x1 = x1;
clip.y1 = y1;
if (!text_redraw(box->text, box->length, box->byte_offset,
box->space, box->style, x, y,
&clip, box->height, scale,
current_background_color, excluded))
return false;
/* does this textbox contain the ghost caret? */
if (ghost_caret.defined && box == ghost_caret.text_box) {
if (!html_redraw_caret(&ghost_caret, current_background_color, scale))
return false;
}
return true;
}
/**
* Redraw a short text string, complete with highlighting
* (for selection/search) and ghost caret
*
* \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 indicates whether string is followed by a space
* \param style text style to use
* \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 current_background_color
* \param excluded exclude this text string from the selection
* \return true iff successful and redraw should proceed
*/
bool text_redraw(const char *utf8_text, size_t utf8_len,
size_t offset, bool space, struct css_style *style,
int x, int y, struct rect *clip,
int height,
float scale, colour current_background_color,
bool excluded)
{
bool highlighted = false;
/* is this box part of a selection? */
if (!excluded && current_redraw_browser) {
unsigned len = utf8_len + (space ? 1 : 0);
unsigned start_idx;
unsigned end_idx;
/* first try the browser window's current selection */
if (selection_defined(current_redraw_browser->sel) &&
selection_highlighted(current_redraw_browser->sel,
offset, offset + len, &start_idx, &end_idx)) {
highlighted = true;
}
/* what about the current search operation, if any? */
if (!highlighted &&
search_current_window == current_redraw_browser->window &&
gui_search_term_highlighted(current_redraw_browser->window,
offset, offset + len, &start_idx, &end_idx)) {
highlighted = true;
}
/* \todo make search terms visible within selected text */
if (highlighted) {
unsigned endtxt_idx = end_idx;
colour hfore_col, hback_col;
bool clip_changed = false;
bool text_visible = true;
int startx, endx;
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_width(style, utf8_text, start_idx, &startx))
startx = 0;
if (!nsfont_width(style, utf8_text, endtxt_idx, &endx))
endx = 0;
/* is there a trailing space that should be highlighted as well? */
if (end_idx > utf8_len) {
int spc_width;
/* \todo is there a more elegant/efficient solution? */
if (nsfont_width(style, " ", 1, &spc_width))
endx += spc_width;
}
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),
style, utf8_text, start_idx,
current_background_color,
/*print_text_black ? 0 :*/ style->color))
return false;
/* decide whether highlighted portion is to be white-on-black or
black-on-white */
if ((current_background_color & 0x808080) == 0x808080)
hback_col = 0;
else
hback_col = 0xffffff;
hfore_col = hback_col ^ 0xffffff;
/* highlighted portion */
if (!plot.fill(x + startx, y, x + endx, y + height * scale,
hback_col))
return false;
if (start_idx > 0) {
int px0 = max(x + startx, clip->x0);
int px1 = min(x + endx, clip->x1);
if (px0 < px1) {
if (!plot.clip(px0, clip->y0, px1, clip->y1))
return false;
clip_changed = true;
} else
text_visible = false;
}
if (text_visible &&
!plot.text(x, y + (int) (height * 0.75 * scale),
style, utf8_text, endtxt_idx,
hback_col, hfore_col))
return false;
/* draw any text succeeding highlighted portion */
if (endtxt_idx < utf8_len) {
int px0 = max(x + endx, clip->x0);
if (px0 < clip->x1) {
if (!plot.clip(px0, clip->y0, clip->x1, clip->y1))
return false;
clip_changed = true;
if (!plot.text(x, y + (int) (height * 0.75 * scale),
style, utf8_text, utf8_len,
current_background_color,
/*print_text_black ? 0 :*/ style->color))
return false;
}
}
if (clip_changed &&
!plot.clip(clip->x0, clip->y0, clip->x1, clip->y1))
return false;
}
}
if (!highlighted) {
if (!plot.text(x, y + (int) (height * 0.75 * scale),
style, utf8_text, utf8_len,
current_background_color,
/*print_text_black ? 0 :*/ style->color))
return false;
}
return true;
}
/**
* Draw text caret.
*
* \param c structure describing text caret
* \param current_background_color background colour under the caret
* \param scale current scale setting (1.0 = 100%)
* \return true iff successful and redraw should proceed
*/
bool html_redraw_caret(struct caret *c, colour current_background_color,
float scale)
{
colour caret_color = 0x808080; /* todo - choose a proper colour */
int xc = c->x, y = c->y;
int h = c->height - 1;
int w = (h + 7) / 8;
return (plot.line(xc * scale, y * scale,
xc * scale, (y + h) * scale,
0, caret_color, false, false) &&
plot.line((xc - w) * scale, y * scale,
(xc + w) * scale, y * scale,
0, caret_color, false, false) &&
plot.line((xc - w) * scale, (y + h) * scale,
(xc + w) * scale, (y + h) * scale,
0, caret_color, false, false));
}
/**
* 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 padding_width width of padding box
* \param padding_height height of padding box
* \param scale scale for redraw
* \return true if successful, false otherwise
*/
bool html_redraw_borders(struct box *box, int x_parent, int y_parent,
int padding_width, int padding_height, float scale)
{
int top = box->border[TOP];
int right = box->border[RIGHT];
int bottom = box->border[BOTTOM];
int left = box->border[LEFT];
if (scale != 1.0) {
top *= scale;
right *= scale;
bottom *= scale;
left *= scale;
}
assert(box->style);
if (box->type == BOX_INLINE && !box->object && !box->gadget &&
!box->text) {
int padding_height = (box->padding[TOP] + box->height +
box->padding[BOTTOM]) * scale;
for (struct box *c = box; c; c = c->next) {
int x = (x_parent + c->x) * scale;
int y = y_parent + c->y;
int padding_width = c->width;
if (c != box)
y -= box->padding[TOP];
if (c == box)
padding_width += box->padding[LEFT];
if (!box->inline_end || c == box->inline_end)
padding_width += box->padding[RIGHT];
if (scale != 1) {
y *= scale;
padding_width *= scale;
}
int p[20] = {
x, y,
x - left, y - top,
x + padding_width + right, y - top,
x + padding_width, y,
x + padding_width, y + padding_height,
x + padding_width + right,
y + padding_height + bottom,
x - left, y + padding_height + bottom,
x, y + padding_height,
x, y,
x - left, y - top
};
if (box->border[LEFT] && c == box)
html_redraw_border_plot(LEFT, p,
box->style->border[LEFT].color,
box->style->border[LEFT].style,
box->border[LEFT] * scale);
if (box->border[TOP])
html_redraw_border_plot(TOP, p,
box->style->border[TOP].color,
box->style->border[TOP].style,
box->border[TOP] * scale);
if (box->border[BOTTOM])
html_redraw_border_plot(BOTTOM, p,
box->style->border[BOTTOM].
color,
box->style->border[BOTTOM].
style,
box->border[BOTTOM] * scale);
if (box->border[RIGHT] && (!box->inline_end ||
c == box->inline_end))
html_redraw_border_plot(RIGHT, p,
box->style->border[RIGHT].color,
box->style->border[RIGHT].style,
box->border[RIGHT] * scale);
if (!box->inline_end || c == box->inline_end)
break;
}
} else {
int x = (x_parent + box->x) * scale;
int y = (y_parent + box->y) * scale;
int p[20] = {
x, y,
x - left, y - top,
x + padding_width + right, y - top,
x + padding_width, y,
x + padding_width, y + padding_height,
x + padding_width + right, y + padding_height + bottom,
x - left, y + padding_height + bottom,
x, y + padding_height,
x, y,
x - left, y - top
};
for (unsigned int i = 0; i != 4; i++) {
if (box->border[i] == 0)
continue;
if (!html_redraw_border_plot(i, p,
box->style->border[i].color,
box->style->border[i].style,
box->border[i] * scale))
return false;
}
}
return true;
}
/**
* Draw one border.
*
* \param i index of border (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
* \return true if successful, false otherwise
*/
bool html_redraw_border_plot(int i, int *p, colour c,
css_border_style style, int thickness)
{
int z[8];
bool dotted = false;
unsigned int light = i;
colour c_lit;
if (c == TRANSPARENT)
return true;
switch (style) {
case CSS_BORDER_STYLE_DOTTED:
dotted = true;
case CSS_BORDER_STYLE_DASHED:
if (!plot.line((p[i * 4 + 0] + p[i * 4 + 2]) / 2,
(p[i * 4 + 1] + p[i * 4 + 3]) / 2,
(p[i * 4 + 4] + p[i * 4 + 6]) / 2,
(p[i * 4 + 5] + p[i * 4 + 7]) / 2,
thickness,
c, dotted, !dotted))
return false;
return true;
case CSS_BORDER_STYLE_SOLID:
break;
case CSS_BORDER_STYLE_DOUBLE:
z[0] = p[i * 4 + 0];
z[1] = p[i * 4 + 1];
z[2] = (p[i * 4 + 0] * 2 + p[i * 4 + 2]) / 3;
z[3] = (p[i * 4 + 1] * 2 + p[i * 4 + 3]) / 3;
z[4] = (p[i * 4 + 6] * 2 + p[i * 4 + 4]) / 3;
z[5] = (p[i * 4 + 7] * 2 + p[i * 4 + 5]) / 3;
z[6] = p[i * 4 + 6];
z[7] = p[i * 4 + 7];
if (!plot.polygon(z, 4, c))
return false;
z[0] = p[i * 4 + 2];
z[1] = p[i * 4 + 3];
z[2] = (p[i * 4 + 2] * 2 + p[i * 4 + 0]) / 3;
z[3] = (p[i * 4 + 3] * 2 + p[i * 4 + 1]) / 3;
z[4] = (p[i * 4 + 4] * 2 + p[i * 4 + 6]) / 3;
z[5] = (p[i * 4 + 5] * 2 + p[i * 4 + 7]) / 3;
z[6] = p[i * 4 + 4];
z[7] = p[i * 4 + 5];
if (!plot.polygon(z, 4, c))
return false;
return true;
case CSS_BORDER_STYLE_GROOVE:
light = 3 - light;
case CSS_BORDER_STYLE_RIDGE:
z[0] = p[i * 4 + 0];
z[1] = p[i * 4 + 1];
z[2] = (p[i * 4 + 0] + p[i * 4 + 2]) / 2;
z[3] = (p[i * 4 + 1] + p[i * 4 + 3]) / 2;
z[4] = (p[i * 4 + 6] + p[i * 4 + 4]) / 2;
z[5] = (p[i * 4 + 7] + p[i * 4 + 5]) / 2;
z[6] = p[i * 4 + 6];
z[7] = p[i * 4 + 7];
if (!plot.polygon(z, 4, light <= 1 ?
html_redraw_darker(c) :
html_redraw_lighter(c)))
return false;
z[0] = p[i * 4 + 2];
z[1] = p[i * 4 + 3];
z[6] = p[i * 4 + 4];
z[7] = p[i * 4 + 5];
if (!plot.polygon(z, 4, light <= 1 ?
html_redraw_lighter(c) :
html_redraw_darker(c)))
return false;
return true;
case CSS_BORDER_STYLE_INSET:
light = (light + 2) % 4;
case CSS_BORDER_STYLE_OUTSET:
z[0] = p[i * 4 + 0];
z[1] = p[i * 4 + 1];
z[2] = (p[i * 4 + 0] + p[i * 4 + 2]) / 2;
z[3] = (p[i * 4 + 1] + p[i * 4 + 3]) / 2;
z[4] = (p[i * 4 + 6] + p[i * 4 + 4]) / 2;
z[5] = (p[i * 4 + 7] + p[i * 4 + 5]) / 2;
z[6] = p[i * 4 + 6];
z[7] = p[i * 4 + 7];
c_lit = c;
switch (light) {
case 3:
c_lit = html_redraw_lighter(c_lit);
case 0:
c_lit = html_redraw_lighter(c_lit);
break;
case 1:
c_lit = html_redraw_darker(c_lit);
case 2:
c_lit = html_redraw_darker(c_lit);
}
if (!plot.polygon(z, 4, c_lit))
return false;
z[0] = p[i * 4 + 2];
z[1] = p[i * 4 + 3];
z[6] = p[i * 4 + 4];
z[7] = p[i * 4 + 5];
switch (light) {
case 0:
c = html_redraw_lighter(c);
case 3:
c = html_redraw_lighter(c);
break;
case 2:
c = html_redraw_darker(c);
case 1:
c = html_redraw_darker(c);
}
if (!plot.polygon(z, 4, c))
return false;
return true;
default:
break;
}
if (!plot.polygon(p + i * 4, 4, c))
return false;
return true;
}
/**
* Make a colour darker.
*
* \param c colour
* \return a darker shade of c
*/
#define mix_colour(c0, c1) ((((c0 >> 16) + 3 * (c1 >> 16)) >> 2) << 16) | \
(((((c0 >> 8) & 0xff) + 3 * ((c1 >> 8) & 0xff)) >> 2) << 8) | \
((((c0 & 0xff) + 3 * (c1 & 0xff)) >> 2) << 0);
colour html_redraw_darker(colour c)
{
return mix_colour(0x000000, c)
}
/**
* Make a colour lighter.
*
* \param c colour
* \return a lighter shade of c
*/
colour html_redraw_lighter(colour c)
{
return mix_colour(0xffffff, c)
}
/**
* Mix two colours to produce a colour suitable for anti-aliasing.
*
* \param c0 first colour
* \param c1 second colour
* \return a colour half way between c0 and c1
*/
colour html_redraw_aa(colour c0, colour c1)
{
return ((((c0 >> 16) + (c1 >> 16)) / 2) << 16) |
(((((c0 >> 8) & 0xff) + ((c1 >> 8) & 0xff)) / 2) << 8) |
((((c0 & 0xff) + (c1 & 0xff)) / 2) << 0);
}
/**
* 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
* \return true if successful, false otherwise
*/
#define WIDGET_BASEC 0xd9d9d9
#define WIDGET_BLOBC 0x000000
bool html_redraw_checkbox(int x, int y, int width, int height,
bool selected)
{
int dark = html_redraw_darker(html_redraw_darker(WIDGET_BASEC));
int lite = html_redraw_lighter(html_redraw_lighter(WIDGET_BASEC));
double z = width * 0.15;
if (z == 0)
z = 1;
if (!(plot.fill(x, y, x + width, y + height, WIDGET_BASEC) &&
plot.line(x, y, x + width, y, 1, dark, false, false) &&
plot.line(x, y, x, y + height, 1, dark, false, false) &&
plot.line(x + width, y, x + width, y + height, 1, lite,
false, false) &&
plot.line(x, y + height, x + width, y + height, 1, lite,
false, false)))
return false;
if (selected) {
if (width < 12 || height < 12) {
/* render a solid box instead of a tick */
if (!plot.fill(x + z + z, y + z + z,
x + width - z, y + height - z,
WIDGET_BLOBC))
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,
2, WIDGET_BLOBC, false, false) &&
plot.line(x + (z * 3),
y + height - z,
x + z + z,
y + (height / 2),
2, WIDGET_BLOBC, false, false)))
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
* \return true if successful, false otherwise
*/
bool html_redraw_radio(int x, int y, int width, int height,
bool selected)
{
int dark = html_redraw_darker(html_redraw_darker(WIDGET_BASEC));
int lite = html_redraw_lighter(html_redraw_lighter(WIDGET_BASEC));
/* plot background of radio button */
if (!plot.disc(x + width * 0.5, y + height * 0.5,
width * 0.5 - 1, WIDGET_BASEC, true))
return false;
/* plot dark arc */
if (!plot.arc(x + width * 0.5, y + height * 0.5,
width * 0.5 - 1, 45, 225, dark))
return false;
/* plot light arc */
if (!plot.arc(x + width * 0.5, y + height * 0.5,
width * 0.5 - 1, 225, 45, lite))
return false;
if (selected) {
/* plot selection blob */
if (!plot.disc(x + width * 0.5, y + height * 0.5,
width * 0.3 - 1, WIDGET_BLOBC, true))
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
* \return true if successful, false otherwise
*/
bool html_redraw_file(int x, int y, int width, int height,
struct box *box, float scale, colour background_colour)
{
int text_width;
const char *text;
size_t length;
if (box->gadget->value)
text = box->gadget->value;
else
text = messages_get("Form_Drop");
length = strlen(text);
if (!nsfont_width(box->style, 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 plot.text(x, y + height * 0.75, box->style, text, length,
background_colour,
/*print_text_black ? 0 :*/ box->style->color);
}
/**
* 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 background_colour current background colour
* \return true if successful, false otherwise
*/
bool html_redraw_background(int x, int y, struct box *box, float scale,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
colour *background_colour)
{
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 px0 = clip_x0, py0 = clip_y0, px1 = clip_x1, py1 = clip_y1;
int ox = x, oy = y;
struct box *parent;
plot_content = (box->background != NULL);
if (plot_content) {
/* handle background-repeat */
switch (box->style->background_repeat) {
case CSS_BACKGROUND_REPEAT_REPEAT:
repeat_x = repeat_y = true;
/* optimisation: only plot the colour if bitmap is not opaque */
if (box->background->bitmap)
plot_colour = !bitmap_get_opaque(
box->background->bitmap);
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 */
switch (box->style->background_position.horz.pos) {
case CSS_BACKGROUND_POSITION_PERCENT:
x += (box->padding[LEFT] + box->width +
box->padding[RIGHT] -
box->background->width) * scale *
box->style->background_position.horz.
value.percent / 100;
break;
case CSS_BACKGROUND_POSITION_LENGTH:
x += (int) (css_len2px(&box->style->background_position.
horz.value.length, box->style) * scale);
break;
default:
break;
}
switch (box->style->background_position.vert.pos) {
case CSS_BACKGROUND_POSITION_PERCENT:
y += (box->padding[TOP] + box->height +
box->padding[BOTTOM] -
box->background->height) * scale *
box->style->background_position.vert.
value.percent / 100;
break;
case CSS_BACKGROUND_POSITION_LENGTH:
y += (int) (css_len2px(&box->style->background_position.
vert.value.length, box->style) * scale);
break;
default:
break;
}
}
/* special case for table rows as their background needs to be clipped to
* all the cells */
if (box->type == BOX_TABLE_ROW) {
for (parent = box->parent; ((parent) && (parent->type != BOX_TABLE));
parent = parent->parent);
assert(parent && (parent->style));
clip_to_children = (parent->style->border_spacing.horz.value > 0) ||
(parent->style->border_spacing.vert.value > 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 */
clip_x0 = ox + (clip_box->x * scale);
clip_y0 = oy + (clip_box->y * scale);
clip_x1 = clip_x0 + (clip_box->padding[LEFT] +
clip_box->width + clip_box->padding[RIGHT]) * scale;
clip_y1 = clip_y0 + (clip_box->padding[TOP] +
clip_box->height + clip_box->padding[BOTTOM]) * scale;
if (clip_x0 < px0) clip_x0 = px0;
if (clip_y0 < py0) clip_y0 = py0;
if (clip_x1 > px1) clip_x1 = px1;
if (clip_y1 > py1) clip_y1 = py1;
/* <td> attributes override <tr> */
if ((clip_x0 >= clip_x1) || (clip_y0 >= clip_y1) ||
(clip_box->style->background_color != TRANSPARENT) ||
(clip_box->background &&
clip_box->background->bitmap &&
bitmap_get_opaque(clip_box->background->bitmap)))
continue;
}
/* plot the background colour */
if (box->style->background_color != TRANSPARENT) {
*background_colour = box->style->background_color;
if (plot_colour)
if (!plot.fill(clip_x0, clip_y0, clip_x1, clip_y1,
*background_colour))
return false;
}
/* and plot the image */
if (plot_content) {
if (!plot.clip(clip_x0, clip_y0, clip_x1, clip_y1))
return false;
if (!content_redraw_tiled(box->background, x, y,
ceilf(box->background->width * scale),
ceilf(box->background->height * scale),
clip_x0, clip_y0, clip_x1, clip_y1, scale,
*background_colour,
repeat_x, repeat_y))
return false;
}
/* only <tr> rows being clipped to child boxes loop */
if (!clip_to_children)
return true;
}
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
* \return true if successful, false otherwise
*/
bool html_redraw_text_decoration(struct box *box,
int x_parent, int y_parent, float scale,
colour background_colour)
{
static const css_text_decoration 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 };
int colour;
unsigned int i;
/* antialias colour for under/overline */
colour = html_redraw_aa(background_colour, box->style->color);
if (box->type == BOX_INLINE) {
if (!box->inline_end)
return true;
for (i = 0; i != NOF_ELEMENTS(decoration); i++)
if (box->style->text_decoration & decoration[i])
if (!html_redraw_text_decoration_inline(box,
x_parent, y_parent, scale,
colour, line_ratio[i]))
return false;
} else {
for (i = 0; i != NOF_ELEMENTS(decoration); i++)
if (box->style->text_decoration & decoration[i])
if (!html_redraw_text_decoration_block(box,
x_parent + box->x,
y_parent + box->y,
scale,
colour, line_ratio[i]))
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
* \return true if successful, false otherwise
*/
bool html_redraw_text_decoration_inline(struct box *box, int x, int y,
float scale, colour colour, float ratio)
{
for (struct box *c = box->next;
c && c != box->inline_end;
c = c->next) {
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,
0, colour, false, false))
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
* \return true if successful, false otherwise
*/
bool html_redraw_text_decoration_block(struct box *box, int x, int y,
float scale, colour colour, float ratio)
{
/* draw through text descendants */
for (struct box *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,
0, colour, false, false))
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))
return false;
}
}
return true;
}
/**
* Plot scrollbars for a scrolling box.
*
* \param box scrolling box
* \param scale scale for redraw
* \param x coordinate of box
* \param y coordinate of box
* \param padding_width width of padding box
* \param padding_height height of padding box
* \return true if successful, false otherwise
*/
bool html_redraw_scrollbars(struct box *box, float scale,
int x, int y, int padding_width, int padding_height,
colour background_colour)
{
const int w = SCROLLBAR_WIDTH * scale;
bool vscroll, hscroll;
int well_height, bar_top, bar_height;
int well_width, bar_left, bar_width;
const colour vcolour = box->style->border[RIGHT].color;
const colour hcolour = box->style->border[BOTTOM].color;
box_scrollbar_dimensions(box, padding_width, padding_height, w,
&vscroll, &hscroll,
&well_height, &bar_top, &bar_height,
&well_width, &bar_left, &bar_width);
#define TRIANGLE(x0, y0, x1, y1, x2, y2, c) \
if (!plot.line(x0, y0, x1, y1, scale, c, false, false)) \
return false; \
if (!plot.line(x0, y0, x2, y2, scale, c, false, false)) \
return false; \
if (!plot.line(x1, y1, x2, y2, scale, c, false, false)) \
return false;
/* fill scrollbar well(s) with background colour */
if (vscroll)
if (!plot.fill(x + padding_width - w, y,
x + padding_width, y + padding_height,
background_colour))
return false;
if (hscroll)
if (!plot.fill(x, y + padding_height - w,
x + padding_width, y + padding_height,
background_colour))
return false;
/* vertical scrollbar */
if (vscroll) {
/* left line */
if (!plot.line(x + padding_width - w, y,
x + padding_width - w, y + padding_height,
scale, vcolour, false, false))
return false;
/* up arrow */
TRIANGLE(x + padding_width - w / 2, y + w / 4,
x + padding_width - w * 3 / 4, y + w * 3 / 4,
x + padding_width - w / 4, y + w * 3 / 4,
vcolour);
/* separator */
if (!plot.line(x + padding_width - w, y + w,
x + padding_width, y + w,
scale, vcolour, false, false))
return false;
/* bar */
if (!plot.rectangle(x + padding_width - w * 3 / 4,
y + w + bar_top + w / 4,
w / 2, bar_height - w / 2,
scale, vcolour, false, false))
return false;
/* separator */
if (!plot.line(x + padding_width - w, y + w + well_height,
x + padding_width, y + w + well_height,
scale, vcolour, false, false))
return false;
/* down arrow */
TRIANGLE(x + padding_width - w / 2,
y + w + well_height + w * 3 / 4,
x + padding_width - w * 3 / 4,
y + w + well_height + w / 4,
x + padding_width - w / 4,
y + w + well_height + w / 4,
vcolour);
}
/* horizontal scrollbar */
if (hscroll) {
/* top line */
if (!plot.line(x, y + padding_height - w,
x + well_width + w + w, y + padding_height - w,
scale, hcolour, false, false))
return false;
/* left arrow */
TRIANGLE(x + w / 4, y + padding_height - w / 2,
x + w * 3 / 4, y + padding_height - w * 3 / 4,
x + w * 3 / 4, y + padding_height - w / 4,
hcolour);
/* separator */
if (!plot.line(x + w, y + padding_height - w,
x + w, y + padding_height,
scale, hcolour, false, false))
return false;
/* bar */
if (!plot.rectangle(x + w + bar_left + w / 4,
y + padding_height - w * 3 / 4,
bar_width - w / 2, w / 2,
scale, hcolour, false, false))
return false;
/* separator */
if (!plot.line(x + w + well_width, y + padding_height - w,
x + w + well_width, y + padding_height,
scale, hcolour, false, false))
return false;
/* right arrow */
TRIANGLE(x + w + well_width + w * 3 / 4,
y + padding_height - w / 2,
x + w + well_width + w / 4,
y + padding_height - w * 3 / 4,
x + w + well_width + w / 4,
y + padding_height - w / 4,
hcolour);
}
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->descendant_y0 < -box->border[TOP] ||
box->padding[TOP] + box->height + box->padding[BOTTOM] +
box->border[BOTTOM] < 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->descendant_x0 < -box->border[LEFT] ||
box->padding[LEFT] + box->width + box->padding[RIGHT] +
box->border[RIGHT] < box->descendant_x1;
}
/**
* Calculate scrollbar dimensions and positions for a box.
*
* \param box scrolling box
* \param padding_width scaled width of padding box
* \param padding_height scaled height of padding box
* \param w scaled scrollbar width
* \param vscroll updated to vertical scrollbar present
* \param hscroll updated to horizontal scrollbar present
* \param well_height updated to vertical well height
* \param bar_top updated to top position of vertical scrollbar
* \param bar_height updated to height of vertical scrollbar
* \param well_width updated to horizontal well width
* \param bar_left updated to left position of horizontal scrollbar
* \param bar_width updated to width of horizontal scrollbar
*/
void box_scrollbar_dimensions(const struct box * const box,
const int padding_width, const int padding_height, const int w,
bool * const vscroll, bool * const hscroll,
int * const well_height,
int * const bar_top, int * const bar_height,
int * const well_width,
int * const bar_left, int * const bar_width)
{
*vscroll = box_vscrollbar_present(box);
*hscroll = box_hscrollbar_present(box);
*well_height = padding_height - w - w;
*bar_top = 0;
*bar_height = *well_height;
if (box->descendant_y1 - box->descendant_y0 != 0) {
*bar_top = (float) *well_height * (float) box->scroll_y /
(float) (box->descendant_y1 -
box->descendant_y0);
*bar_height = (float) *well_height * (float) box->height /
(float) (box->descendant_y1 -
box->descendant_y0);
}
*well_width = padding_width - w - w - (*vscroll ? w : 0);
*bar_left = 0;
*bar_width = *well_width;
if (box->descendant_x1 - box->descendant_x0 != 0) {
*bar_left = (float) *well_width * (float) box->scroll_x /
(float) (box->descendant_x1 -
box->descendant_x0);
*bar_width = (float) *well_width * (float) box->width /
(float) (box->descendant_x1 -
box->descendant_x0);
}
}