/* * Copyright 2006 Richard Wilson * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file * Knockout rendering implementation. * * Knockout rendering is an optimisation which is particularly for * unaccelerated screen redraw. It tries to avoid plotting the same area more * than once. * * If the object is to plot two overlapping rectangles (one large, one small), * such as: * * +-----------------+ * |#################| * |####+-------+####| * |####|:::::::|####| * |####|:::::::|####| * |####|:::::::|####| * |####+-------+####| * |#################| * +-----------------+ * * Without knockout rendering we plot the bottom rectangle and then the top one: * * +-----------------+ +-----------------+ * |#################| |#################| * |#################| |####+-------+####| * |#################| |####|:::::::|####| * |#################| and then, |####|:::::::|####| * |#################| |####|:::::::|####| * |#################| |####+-------+####| * |#################| |#################| * +-----------------+ +-----------------+ * * With knockout rendering, the bottom rectangle is split up into smaller * ones and each pixel is just plotted once: * * +-----------------+ * |#################| * +----+-------+----+ * |####|:::::::|####| * |####|:::::::|####| * |####|:::::::|####| * +----+-------+----+ * |#################| * +-----------------+ */ #include #include #include #include "utils/utils.h" #include "utils/log.h" #include "utils/errors.h" #include "netsurf/bitmap.h" #include "content/content.h" #include "netsurf/plotters.h" #include "desktop/gui_internal.h" #include "desktop/knockout.h" /* Define to enable knockout debug */ #undef KNOCKOUT_DEBUG #define KNOCKOUT_ENTRIES 3072 /* 40 bytes each */ #define KNOCKOUT_BOXES 768 /* 28 bytes each */ #define KNOCKOUT_POLYGONS 3072 /* 4 bytes each */ struct knockout_box; struct knockout_entry; typedef enum { KNOCKOUT_PLOT_RECTANGLE, KNOCKOUT_PLOT_LINE, KNOCKOUT_PLOT_POLYGON, KNOCKOUT_PLOT_FILL, /* knockout, knocked out */ KNOCKOUT_PLOT_CLIP, KNOCKOUT_PLOT_TEXT, KNOCKOUT_PLOT_DISC, KNOCKOUT_PLOT_ARC, KNOCKOUT_PLOT_BITMAP, /* knockout, knocked out */ KNOCKOUT_PLOT_GROUP_START, KNOCKOUT_PLOT_GROUP_END, } knockout_type; struct knockout_box { struct rect bbox; bool deleted; /* box has been deleted, ignore */ struct knockout_box *child; struct knockout_box *next; }; struct knockout_entry { knockout_type type; struct knockout_box *box; /* relating series of knockout clips */ union { struct { struct rect r; plot_style_t plot_style; } rectangle; struct { struct rect l; plot_style_t plot_style; } line; struct { int *p; unsigned int n; plot_style_t plot_style; } polygon; struct { struct rect r; plot_style_t plot_style; } fill; struct rect clip; struct { int x; int y; const char *text; size_t length; plot_font_style_t font_style; } text; struct { int x; int y; int radius; plot_style_t plot_style; } disc; struct { int x; int y; int radius; int angle1; int angle2; plot_style_t plot_style; } arc; struct { int x; int y; int width; int height; struct bitmap *bitmap; colour bg; bitmap_flags_t flags; } bitmap; struct { const char *name; } group_start; } data; }; static struct knockout_entry knockout_entries[KNOCKOUT_ENTRIES]; static struct knockout_box knockout_boxes[KNOCKOUT_BOXES]; static int knockout_polygons[KNOCKOUT_POLYGONS]; static int knockout_entry_cur = 0; static int knockout_box_cur = 0; static int knockout_polygon_cur = 0; static struct knockout_box *knockout_list = NULL; static struct plotter_table real_plot; static struct rect clip_cur; static int nested_depth = 0; /** * fill an area recursively */ static nserror knockout_plot_fill_recursive(const struct redraw_context *ctx, struct knockout_box *box, plot_style_t *plot_style) { struct knockout_box *parent; nserror res; nserror ffres = NSERROR_OK; /* first failing result */ for (parent = box; parent; parent = parent->next) { if (parent->deleted) continue; if (parent->child) { res = knockout_plot_fill_recursive(ctx, parent->child, plot_style); } else { res = real_plot.rectangle(ctx, plot_style, &parent->bbox); } /* remember the first error */ if ((res != NSERROR_OK) && (ffres == NSERROR_OK)) { ffres = res; } } return ffres; } /** * bitmap plot recusivley */ static nserror knockout_plot_bitmap_recursive(const struct redraw_context *ctx, struct knockout_box *box, struct knockout_entry *entry) { nserror res; nserror ffres = NSERROR_OK; /* first failing result */ struct knockout_box *parent; for (parent = box; parent; parent = parent->next) { if (parent->deleted) continue; if (parent->child) { res = knockout_plot_bitmap_recursive(ctx, parent->child, entry); } else { real_plot.clip(ctx, &parent->bbox); res = real_plot.bitmap(ctx, entry->data.bitmap.bitmap, entry->data.bitmap.x, entry->data.bitmap.y, entry->data.bitmap.width, entry->data.bitmap.height, entry->data.bitmap.bg, entry->data.bitmap.flags); } /* remember the first error */ if ((res != NSERROR_OK) && (ffres == NSERROR_OK)) { ffres = res; } } return ffres; } /** * Flush the current knockout session to empty the buffers * * \return true on success, false otherwise */ static nserror knockout_plot_flush(const struct redraw_context *ctx) { int i; struct knockout_box *box; nserror res = NSERROR_OK; /* operation result */ nserror ffres = NSERROR_OK; /* first failing result */ /* debugging information */ #ifdef KNOCKOUT_DEBUG LOG("Entries are %i/%i, %i/%i, %i/%i", knockout_entry_cur, KNOCKOUT_ENTRIES, knockout_box_cur, KNOCKOUT_BOXES, knockout_polygon_cur, KNOCKOUT_POLYGONS); #endif for (i = 0; i < knockout_entry_cur; i++) { switch (knockout_entries[i].type) { case KNOCKOUT_PLOT_RECTANGLE: res = real_plot.rectangle(ctx, &knockout_entries[i].data.rectangle.plot_style, &knockout_entries[i].data.rectangle.r); break; case KNOCKOUT_PLOT_LINE: res = real_plot.line(ctx, &knockout_entries[i].data.line.plot_style, &knockout_entries[i].data.line.l); break; case KNOCKOUT_PLOT_POLYGON: res = real_plot.polygon(ctx, &knockout_entries[i].data.polygon.plot_style, knockout_entries[i].data.polygon.p, knockout_entries[i].data.polygon.n); break; case KNOCKOUT_PLOT_FILL: box = knockout_entries[i].box->child; if (box) { res = knockout_plot_fill_recursive(ctx, box, &knockout_entries[i].data.fill.plot_style); } else if (!knockout_entries[i].box->deleted) { res = real_plot.rectangle(ctx, &knockout_entries[i].data.fill.plot_style, &knockout_entries[i].data.fill.r); } break; case KNOCKOUT_PLOT_CLIP: res = real_plot.clip(ctx, &knockout_entries[i].data.clip); break; case KNOCKOUT_PLOT_TEXT: res = real_plot.text(ctx, &knockout_entries[i].data.text.font_style, knockout_entries[i].data.text.x, knockout_entries[i].data.text.y, knockout_entries[i].data.text.text, knockout_entries[i].data.text.length); break; case KNOCKOUT_PLOT_DISC: res = real_plot.disc(ctx, &knockout_entries[i].data.disc.plot_style, knockout_entries[i].data.disc.x, knockout_entries[i].data.disc.y, knockout_entries[i].data.disc.radius); break; case KNOCKOUT_PLOT_ARC: res = real_plot.arc(ctx, &knockout_entries[i].data.arc.plot_style, knockout_entries[i].data.arc.x, knockout_entries[i].data.arc.y, knockout_entries[i].data.arc.radius, knockout_entries[i].data.arc.angle1, knockout_entries[i].data.arc.angle2); break; case KNOCKOUT_PLOT_BITMAP: box = knockout_entries[i].box->child; if (box) { res = knockout_plot_bitmap_recursive(ctx, box, &knockout_entries[i]); } else if (!knockout_entries[i].box->deleted) { res = real_plot.bitmap(ctx, knockout_entries[i].data.bitmap.bitmap, knockout_entries[i].data.bitmap.x, knockout_entries[i].data.bitmap.y, knockout_entries[i].data.bitmap.width, knockout_entries[i].data.bitmap.height, knockout_entries[i].data.bitmap.bg, knockout_entries[i].data.bitmap.flags); } break; case KNOCKOUT_PLOT_GROUP_START: res = real_plot.group_start(ctx, knockout_entries[i].data.group_start.name); break; case KNOCKOUT_PLOT_GROUP_END: res = real_plot.group_end(ctx); break; } /* remember the first error */ if ((res != NSERROR_OK) && (ffres == NSERROR_OK)) { ffres = res; } } knockout_entry_cur = 0; knockout_box_cur = 0; knockout_polygon_cur = 0; knockout_list = NULL; return ffres; } /** * Knockout a section of previous rendering * * \param ctx The current redraw context. * \param x0 The left edge of the removal box * \param y0 The bottom edge of the removal box * \param x1 The right edge of the removal box * \param y1 The top edge of the removal box * \param owner The parent box set to consider, or NULL for top level */ static void knockout_calculate(const struct redraw_context *ctx, int x0, int y0, int x1, int y1, struct knockout_box *owner) { struct knockout_box *box; struct knockout_box *parent; struct knockout_box *prev = NULL; int nx0, ny0, nx1, ny1; if (owner == NULL) box = knockout_list; else box = owner->child; for (parent = box; parent; parent = parent->next) { /* permanently delink deleted nodes */ if (parent->deleted) { if (prev) { /* not the first valid element: just skip future */ prev->next = parent->next; } else { if (owner) { /* first valid element: update child reference */ owner->child = parent->next; /* have we deleted all child nodes? */ if (!owner->child) owner->deleted = true; } else { /* we are the head of the list */ knockout_list = parent->next; } } continue; } else { prev = parent; } /* get the parent dimensions */ nx0 = parent->bbox.x0; ny0 = parent->bbox.y0; nx1 = parent->bbox.x1; ny1 = parent->bbox.y1; /* reject non-overlapping boxes */ if ((nx0 >= x1) || (nx1 <= x0) || (ny0 >= y1) || (ny1 <= y0)) continue; /* check for a total knockout */ if ((x0 <= nx0) && (x1 >= nx1) && (y0 <= ny0) && (y1 >= ny1)) { parent->deleted = true; continue; } /* has the box been replaced by children? */ if (parent->child) { knockout_calculate(ctx, x0, y0, x1, y1, parent); } else { /* we need a maximum of 4 child boxes */ if (knockout_box_cur + 4 >= KNOCKOUT_BOXES) { knockout_plot_flush(ctx); return; } /* clip top */ if (y1 < ny1) { knockout_boxes[knockout_box_cur].bbox.x0 = nx0; knockout_boxes[knockout_box_cur].bbox.y0 = y1; knockout_boxes[knockout_box_cur].bbox.x1 = nx1; knockout_boxes[knockout_box_cur].bbox.y1 = ny1; knockout_boxes[knockout_box_cur].deleted = false; knockout_boxes[knockout_box_cur].child = NULL; knockout_boxes[knockout_box_cur].next = parent->child; parent->child = &knockout_boxes[knockout_box_cur++]; ny1 = y1; } /* clip bottom */ if (y0 > ny0) { knockout_boxes[knockout_box_cur].bbox.x0 = nx0; knockout_boxes[knockout_box_cur].bbox.y0 = ny0; knockout_boxes[knockout_box_cur].bbox.x1 = nx1; knockout_boxes[knockout_box_cur].bbox.y1 = y0; knockout_boxes[knockout_box_cur].deleted = false; knockout_boxes[knockout_box_cur].child = NULL; knockout_boxes[knockout_box_cur].next = parent->child; parent->child = &knockout_boxes[knockout_box_cur++]; ny0 = y0; } /* clip right */ if (x1 < nx1) { knockout_boxes[knockout_box_cur].bbox.x0 = x1; knockout_boxes[knockout_box_cur].bbox.y0 = ny0; knockout_boxes[knockout_box_cur].bbox.x1 = nx1; knockout_boxes[knockout_box_cur].bbox.y1 = ny1; knockout_boxes[knockout_box_cur].deleted = false; knockout_boxes[knockout_box_cur].child = NULL; knockout_boxes[knockout_box_cur].next = parent->child; parent->child = &knockout_boxes[knockout_box_cur++]; /* nx1 isn't used again, but if it was it would * need to be updated to x1 here. */ } /* clip left */ if (x0 > nx0) { knockout_boxes[knockout_box_cur].bbox.x0 = nx0; knockout_boxes[knockout_box_cur].bbox.y0 = ny0; knockout_boxes[knockout_box_cur].bbox.x1 = x0; knockout_boxes[knockout_box_cur].bbox.y1 = ny1; knockout_boxes[knockout_box_cur].deleted = false; knockout_boxes[knockout_box_cur].child = NULL; knockout_boxes[knockout_box_cur].next = parent->child; parent->child = &knockout_boxes[knockout_box_cur++]; /* nx0 isn't used again, but if it was it would * need to be updated to x0 here. */ } } } } /** * knockout rectangle plotting. * * The rectangle can be filled an outline or both controlled * by the plot style The line can be solid, dotted or * dashed. Top left corner at (x0,y0) and rectangle has given * width and height. * * \param ctx The current redraw context. * \param pstyle Style controlling the rectangle plot. * \param rect A rectangle defining the line to be drawn * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_rectangle(const struct redraw_context *ctx, const plot_style_t *pstyle, const struct rect *rect) { int kx0, ky0, kx1, ky1; nserror res = NSERROR_OK; if (pstyle->fill_type != PLOT_OP_TYPE_NONE) { /* filled draw */ /* get our bounds */ kx0 = (rect->x0 > clip_cur.x0) ? rect->x0 : clip_cur.x0; ky0 = (rect->y0 > clip_cur.y0) ? rect->y0 : clip_cur.y0; kx1 = (rect->x1 < clip_cur.x1) ? rect->x1 : clip_cur.x1; ky1 = (rect->y1 < clip_cur.y1) ? rect->y1 : clip_cur.y1; if ((kx0 > clip_cur.x1) || (kx1 < clip_cur.x0) || (ky0 > clip_cur.y1) || (ky1 < clip_cur.y0)) { return NSERROR_OK; } /* fills both knock out and get knocked out */ knockout_calculate(ctx, kx0, ky0, kx1, ky1, NULL); knockout_boxes[knockout_box_cur].bbox = *rect; knockout_boxes[knockout_box_cur].deleted = false; knockout_boxes[knockout_box_cur].child = NULL; knockout_boxes[knockout_box_cur].next = knockout_list; knockout_list = &knockout_boxes[knockout_box_cur]; knockout_entries[knockout_entry_cur].box = &knockout_boxes[knockout_box_cur]; knockout_entries[knockout_entry_cur].data.fill.r = *rect; knockout_entries[knockout_entry_cur].data.fill.plot_style = *pstyle; knockout_entries[knockout_entry_cur].data.fill.plot_style.stroke_type = PLOT_OP_TYPE_NONE; /* ensure we only plot the fill */ knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_FILL; if ((++knockout_entry_cur >= KNOCKOUT_ENTRIES) || (++knockout_box_cur >= KNOCKOUT_BOXES)) { res = knockout_plot_flush(ctx); } } if (pstyle->stroke_type != PLOT_OP_TYPE_NONE) { /* draw outline */ knockout_entries[knockout_entry_cur].data.rectangle.r = *rect; knockout_entries[knockout_entry_cur].data.fill.plot_style = *pstyle; knockout_entries[knockout_entry_cur].data.fill.plot_style.fill_type = PLOT_OP_TYPE_NONE; /* ensure we only plot the outline */ knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_RECTANGLE; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { res = knockout_plot_flush(ctx); } } return res; } /** * Knockout line plotting. * * plot a line from (x0,y0) to (x1,y1). Coordinates are at * centre of line width/thickness. * * \param ctx The current redraw context. * \param pstyle Style controlling the line plot. * \param line A rectangle defining the line to be drawn * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_line(const struct redraw_context *ctx, const plot_style_t *pstyle, const struct rect *line) { knockout_entries[knockout_entry_cur].data.line.l = *line; knockout_entries[knockout_entry_cur].data.line.plot_style = *pstyle; knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_LINE; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { return knockout_plot_flush(ctx); } return NSERROR_OK; } /** * Knockout polygon plotting. * * Plots a filled polygon with straight lines between * points. The lines around the edge of the ploygon are not * plotted. The polygon is filled with the non-zero winding * rule. * * \param ctx The current redraw context. * \param pstyle Style controlling the polygon plot. * \param p verticies of polygon * \param n number of verticies. * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_polygon(const struct redraw_context *ctx, const plot_style_t *pstyle, const int *p, unsigned int n) { int *dest; nserror res = NSERROR_OK; nserror ffres = NSERROR_OK; /* ensure we have sufficient room even when flushed */ if (n * 2 >= KNOCKOUT_POLYGONS) { ffres = knockout_plot_flush(ctx); res = real_plot.polygon(ctx, pstyle, p, n); /* return the first error */ if ((res != NSERROR_OK) && (ffres == NSERROR_OK)) { ffres = res; } return ffres; } /* ensure we have enough room right now */ if (knockout_polygon_cur + n * 2 >= KNOCKOUT_POLYGONS) { ffres = knockout_plot_flush(ctx); } /* copy our data */ dest = &(knockout_polygons[knockout_polygon_cur]); memcpy(dest, p, n * 2 * sizeof(int)); knockout_polygon_cur += n * 2; knockout_entries[knockout_entry_cur].data.polygon.p = dest; knockout_entries[knockout_entry_cur].data.polygon.n = n; knockout_entries[knockout_entry_cur].data.polygon.plot_style = *pstyle; knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_POLYGON; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { res = knockout_plot_flush(ctx); } /* return the first error */ if ((res != NSERROR_OK) && (ffres == NSERROR_OK)) { ffres = res; } return ffres; } /** * knockout path plotting. * * The knockout implementation simply flushes the queue and plots the path * directly using real plotter. * * \param ctx The current redraw context. * \param pstyle Style controlling the path plot. * \param p elements of path * \param n nunber of elements on path * \param width The width of the path * \param transform A transform to apply to the path. * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_path(const struct redraw_context *ctx, const plot_style_t *pstyle, const float *p, unsigned int n, float width, const float transform[6]) { nserror res; nserror ffres; ffres = knockout_plot_flush(ctx); res = real_plot.path(ctx, pstyle, p, n, width, transform); /* return the first error */ if ((res != NSERROR_OK) && (ffres == NSERROR_OK)) { ffres = res; } return ffres; } static nserror knockout_plot_clip(const struct redraw_context *ctx, const struct rect *clip) { nserror res = NSERROR_OK; if (clip->x1 < clip->x0 || clip->y0 > clip->y1) { #ifdef KNOCKOUT_DEBUG LOG("bad clip rectangle %i %i %i %i", clip->x0, clip->y0, clip->x1, clip->y1); #endif return NSERROR_BAD_SIZE; } /* memorise clip for bitmap tiling */ clip_cur = *clip; knockout_entries[knockout_entry_cur].data.clip = *clip; knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_CLIP; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { res = knockout_plot_flush(ctx); } return res; } /** * Text plotting. * * \param ctx The current redraw context. * \param fstyle plot style for this text * \param x x coordinate * \param y y coordinate * \param text UTF-8 string to plot * \param length length of string, in bytes * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_text(const struct redraw_context *ctx, const plot_font_style_t *fstyle, int x, int y, const char *text, size_t length) { nserror res = NSERROR_OK; knockout_entries[knockout_entry_cur].data.text.x = x; knockout_entries[knockout_entry_cur].data.text.y = y; knockout_entries[knockout_entry_cur].data.text.text = text; knockout_entries[knockout_entry_cur].data.text.length = length; knockout_entries[knockout_entry_cur].data.text.font_style = *fstyle; knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_TEXT; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { res = knockout_plot_flush(ctx); } return res; } /** * knockout circle plotting * * Plot a circle centered on (x,y), which is optionally filled. * * \param ctx The current redraw context. * \param pstyle Style controlling the circle plot. * \param x x coordinate of circle centre. * \param y y coordinate of circle centre. * \param radius circle radius. * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_disc(const struct redraw_context *ctx, const plot_style_t *pstyle, int x, int y, int radius) { nserror res = NSERROR_OK; knockout_entries[knockout_entry_cur].data.disc.x = x; knockout_entries[knockout_entry_cur].data.disc.y = y; knockout_entries[knockout_entry_cur].data.disc.radius = radius; knockout_entries[knockout_entry_cur].data.disc.plot_style = *pstyle; knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_DISC; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { res = knockout_plot_flush(ctx); } return res; } /** * Plots an arc * * plot an arc segment around (x,y), anticlockwise from angle1 * to angle2. Angles are measured anticlockwise from * horizontal, in degrees. * * \param ctx The current redraw context. * \param pstyle Style controlling the arc plot. * \param x The x coordinate of the arc. * \param y The y coordinate of the arc. * \param radius The radius of the arc. * \param angle1 The start angle of the arc. * \param angle2 The finish angle of the arc. * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_arc(const struct redraw_context *ctx, const plot_style_t *pstyle, int x, int y, int radius, int angle1, int angle2) { nserror res = NSERROR_OK; knockout_entries[knockout_entry_cur].data.arc.x = x; knockout_entries[knockout_entry_cur].data.arc.y = y; knockout_entries[knockout_entry_cur].data.arc.radius = radius; knockout_entries[knockout_entry_cur].data.arc.angle1 = angle1; knockout_entries[knockout_entry_cur].data.arc.angle2 = angle2; knockout_entries[knockout_entry_cur].data.arc.plot_style = *pstyle; knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_ARC; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { res = knockout_plot_flush(ctx); } return res; } /** * knockout bitmap plotting. * * Tiled plot of a bitmap image. (x,y) gives the top left * coordinate of an explicitly placed tile. From this tile the * image can repeat in all four directions -- up, down, left * and right -- to the extents given by the current clip * rectangle. * * The bitmap_flags say whether to tile in the x and y * directions. If not tiling in x or y directions, the single * image is plotted. The width and height give the dimensions * the image is to be scaled to. * * \param ctx The current redraw context. * \param bitmap The bitmap to plot * \param x The x coordinate to plot the bitmap * \param y The y coordiante to plot the bitmap * \param width The width of area to plot the bitmap into * \param height The height of area to plot the bitmap into * \param bg the background colour to alpha blend into * \param flags the flags controlling the type of plot operation * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_bitmap(const struct redraw_context *ctx, struct bitmap *bitmap, int x, int y, int width, int height, colour bg, bitmap_flags_t flags) { int kx0, ky0, kx1, ky1; nserror res; nserror ffres = NSERROR_OK; /* get our bounds */ kx0 = clip_cur.x0; ky0 = clip_cur.y0; kx1 = clip_cur.x1; ky1 = clip_cur.y1; if (!(flags & BITMAPF_REPEAT_X)) { if (x > kx0) kx0 = x; if (x + width < kx1) kx1 = x + width; if ((kx0 > clip_cur.x1) || (kx1 < clip_cur.x0)) return NSERROR_OK; } if (!(flags & BITMAPF_REPEAT_Y)) { if (y > ky0) ky0 = y; if (y + height < ky1) ky1 = y + height; if ((ky0 > clip_cur.y1) || (ky1 < clip_cur.y0)) return NSERROR_OK; } /* tiled bitmaps both knock out and get knocked out */ if (guit->bitmap->get_opaque(bitmap)) { knockout_calculate(ctx, kx0, ky0, kx1, ky1, NULL); } knockout_boxes[knockout_box_cur].bbox.x0 = kx0; knockout_boxes[knockout_box_cur].bbox.y0 = ky0; knockout_boxes[knockout_box_cur].bbox.x1 = kx1; knockout_boxes[knockout_box_cur].bbox.y1 = ky1; knockout_boxes[knockout_box_cur].deleted = false; knockout_boxes[knockout_box_cur].child = NULL; knockout_boxes[knockout_box_cur].next = knockout_list; knockout_list = &knockout_boxes[knockout_box_cur]; knockout_entries[knockout_entry_cur].box = &knockout_boxes[knockout_box_cur]; knockout_entries[knockout_entry_cur].data.bitmap.x = x; knockout_entries[knockout_entry_cur].data.bitmap.y = y; knockout_entries[knockout_entry_cur].data.bitmap.width = width; knockout_entries[knockout_entry_cur].data.bitmap.height = height; knockout_entries[knockout_entry_cur].data.bitmap.bitmap = bitmap; knockout_entries[knockout_entry_cur].data.bitmap.bg = bg; knockout_entries[knockout_entry_cur].data.bitmap.flags = flags; knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_BITMAP; if ((++knockout_entry_cur >= KNOCKOUT_ENTRIES) || (++knockout_box_cur >= KNOCKOUT_BOXES)) { ffres = knockout_plot_flush(ctx); } res = knockout_plot_clip(ctx, &clip_cur); /* return the first error */ if ((res != NSERROR_OK) && (ffres == NSERROR_OK)) { ffres = res; } return ffres; } /** * Start of a group of objects. * * Used when plotter implements export to a vector graphics file format. * * \param ctx The current redraw context. * \param name The name of the group being started. * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_group_start(const struct redraw_context *ctx, const char *name) { if (real_plot.group_start == NULL) { return NSERROR_OK; } knockout_entries[knockout_entry_cur].data.group_start.name = name; knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_GROUP_START; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { return knockout_plot_flush(ctx); } return NSERROR_OK; } /** * End a group of objects. * * Used when plotter implements export to a vector graphics file format. * * \param ctx The current redraw context. * \return NSERROR_OK on success else error code. */ static nserror knockout_plot_group_end(const struct redraw_context *ctx) { if (real_plot.group_end == NULL) { return NSERROR_OK; } knockout_entries[knockout_entry_cur].type = KNOCKOUT_PLOT_GROUP_END; if (++knockout_entry_cur >= KNOCKOUT_ENTRIES) { return knockout_plot_flush(ctx); } return NSERROR_OK; } /* exported functions documented in desktop/knockout.h */ bool knockout_plot_start(const struct redraw_context *ctx, struct redraw_context *knk_ctx) { /* check if we're recursing */ if (nested_depth++ > 0) { /* we should already have the knockout renderer as default */ assert(ctx->plot->rectangle == knockout_plotters.rectangle); *knk_ctx = *ctx; return true; } /* end any previous sessions */ if (knockout_entry_cur > 0) knockout_plot_end(ctx); /* get copy of real plotter table */ real_plot = *(ctx->plot); /* set up knockout rendering context */ *knk_ctx = *ctx; knk_ctx->plot = &knockout_plotters; return true; } /* exported functions documented in desktop/knockout.h */ bool knockout_plot_end(const struct redraw_context *ctx) { /* only output when we've finished any nesting */ if (--nested_depth == 0) { return knockout_plot_flush(ctx); } assert(nested_depth > 0); return true; } /** * knockout plotter operation table */ const struct plotter_table knockout_plotters = { .rectangle = knockout_plot_rectangle, .line = knockout_plot_line, .polygon = knockout_plot_polygon, .clip = knockout_plot_clip, .text = knockout_plot_text, .disc = knockout_plot_disc, .arc = knockout_plot_arc, .bitmap = knockout_plot_bitmap, .group_start = knockout_plot_group_start, .group_end = knockout_plot_group_end, .flush = knockout_plot_flush, .path = knockout_plot_path, .option_knockout = true, };