/* * Copyright 2004-2008 James Bursa * Copyright 2004-2007 John M Bell * Copyright 2004-2007 Richard Wilson * Copyright 2005-2006 Adrian Lees * Copyright 2006 Rob Kendrick * Copyright 2008 Michael Drake * Copyright 2009 Paul Blokus * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** \file * Redraw of a CONTENT_HTML (implementation). */ #include #include #include #include #include #include #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" static bool html_redraw_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); 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); 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); 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); 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); 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); static bool html_redraw_checkbox(int x, int y, int width, int height, bool selected, const struct redraw_context *ctx); static bool html_redraw_radio(int x, int y, int width, int height, bool selected, const struct redraw_context *ctx); 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); 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); 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); 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 bool html_redraw_text_decoration_inline(struct box *box, int x, int y, float scale, colour colour, float ratio, const struct redraw_context *ctx); 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); bool html_redraw_debug = false; /** * 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; } /** * 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; } /** * 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, 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; 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->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || box->type == BOX_TABLE_CELL || box->object) { /* find intersection of clip rectangle and box */ if (r.x0 < clip->x0) r.x0 = clip->x0; if (r.y0 < clip->y0) r.y0 = clip->y0; if (clip->x1 < r.x1) r.x1 = clip->x1; if (clip->y1 < r.y1) r.y1 = clip->y1; /* no point trying to draw 0-width/height boxes */ if (r.x0 == r.x1 || r.y0 == r.y1) /* not an error */ return ((!plot->group_end) || (plot->group_end())); /* clip to it */ if (!plot->clip(&r)) return false; } else { /* clip box is fine, clip to it */ r = *clip; if (!plot->clip(&r)) return false; } /* background colour and image for block level content and replaced * inlines */ bg_box = html_redraw_find_bg_box(box); /* bg_box == NULL implies that this box should not have * its background rendered. Otherwise filter out linebreaks, * optimize away non-differing inlines, only plot background * for BOX_TEXT it's in an inline */ if (bg_box && bg_box->type != BOX_BR && bg_box->type != BOX_TEXT && bg_box->type != BOX_INLINE_END && (bg_box->type != BOX_INLINE || bg_box->object || bg_box->flags & IFRAME || box->flags & REPLACE_DIM)) { /* find intersection of clip box and border edge */ struct rect p; p.x0 = x - border_left < r.x0 ? r.x0 : x - border_left; p.y0 = y - border_top < r.y0 ? r.y0 : y - border_top; p.x1 = x + padding_width + border_right < r.x1 ? x + padding_width + border_right : r.x1; p.y1 = y + padding_height + border_bottom < r.y1 ? y + padding_height + border_bottom : r.y1; if (!box->parent) { /* Root element, special case: * background covers margins too */ int m_left, m_top, m_right, m_bottom; if (scale == 1.0) { m_left = box->margin[LEFT]; m_top = box->margin[TOP]; m_right = box->margin[RIGHT]; m_bottom = box->margin[BOTTOM]; } else { m_left = box->margin[LEFT] * scale; m_top = box->margin[TOP] * scale; m_right = box->margin[RIGHT] * scale; m_bottom = box->margin[BOTTOM] * scale; } p.x0 = p.x0 - m_left < r.x0 ? r.x0 : p.x0 - m_left; p.y0 = p.y0 - m_top < r.y0 ? r.y0 : p.y0 - m_top; p.x1 = p.x1 + m_right < r.x1 ? p.x1 + m_right : r.x1; p.y1 = p.y1 + m_bottom < r.y1 ? p.y1 + m_bottom : r.y1; } /* valid clipping rectangles only */ if ((p.x0 < p.x1) && (p.y0 < p.y1)) { /* plot background */ if (!html_redraw_background(x, y, box, scale, &p, ¤t_background_color, bg_box, ctx)) return false; /* restore previous graphics window */ if (!plot->clip(&r)) return false; } } /* borders for block level content and replaced inlines */ if (box->style && box->type != BOX_TEXT && box->type != BOX_INLINE_END && (box->type != BOX_INLINE || box->object || box->flags & IFRAME || box->flags & REPLACE_DIM) && (border_top || border_right || border_bottom || border_left)) { if (!html_redraw_borders(box, x_parent, y_parent, padding_width, padding_height, &r, scale, ctx)) return false; } /* backgrounds and borders for non-replaced inlines */ if (box->style && box->type == BOX_INLINE && box->inline_end && (html_redraw_box_has_background(box) || border_top || border_right || border_bottom || border_left)) { /* inline backgrounds and borders span other boxes and may * wrap onto separate lines */ struct box *ib; struct rect b; /* border edge rectangle */ struct rect p; /* clipped rect */ bool first = true; int ib_x; int ib_y = y; int ib_p_width; int ib_b_left, ib_b_right; b.x0 = x - border_left; b.x1 = x + padding_width + border_right; b.y0 = y - border_top; b.y1 = y + padding_height + border_bottom; p.x0 = b.x0 < r.x0 ? r.x0 : b.x0; p.x1 = b.x1 < r.x1 ? b.x1 : r.x1; p.y0 = b.y0 < r.y0 ? r.y0 : b.y0; p.y1 = b.y1 < r.y1 ? b.y1 : r.y1; for (ib = box; ib; ib = ib->next) { /* to get extents of rectangle(s) associated with * inline, cycle though all boxes in inline, skipping * over floats */ if (ib->type == BOX_FLOAT_LEFT || ib->type == BOX_FLOAT_RIGHT) continue; if (scale == 1.0) { ib_x = x_parent + ib->x; ib_y = y_parent + ib->y; ib_p_width = ib->padding[LEFT] + ib->width + ib->padding[RIGHT]; ib_b_left = ib->border[LEFT].width; ib_b_right = ib->border[RIGHT].width; } else { ib_x = (x_parent + ib->x) * scale; ib_y = (y_parent + ib->y) * scale; ib_p_width = (ib->padding[LEFT] + ib->width + ib->padding[RIGHT]) * scale; ib_b_left = ib->border[LEFT].width * scale; ib_b_right = ib->border[RIGHT].width * scale; } if ((ib->flags & NEW_LINE) && ib != box) { /* inline element has wrapped, plot background * and borders */ if (!html_redraw_inline_background( x, y, box, scale, &p, b, first, false, ¤t_background_color, ctx)) return false; /* restore previous graphics window */ if (!plot->clip(&r)) return false; if (!html_redraw_inline_borders(box, b, &r, scale, first, false, ctx)) return false; /* reset coords */ b.x0 = ib_x - ib_b_left; b.y0 = ib_y - border_top - padding_top; b.y1 = ib_y + padding_height - padding_top + border_bottom; p.x0 = b.x0 < r.x0 ? r.x0 : b.x0; p.y0 = b.y0 < r.y0 ? r.y0 : b.y0; p.y1 = b.y1 < r.y1 ? b.y1 : r.y1; first = false; } /* increase width for current box */ b.x1 = ib_x + ib_p_width + ib_b_right; p.x1 = b.x1 < r.x1 ? b.x1 : r.x1; if (ib == box->inline_end) /* reached end of BOX_INLINE span */ break; } /* plot background and borders for last rectangle of * the inline */ if (!html_redraw_inline_background(x, ib_y, box, scale, &p, b, first, true, ¤t_background_color, ctx)) return false; /* restore previous graphics window */ if (!plot->clip(&r)) return false; if (!html_redraw_inline_borders(box, b, &r, scale, first, true, ctx)) return false; } /* Debug outlines */ if (html_redraw_debug) { int margin_left, margin_right; int margin_top, margin_bottom; if (scale == 1.0) { /* avoid trivial fp maths */ margin_left = box->margin[LEFT]; margin_top = box->margin[TOP]; margin_right = box->margin[RIGHT]; margin_bottom = box->margin[BOTTOM]; } else { margin_left = box->margin[LEFT] * scale; margin_top = box->margin[TOP] * scale; margin_right = box->margin[RIGHT] * scale; margin_bottom = box->margin[BOTTOM] * scale; } /* Content edge -- blue */ if (!plot->rectangle(x + padding_left, y + padding_top, x + padding_left + width, y + padding_top + height, plot_style_content_edge)) return false; /* Padding edge -- red */ if (!plot->rectangle(x, y, x + padding_width, y + padding_height, plot_style_padding_edge)) return false; /* Margin edge -- yellow */ if (!plot->rectangle( x - border_left - margin_left, y - border_top - margin_top, x + padding_width + border_right + margin_right, y + padding_height + border_bottom + margin_bottom, plot_style_margin_edge)) return false; } /* clip to the padding edge for objects, or boxes with overflow hidden * or scroll */ if ((box->style && css_computed_overflow(box->style) != CSS_OVERFLOW_VISIBLE) || box->object || box->flags & IFRAME) { r.x0 = x; r.y0 = y; r.x1 = x + padding_width; r.y1 = y + padding_height; if (r.x0 < clip->x0) r.x0 = clip->x0; if (r.y0 < clip->y0) r.y0 = clip->y0; if (clip->x1 < r.x1) r.x1 = clip->x1; if (clip->y1 < r.y1) r.y1 = clip->y1; if (r.x1 <= r.x0 || r.y1 <= r.y0) return ((!plot->group_end) || (plot->group_end())); if (box->type == BOX_BLOCK || box->type == BOX_INLINE_BLOCK || box->type == BOX_TABLE_CELL || box->object) { if (!plot->clip(&r)) return false; } } /* text decoration */ if (box->type != BOX_TEXT && box->style && css_computed_text_decoration(box->style) != CSS_TEXT_DECORATION_NONE) if (!html_redraw_text_decoration(box, x_parent, y_parent, scale, current_background_color, ctx)) return false; if (box->object && width != 0 && height != 0) { struct content_redraw_data obj_data; x_scrolled = x - scrollbar_get_offset(box->scroll_x) * scale; y_scrolled = y - scrollbar_get_offset(box->scroll_y) * scale; obj_data.x = x_scrolled + padding_left; obj_data.y = y_scrolled + padding_top; obj_data.width = width; obj_data.height = height; obj_data.background_colour = current_background_color; obj_data.scale = scale; obj_data.repeat_x = false; obj_data.repeat_y = false; if (content_get_type(box->object) == CONTENT_HTML) { obj_data.x /= scale; obj_data.y /= scale; } if (!content_redraw(box->object, &obj_data, &r, ctx)) 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 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 */ 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; } /** * 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 */ 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; } /** * 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; } /** * 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 */ 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 */ 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; } 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 */ 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; } /** * 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 */ 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 */ 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 */ 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 . The background will be drawn relative * to ::box, using the background information contained within ::background. */ 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); /* attributes override */ /* if the background content is opaque there * is no need to plot underneath it. */ if ((r.x0 >= r.x1) || (r.y0 >= r.y1) || (nscss_color_is_transparent(bgcol) == false) || ((clip_box->background != NULL) && content_get_opaque(clip_box->background))) continue; } /* plot the background colour */ css_computed_background_color(background->style, &bgcol); if (nscss_color_is_transparent(bgcol) == false) { *background_colour = nscss_color_to_ns(bgcol); pstyle_fill_bg.fill_colour = *background_colour; if (plot_colour) 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 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 */ 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 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 */ 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; } /** * 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 */ 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 */ 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; }