diff options
Diffstat (limited to 'base/gdevbbox.c')
-rw-r--r-- | base/gdevbbox.c | 1312 |
1 files changed, 1312 insertions, 0 deletions
diff --git a/base/gdevbbox.c b/base/gdevbbox.c new file mode 100644 index 000000000..77832d5f6 --- /dev/null +++ b/base/gdevbbox.c @@ -0,0 +1,1312 @@ +/* Copyright (C) 2001-2012 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, + modified or distributed except as expressly authorized under the terms + of the license contained in the file LICENSE in this distribution. + + Refer to licensing information at http://www.artifex.com or contact + Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134, San Rafael, + CA 94903, U.S.A., +1(415)492-9861, for further information. +*/ + +/* Device for tracking bounding box */ +#include "math_.h" +#include "memory_.h" +#include "gx.h" +#include "gserrors.h" +#include "gsparam.h" +#include "gxdevice.h" +#include "gsdevice.h" /* requires gsmatrix.h */ +#include "gdevbbox.h" +#include "gxdcolor.h" /* for gx_device_black/white */ +#include "gxiparam.h" /* for image source size */ +#include "gxistate.h" +#include "gxpaint.h" +#include "gxpath.h" +#include "gxcpath.h" + +#include "gdevkrnlsclass.h" /* 'standard' built in subclasses, currently First/Last Page and object filter */ + +/* GC descriptor */ +public_st_device_bbox(); + +/* Device procedures */ +static dev_proc_open_device(bbox_open_device); +static dev_proc_close_device(bbox_close_device); +static dev_proc_output_page(bbox_output_page); +static dev_proc_fill_rectangle(bbox_fill_rectangle); +static dev_proc_copy_mono(bbox_copy_mono); +static dev_proc_copy_color(bbox_copy_color); +static dev_proc_get_params(bbox_get_params); +static dev_proc_put_params(bbox_put_params); +static dev_proc_copy_alpha(bbox_copy_alpha); +static dev_proc_fill_path(bbox_fill_path); +static dev_proc_stroke_path(bbox_stroke_path); +static dev_proc_fill_mask(bbox_fill_mask); +static dev_proc_fill_trapezoid(bbox_fill_trapezoid); +static dev_proc_fill_parallelogram(bbox_fill_parallelogram); +static dev_proc_fill_triangle(bbox_fill_triangle); +static dev_proc_draw_thin_line(bbox_draw_thin_line); +static dev_proc_strip_tile_rectangle(bbox_strip_tile_rectangle); +static dev_proc_strip_copy_rop(bbox_strip_copy_rop); +static dev_proc_strip_copy_rop2(bbox_strip_copy_rop2); +static dev_proc_strip_tile_rect_devn(bbox_strip_tile_rect_devn); +static dev_proc_begin_typed_image(bbox_begin_typed_image); +static dev_proc_create_compositor(bbox_create_compositor); +static dev_proc_text_begin(bbox_text_begin); +static dev_proc_fillpage(bbox_fillpage); + +/* The device prototype */ +/* + * Normally this would be static, but if the device is going to be used + * stand-alone, it has to be public. + */ +/*static*/ const +/* + * The bbox device sets the resolution to some value R (currently 4000), and + * the page size in device pixels to slightly smaller than the largest + * representable values (around 500K), leaving a little room for stroke + * widths, rounding, etc. If an input file (or the command line) resets the + * resolution to a value R' > R, the page size in pixels will get multiplied + * by R'/R, and will thereby exceed the representable range, causing a + * limitcheck. That is why the bbox device must set the resolution to a + * value larger than that of any real device. A consequence of this is that + * the page size in inches is limited to the maximum representable pixel + * size divided by R, which gives a limit of about 120" in each dimension. + */ +#define MAX_COORD (max_int_in_fixed - 1000) +#define MAX_RESOLUTION 4000 +gx_device_bbox gs_bbox_device = +{ + /* + * Define the device as 8-bit gray scale to avoid computing halftones. + */ + std_device_dci_body(gx_device_bbox, 0, "bbox", + MAX_COORD, MAX_COORD, + MAX_RESOLUTION, MAX_RESOLUTION, + 1, 8, 255, 0, 256, 1), + {bbox_open_device, + gx_upright_get_initial_matrix, + NULL, /* sync_output */ + bbox_output_page, + bbox_close_device, + gx_default_gray_map_rgb_color, + gx_default_gray_map_color_rgb, + bbox_fill_rectangle, + NULL, /* tile_rectangle */ + bbox_copy_mono, + bbox_copy_color, + NULL, /* draw_line */ + NULL, /* get_bits */ + bbox_get_params, + bbox_put_params, + gx_default_map_cmyk_color, + NULL, /* get_xfont_procs */ + NULL, /* get_xfont_device */ + gx_default_map_rgb_alpha_color, + gx_page_device_get_page_device, + NULL, /* get_alpha_bits */ + bbox_copy_alpha, + NULL, /* get_band */ + NULL, /* copy_rop */ + bbox_fill_path, + bbox_stroke_path, + bbox_fill_mask, + bbox_fill_trapezoid, + bbox_fill_parallelogram, + bbox_fill_triangle, + bbox_draw_thin_line, + gx_default_begin_image, + NULL, /* image_data */ + NULL, /* end_image */ + bbox_strip_tile_rectangle, + bbox_strip_copy_rop, + NULL, /* get_clipping_box */ + bbox_begin_typed_image, + NULL, /* get_bits_rectangle */ + gx_default_map_color_rgb_alpha, + bbox_create_compositor, + NULL, /* get_hardware_params */ + bbox_text_begin, + NULL, /* finish_copydevice */ + NULL, /* begin_transparency_group */ + NULL, /* end_transparency_group */ + NULL, /* begin_transparency_mask */ + NULL, /* end_transparency_mask */ + NULL, /* discard_transparency_layer */ + NULL, /* get_color_mapping_procs */ + NULL, /* get_color_comp_index */ + NULL, /* encode_color */ + NULL, /* decode_color */ + NULL, /* pattern_manage */ + NULL, /* fill_rectangle_hl_color */ + NULL, /* include_color_space */ + NULL, /* fill_linear_color_scanline */ + NULL, /* fill_linear_color_trapezoid */ + NULL, /* fill_linear_color_triangle */ + NULL, /* update_spot_equivalent_colors */ + NULL, /* ret_devn_params */ + bbox_fillpage, /* fillpage */ + NULL, /* push_transparency_state */ + NULL, /* pop_transparency_state */ + NULL, /* put_image */ + NULL, /* dev_spec_op */ + NULL, /* copy_planes */ + NULL, /* get_profile */ + NULL, /* set_graphics_type_tag */ + bbox_strip_copy_rop2, + bbox_strip_tile_rect_devn + }, + 0, /* target */ + 1, /*true *//* free_standing */ + 1 /*true *//* forward_open_close */ +}; + +#undef MAX_COORD +#undef MAX_RESOLUTION + +/* Default box procedures */ + +bool +bbox_default_init_box(void *pdata) +{ + gx_device_bbox *const bdev = (gx_device_bbox *)pdata; + gs_fixed_rect *const pr = &bdev->bbox; + + pr->p.x = pr->p.y = max_fixed; + pr->q.x = pr->q.y = min_fixed; + return bdev->white != bdev->transparent; +} +#define BBOX_INIT_BOX(bdev)\ + bdev->box_procs.init_box(bdev->box_proc_data) + +void +bbox_default_get_box(const void *pdata, gs_fixed_rect *pbox) +{ + const gx_device_bbox *const bdev = (const gx_device_bbox *)pdata; + + *pbox = bdev->bbox; +} +#define BBOX_GET_BOX(bdev, pbox)\ + bdev->box_procs.get_box(bdev->box_proc_data, pbox); + +void +bbox_default_add_rect(void *pdata, fixed x0, fixed y0, fixed x1, fixed y1) +{ + gx_device_bbox *const bdev = (gx_device_bbox *)pdata; + gs_fixed_rect *const pr = &bdev->bbox; + + if (x0 < pr->p.x) + pr->p.x = x0; + if (y0 < pr->p.y) + pr->p.y = y0; + if (x1 > pr->q.x) + pr->q.x = x1; + if (y1 > pr->q.y) + pr->q.y = y1; +} +#define BBOX_ADD_RECT(bdev, x0, y0, x1, y1)\ + bdev->box_procs.add_rect(bdev->box_proc_data, x0, y0, x1, y1) +#define BBOX_ADD_INT_RECT(bdev, x0, y0, x1, y1)\ + BBOX_ADD_RECT(bdev, int2fixed(x0), int2fixed(y0), int2fixed(x1),\ + int2fixed(y1)) + +bool +bbox_default_in_rect(const void *pdata, const gs_fixed_rect *pbox) +{ + const gx_device_bbox *const bdev = (const gx_device_bbox *)pdata; + + return rect_within(*pbox, bdev->bbox); +} +#define BBOX_IN_RECT(bdev, pbox)\ + bdev->box_procs.in_rect(bdev->box_proc_data, pbox) + +static const gx_device_bbox_procs_t box_procs_default = { + bbox_default_init_box, bbox_default_get_box, bbox_default_add_rect, + bbox_default_in_rect +}; + +/* ---------------- Open/close/page ---------------- */ + +/* Copy device parameters back from the target. */ +static void +bbox_copy_params(gx_device_bbox * bdev, bool remap_colors) +{ + gx_device *tdev = bdev->target; + + if (tdev != 0) + gx_device_copy_params((gx_device *)bdev, tdev); + if (remap_colors) { + bdev->black = gx_device_black((gx_device *)bdev); + bdev->white = gx_device_white((gx_device *)bdev); + bdev->transparent = + (bdev->white_is_opaque ? gx_no_color_index : bdev->white); + } +} + +#define GX_DC_IS_TRANSPARENT(pdevc, bdev)\ + (gx_dc_is_pure(pdevc) && gx_dc_pure_color(pdevc) == (bdev)->transparent) + +static int +bbox_close_device(gx_device * dev) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gx_device *tdev = bdev->target; + + if (bdev->box_procs.init_box != box_procs_default.init_box) { + /* + * This device was created as a wrapper for a compositor. + * Just free the devices. + */ + int code = (tdev && bdev->forward_open_close ? gs_closedevice(tdev) : 0); + + gs_free_object(dev->memory, dev, "bbox_close_device(composite)"); + return code; + } else { + return (tdev && bdev->forward_open_close ? gs_closedevice(tdev) : 0); + } +} + +/* Initialize a bounding box device. */ +void +gx_device_bbox_init(gx_device_bbox * dev, gx_device * target, gs_memory_t *mem) +{ + gx_device_init((gx_device *) dev, (const gx_device *)&gs_bbox_device, + (target ? target->memory : mem), true); + if (target) { + gx_device_forward_fill_in_procs((gx_device_forward *) dev); + set_dev_proc(dev, get_initial_matrix, gx_forward_get_initial_matrix); + set_dev_proc(dev, map_rgb_color, gx_forward_map_rgb_color); + set_dev_proc(dev, map_color_rgb, gx_forward_map_color_rgb); + set_dev_proc(dev, map_cmyk_color, gx_forward_map_cmyk_color); + set_dev_proc(dev, map_rgb_alpha_color, gx_forward_map_rgb_alpha_color); + set_dev_proc(dev, get_color_mapping_procs, gx_forward_get_color_mapping_procs); + set_dev_proc(dev, get_color_comp_index, gx_forward_get_color_comp_index); + set_dev_proc(dev, encode_color, gx_forward_encode_color); + set_dev_proc(dev, decode_color, gx_forward_decode_color); + set_dev_proc(dev, dev_spec_op, gx_forward_dev_spec_op); + set_dev_proc(dev, fill_rectangle_hl_color, gx_forward_fill_rectangle_hl_color); + set_dev_proc(dev, include_color_space, gx_forward_include_color_space); + set_dev_proc(dev, update_spot_equivalent_colors, + gx_forward_update_spot_equivalent_colors); + set_dev_proc(dev, get_page_device, gx_forward_get_page_device); + set_dev_proc(dev, ret_devn_params, gx_forward_ret_devn_params); + gx_device_set_target((gx_device_forward *)dev, target); + } else { + gx_device_fill_in_procs((gx_device *)dev); + gx_device_forward_fill_in_procs((gx_device_forward *) dev); + } + dev->box_procs = box_procs_default; + dev->box_proc_data = dev; + bbox_copy_params(dev, false); + dev->free_standing = false; /* being used as a component */ +} + +/* Set whether a bounding box device propagates open/close to its target. */ +void +gx_device_bbox_fwd_open_close(gx_device_bbox * dev, bool forward_open_close) +{ + dev->forward_open_close = forward_open_close; +} + +/* Set whether a bounding box device considers white to be opaque. */ +void +gx_device_bbox_set_white_opaque(gx_device_bbox *bdev, bool white_is_opaque) +{ + bdev->white_is_opaque = white_is_opaque; + bdev->transparent = + (bdev->white_is_opaque ? gx_no_color_index : bdev->white); +} + +/* Release a bounding box device. */ +void +gx_device_bbox_release(gx_device_bbox *dev) +{ + /* Just release the reference to the target. */ + gx_device_set_target((gx_device_forward *)dev, NULL); +} + +/* Read back the bounding box in 1/72" units. */ +void +gx_device_bbox_bbox(gx_device_bbox * dev, gs_rect * pbbox) +{ + gs_fixed_rect bbox; + + BBOX_GET_BOX(dev, &bbox); + if (bbox.p.x > bbox.q.x || bbox.p.y > bbox.q.y) { + /* Nothing has been written on this page. */ + pbbox->p.x = pbbox->p.y = pbbox->q.x = pbbox->q.y = 0; + } else { + gs_rect dbox; + gs_matrix mat; + + dbox.p.x = fixed2float(bbox.p.x); + dbox.p.y = fixed2float(bbox.p.y); + dbox.q.x = fixed2float(bbox.q.x); + dbox.q.y = fixed2float(bbox.q.y); + gs_deviceinitialmatrix((gx_device *)dev, &mat); + gs_bbox_transform_inverse(&dbox, &mat, pbbox); + } +} + +static int +bbox_open_device(gx_device * dev) +{ + gx_device_bbox *bdev = (gx_device_bbox *) dev; + int code; + + if (bdev->free_standing) { + gx_device_forward_fill_in_procs((gx_device_forward *) dev); + bdev->box_procs = box_procs_default; + bdev->box_proc_data = bdev; + + code = install_internal_subclass_devices((gx_device **)&bdev, NULL); + if (code < 0) + return code; + } + if (bdev->box_procs.init_box == box_procs_default.init_box) + BBOX_INIT_BOX(bdev); + /* gx_forward_open_device doesn't exist */ + { + gx_device *tdev = bdev->target; + int code = + (tdev && bdev->forward_open_close ? gs_opendevice(tdev) : 0); + + bbox_copy_params(bdev, true); + return code; + } +} + +static int +bbox_output_page(gx_device * dev, int num_copies, int flush) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + + if (bdev->free_standing) { + /* + * This is a free-standing device. Print the page bounding box. + */ + gs_rect bbox; + + gx_device_bbox_bbox(bdev, &bbox); + dmlprintf4(dev->memory, "%%%%BoundingBox: %d %d %d %d\n", + (int)floor(bbox.p.x), (int)floor(bbox.p.y), + (int)ceil(bbox.q.x), (int)ceil(bbox.q.y)); + dmlprintf4(dev->memory, "%%%%HiResBoundingBox: %f %f %f %f\n", + bbox.p.x, bbox.p.y, bbox.q.x, bbox.q.y); + } + return gx_forward_output_page(dev, num_copies, flush); +} + +/* ---------------- Low-level drawing ---------------- */ + +static int +bbox_fill_rectangle(gx_device * dev, int x, int y, int w, int h, + gx_color_index color) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gx_device *tdev = bdev->target; + /* gx_forward_fill_rectangle doesn't exist */ + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, fill_rectangle)(tdev, x, y, w, h, color)); + if (color != bdev->transparent) + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + return code; +} + +static int +bbox_copy_mono(gx_device * dev, const byte * data, + int dx, int raster, gx_bitmap_id id, int x, int y, int w, int h, + gx_color_index zero, gx_color_index one) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* gx_forward_copy_mono doesn't exist */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, copy_mono) + (tdev, data, dx, raster, id, x, y, w, h, zero, one)); + + if ((one != gx_no_color_index && one != bdev->transparent) || + (zero != gx_no_color_index && zero != bdev->transparent) + ) + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + return code; +} + +static int +bbox_copy_color(gx_device * dev, const byte * data, + int dx, int raster, gx_bitmap_id id, int x, int y, int w, int h) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* gx_forward_copy_color doesn't exist */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, copy_color) + (tdev, data, dx, raster, id, x, y, w, h)); + + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + return code; +} + +static int +bbox_copy_alpha(gx_device * dev, const byte * data, int data_x, + int raster, gx_bitmap_id id, int x, int y, int w, int h, + gx_color_index color, int depth) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* gx_forward_copy_alpha doesn't exist */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, copy_alpha) + (tdev, data, data_x, raster, id, x, y, w, h, color, depth)); + + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + return code; +} + +static int +bbox_strip_tile_rectangle(gx_device * dev, const gx_strip_bitmap * tiles, + int x, int y, int w, int h, gx_color_index color0, gx_color_index color1, + int px, int py) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* Skip the call if there is no target. */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, strip_tile_rectangle) + (tdev, tiles, x, y, w, h, color0, color1, px, py)); + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + return code; +} + +static int +bbox_strip_tile_rect_devn(gx_device * dev, const gx_strip_bitmap * tiles, + int x, int y, int w, int h, const gx_drawing_color *pdcolor0, + const gx_drawing_color *pdcolor1, int px, int py) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* Skip the call if there is no target. */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, strip_tile_rect_devn) + (tdev, tiles, x, y, w, h, pdcolor0, pdcolor1, px, py)); + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + return code; +} + +static int +bbox_strip_copy_rop(gx_device * dev, + const byte * sdata, int sourcex, uint sraster, + gx_bitmap_id id, + const gx_color_index * scolors, + const gx_strip_bitmap * textures, + const gx_color_index * tcolors, + int x, int y, int w, int h, + int phase_x, int phase_y, gs_logical_operation_t lop) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* gx_forward_strip_copy_rop doesn't exist */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, strip_copy_rop) + (tdev, sdata, sourcex, sraster, id, scolors, + textures, tcolors, x, y, w, h, phase_x, phase_y, lop)); + + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + return code; +} + +static int +bbox_strip_copy_rop2(gx_device * dev, + const byte * sdata, int sourcex, uint sraster, + gx_bitmap_id id, + const gx_color_index * scolors, + const gx_strip_bitmap * textures, + const gx_color_index * tcolors, + int x, int y, int w, int h, + int phase_x, int phase_y, gs_logical_operation_t lop, + uint planar_height) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* gx_forward_strip_copy_rop doesn't exist */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, strip_copy_rop2) + (tdev, sdata, sourcex, sraster, id, scolors, + textures, tcolors, x, y, w, h, phase_x, phase_y, lop, + planar_height)); + + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + return code; +} + +/* ---------------- Parameters ---------------- */ + +/* We implement get_params to provide a way to read out the bounding box. */ +static int +bbox_get_params(gx_device * dev, gs_param_list * plist) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gs_fixed_rect fbox; + int code = gx_forward_get_params(dev, plist); + gs_param_float_array bba; + float bbox[4]; + + if (code < 0) + return code; + /* + * We might be calling get_params before the device has been + * initialized: in this case, box_proc_data = 0. + */ + if (bdev->box_proc_data == 0) + fbox = bdev->bbox; + else + BBOX_GET_BOX(bdev, &fbox); + bbox[0] = fixed2float(fbox.p.x); + bbox[1] = fixed2float(fbox.p.y); + bbox[2] = fixed2float(fbox.q.x); + bbox[3] = fixed2float(fbox.q.y); + bba.data = bbox, bba.size = 4, bba.persistent = false; + code = param_write_float_array(plist, "PageBoundingBox", &bba); + if (code < 0) + return code; + code = param_write_bool(plist, "WhiteIsOpaque", &bdev->white_is_opaque); + return code; +} + +/* We implement put_params to ensure that we keep the important */ +/* device parameters up to date, and to prevent an /undefined error */ +/* from PageBoundingBox. */ +static int +bbox_put_params(gx_device * dev, gs_param_list * plist) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + int code; + int ecode = 0; + bool white_is_opaque = bdev->white_is_opaque; + gs_param_name param_name; + gs_param_float_array bba; + + code = param_read_float_array(plist, (param_name = "PageBoundingBox"), + &bba); + switch (code) { + case 0: + if (bba.size != 4) { + ecode = gs_note_error(gs_error_rangecheck); + goto e; + } + break; + default: + ecode = code; + e:param_signal_error(plist, param_name, ecode); + case 1: + bba.data = 0; + } + + switch (code = param_read_bool(plist, (param_name = "WhiteIsOpaque"), &white_is_opaque)) { + default: + ecode = code; + param_signal_error(plist, param_name, ecode); + case 0: + case 1: + break; + } + + code = gx_forward_put_params(dev, plist); + if (ecode < 0) + code = ecode; + if (code >= 0) { + if( bba.data != 0) { + BBOX_INIT_BOX(bdev); + BBOX_ADD_RECT(bdev, float2fixed(bba.data[0]), float2fixed(bba.data[1]), + float2fixed(bba.data[2]), float2fixed(bba.data[3])); + } + bdev->white_is_opaque = white_is_opaque; + } + bbox_copy_params(bdev, bdev->is_open); + return code; +} + +/* ---------------- Polygon drawing ---------------- */ + +static fixed +edge_x_at_y(const gs_fixed_edge * edge, fixed y) +{ + return fixed_mult_quo(edge->end.x - edge->start.x, + y - edge->start.y, + edge->end.y - edge->start.y) + edge->start.x; +} +static int +bbox_fill_trapezoid(gx_device * dev, + const gs_fixed_edge * left, const gs_fixed_edge * right, + fixed ybot, fixed ytop, bool swap_axes, + const gx_device_color * pdevc, gs_logical_operation_t lop) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* Skip the call if there is no target. */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, fill_trapezoid) + (tdev, left, right, ybot, ytop, swap_axes, pdevc, lop)); + + if (!GX_DC_IS_TRANSPARENT(pdevc, bdev)) { + fixed x0l = + (left->start.y == ybot ? left->start.x : + edge_x_at_y(left, ybot)); + fixed x1l = + (left->end.y == ytop ? left->end.x : + edge_x_at_y(left, ytop)); + fixed x0r = + (right->start.y == ybot ? right->start.x : + edge_x_at_y(right, ybot)); + fixed x1r = + (right->end.y == ytop ? right->end.x : + edge_x_at_y(right, ytop)); + fixed xminl = min(x0l, x1l), xmaxl = max(x0l, x1l); + fixed xminr = min(x0r, x1r), xmaxr = max(x0r, x1r); + fixed x0 = min(xminl, xminr), x1 = max(xmaxl, xmaxr); + + if (swap_axes) + BBOX_ADD_RECT(bdev, ybot, x0, ytop, x1); + else + BBOX_ADD_RECT(bdev, x0, ybot, x1, ytop); + } + return code; +} + +static int +bbox_fill_parallelogram(gx_device * dev, + fixed px, fixed py, fixed ax, fixed ay, + fixed bx, fixed by, const gx_device_color * pdevc, + gs_logical_operation_t lop) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* Skip the call if there is no target. */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, fill_parallelogram) + (tdev, px, py, ax, ay, bx, by, pdevc, lop)); + + if (!GX_DC_IS_TRANSPARENT(pdevc, bdev)) { + fixed xmin, ymin, xmax, ymax; + + /* bbox_add_rect requires points in correct order. */ +#define SET_MIN_MAX(vmin, vmax, av, bv)\ + BEGIN\ + if (av <= 0) {\ + if (bv <= 0)\ + vmin = av + bv, vmax = 0;\ + else\ + vmin = av, vmax = bv;\ + } else if (bv <= 0)\ + vmin = bv, vmax = av;\ + else\ + vmin = 0, vmax = av + bv;\ + END + SET_MIN_MAX(xmin, xmax, ax, bx); + SET_MIN_MAX(ymin, ymax, ay, by); +#undef SET_MIN_MAX + BBOX_ADD_RECT(bdev, px + xmin, py + ymin, px + xmax, py + ymax); + } + return code; +} + +static int +bbox_fill_triangle(gx_device * dev, + fixed px, fixed py, fixed ax, fixed ay, fixed bx, fixed by, + const gx_device_color * pdevc, gs_logical_operation_t lop) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* Skip the call if there is no target. */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, fill_triangle) + (tdev, px, py, ax, ay, bx, by, pdevc, lop)); + + if (!GX_DC_IS_TRANSPARENT(pdevc, bdev)) { + fixed xmin, ymin, xmax, ymax; + + /* bbox_add_rect requires points in correct order. */ +#define SET_MIN_MAX(vmin, vmax, av, bv)\ + BEGIN\ + if (av <= 0) {\ + if (bv <= 0)\ + vmin = min(av, bv), vmax = 0;\ + else\ + vmin = av, vmax = bv;\ + } else if (bv <= 0)\ + vmin = bv, vmax = av;\ + else\ + vmin = 0, vmax = max(av, bv);\ + END + SET_MIN_MAX(xmin, xmax, ax, bx); + SET_MIN_MAX(ymin, ymax, ay, by); +#undef SET_MIN_MAX + BBOX_ADD_RECT(bdev, px + xmin, py + ymin, px + xmax, py + ymax); + } + return code; +} + +static int +bbox_draw_thin_line(gx_device * dev, + fixed fx0, fixed fy0, fixed fx1, fixed fy1, + const gx_device_color * pdevc, gs_logical_operation_t lop, + fixed adjustx, fixed adjusty) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + /* Skip the call if there is no target. */ + gx_device *tdev = bdev->target; + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, draw_thin_line) + (tdev, fx0, fy0, fx1, fy0, pdevc, lop, adjustx, adjusty)); + + if (!GX_DC_IS_TRANSPARENT(pdevc, bdev)) { + fixed xmin, ymin, xmax, ymax; + + /* bbox_add_rect requires points in correct order. */ +#define SET_MIN_MAX(vmin, vmax, av, bv)\ + BEGIN\ + if (av < bv)\ + vmin = av, vmax = bv;\ + else\ + vmin = bv, vmax = av;\ + END + SET_MIN_MAX(xmin, xmax, fx0, fx1); + SET_MIN_MAX(ymin, ymax, fy0, fy1); +#undef SET_MIN_MAX + BBOX_ADD_RECT(bdev, xmin, ymin, xmax, ymax); + } + return code; +} + +/* ---------------- High-level drawing ---------------- */ + +#define adjust_box(pbox, adj)\ +((pbox)->p.x -= (adj).x, (pbox)->p.y -= (adj).y,\ + (pbox)->q.x += (adj).x, (pbox)->q.y += (adj).y) + +static int +bbox_fill_path(gx_device * dev, const gs_imager_state * pis, gx_path * ppath, + const gx_fill_params * params, const gx_device_color * pdevc, + const gx_clip_path * pcpath) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gx_device *tdev = bdev->target; + dev_proc_fill_path((*fill_path)) = + (tdev == 0 ? dev_proc(&gs_null_device, fill_path) : + dev_proc(tdev, fill_path)); + int code; + + if (ppath == NULL) { + /* A special handling of shfill with no path. */ + gs_fixed_rect ibox; + gs_fixed_point adjust; + + if (pcpath == NULL) + return 0; + gx_cpath_inner_box(pcpath, &ibox); + adjust = params->adjust; + adjust_box(&ibox, adjust); + BBOX_ADD_RECT(bdev, ibox.p.x, ibox.p.y, ibox.q.x, ibox.q.y); + return 0; + } else if (!GX_DC_IS_TRANSPARENT(pdevc, bdev) && !gx_path_is_void(ppath)) { + gs_fixed_rect ibox; + gs_fixed_point adjust; + + if (gx_path_bbox(ppath, &ibox) < 0) + return 0; + adjust = params->adjust; + adjust_box(&ibox, adjust); + /* + * If the path lies within the already accumulated box, just draw + * on the target. + */ + if (BBOX_IN_RECT(bdev, &ibox)) + return fill_path(tdev, pis, ppath, params, pdevc, pcpath); + /* + * If the target uses the default algorithm, just draw on the + * bbox device. + */ + if (tdev != 0 && fill_path == gx_default_fill_path) + return fill_path(dev, pis, ppath, params, pdevc, pcpath); + /* Draw on the target now. */ + code = fill_path(tdev, pis, ppath, params, pdevc, pcpath); + if (code < 0) + return code; + if (pcpath != NULL && + !gx_cpath_includes_rectangle(pcpath, ibox.p.x, ibox.p.y, + ibox.q.x, ibox.q.y) + ) { + /* + * Let the target do the drawing, but break down the + * fill path into pieces for computing the bounding box. + */ + gx_drawing_color devc; + + set_nonclient_dev_color(&devc, bdev->black); /* any non-white color will do */ + bdev->target = NULL; + code = gx_default_fill_path(dev, pis, ppath, params, &devc, pcpath); + bdev->target = tdev; + } else { /* Just use the path bounding box. */ + BBOX_ADD_RECT(bdev, ibox.p.x, ibox.p.y, ibox.q.x, ibox.q.y); + } + return code; + } else + return fill_path(tdev, pis, ppath, params, pdevc, pcpath); +} + +static int +bbox_stroke_path(gx_device * dev, const gs_imager_state * pis, gx_path * ppath, + const gx_stroke_params * params, + const gx_drawing_color * pdevc, const gx_clip_path * pcpath) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gx_device *tdev = bdev->target; + /* Skip the call if there is no target. */ + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, stroke_path)(tdev, pis, ppath, params, pdevc, pcpath)); + + if (!GX_DC_IS_TRANSPARENT(pdevc, bdev)) { + gs_fixed_rect ibox; + gs_fixed_point expand; + + if (gx_stroke_path_expansion(pis, ppath, &expand) == 0 && + gx_path_bbox(ppath, &ibox) >= 0 + ) { + /* The fast result is exact. */ + adjust_box(&ibox, expand); + } else { + /* + * The result is not exact. Compute an exact result using + * strokepath. + */ + gx_path *spath = gx_path_alloc(pis->memory, "bbox_stroke_path"); + int code = 0; + + if (spath) + code = gx_imager_stroke_add(ppath, spath, dev, pis); + else + code = -1; + if (code >= 0) + code = gx_path_bbox(spath, &ibox); + if (code < 0) { + ibox.p.x = ibox.p.y = min_fixed; + ibox.q.x = ibox.q.y = max_fixed; + } + if (spath) + gx_path_free(spath, "bbox_stroke_path"); + } + if (pcpath != NULL && + !gx_cpath_includes_rectangle(pcpath, ibox.p.x, ibox.p.y, + ibox.q.x, ibox.q.y) + ) { + /* Let the target do the drawing, but break down the */ + /* fill path into pieces for computing the bounding box. */ + gx_drawing_color devc; + + set_nonclient_dev_color(&devc, bdev->black); /* any non-white color will do */ + bdev->target = NULL; + gx_default_stroke_path(dev, pis, ppath, params, &devc, pcpath); + bdev->target = tdev; + } else { + /* Just use the path bounding box. */ + BBOX_ADD_RECT(bdev, ibox.p.x, ibox.p.y, ibox.q.x, ibox.q.y); + } + } + return code; +} + +static int +bbox_fill_mask(gx_device * dev, + const byte * data, int dx, int raster, gx_bitmap_id id, + int x, int y, int w, int h, + const gx_drawing_color * pdcolor, int depth, + gs_logical_operation_t lop, const gx_clip_path * pcpath) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gx_device *tdev = bdev->target; + /* Skip the call if there is no target. */ + int code = + (tdev == 0 ? 0 : + dev_proc(tdev, fill_mask) + (tdev, data, dx, raster, id, x, y, w, h, + pdcolor, depth, lop, pcpath)); + + if (pcpath != NULL && + !gx_cpath_includes_rectangle(pcpath, int2fixed(x), int2fixed(y), + int2fixed(x + w), + int2fixed(y + h)) + ) { + /* Let the target do the drawing, but break down the */ + /* image into pieces for computing the bounding box. */ + bdev->target = NULL; + gx_default_fill_mask(dev, data, dx, raster, id, x, y, w, h, + pdcolor, depth, lop, pcpath); + bdev->target = tdev; + } else { + /* Just use the mask bounding box. */ + BBOX_ADD_INT_RECT(bdev, x, y, x + w, y + h); + } + return code; +} + +/* ------ Bitmap imaging ------ */ + +typedef struct bbox_image_enum_s { + gx_image_enum_common; + gs_matrix matrix; /* map from image space to device space */ + const gx_clip_path *pcpath; + gx_image_enum_common_t *target_info; + bool params_are_const; + int x0, x1; + int y, height; +} bbox_image_enum; + +gs_private_st_suffix_add2(st_bbox_image_enum, bbox_image_enum, + "bbox_image_enum", bbox_image_enum_enum_ptrs, bbox_image_enum_reloc_ptrs, + st_gx_image_enum_common, pcpath, target_info); + +static image_enum_proc_plane_data(bbox_image_plane_data); +static image_enum_proc_end_image(bbox_image_end_image); +static image_enum_proc_flush(bbox_image_flush); +static image_enum_proc_planes_wanted(bbox_image_planes_wanted); +static const gx_image_enum_procs_t bbox_image_enum_procs = { + bbox_image_plane_data, bbox_image_end_image, + bbox_image_flush, bbox_image_planes_wanted +}; + +static int +bbox_image_begin(const gs_imager_state * pis, const gs_matrix * pmat, + const gs_image_common_t * pic, const gs_int_rect * prect, + const gx_clip_path * pcpath, gs_memory_t * memory, + bbox_image_enum ** ppbe) +{ + int code; + gs_matrix mat; + bbox_image_enum *pbe; + + if (pmat == 0) + pmat = &ctm_only(pis); + if ((code = gs_matrix_invert(&pic->ImageMatrix, &mat)) < 0 || + (code = gs_matrix_multiply(&mat, pmat, &mat)) < 0 + ) + return code; + pbe = gs_alloc_struct(memory, bbox_image_enum, &st_bbox_image_enum, + "bbox_image_begin"); + if (pbe == 0) + return_error(gs_error_VMerror); + pbe->memory = memory; + pbe->matrix = mat; + pbe->pcpath = pcpath; + pbe->target_info = 0; /* in case no target */ + pbe->params_are_const = false; /* check the first time */ + if (prect) { + pbe->x0 = prect->p.x, pbe->x1 = prect->q.x; + pbe->y = prect->p.y, pbe->height = prect->q.y - prect->p.y; + } else { + gs_int_point size; + int code = (*pic->type->source_size) (pis, pic, &size); + + if (code < 0) { + gs_free_object(memory, pbe, "bbox_image_begin"); + return code; + } + pbe->x0 = 0, pbe->x1 = size.x; + pbe->y = 0, pbe->height = size.y; + } + *ppbe = pbe; + return 0; +} + +static void +bbox_image_copy_target_info(bbox_image_enum * pbe) +{ + const gx_image_enum_common_t *target_info = pbe->target_info; + + pbe->num_planes = target_info->num_planes; + memcpy(pbe->plane_depths, target_info->plane_depths, + pbe->num_planes * sizeof(pbe->plane_depths[0])); + memcpy(pbe->plane_widths, target_info->plane_widths, + pbe->num_planes * sizeof(pbe->plane_widths[0])); +} + +static int +bbox_begin_typed_image(gx_device * dev, + const gs_imager_state * pis, const gs_matrix * pmat, + const gs_image_common_t * pic, const gs_int_rect * prect, + const gx_drawing_color * pdcolor, + const gx_clip_path * pcpath, + gs_memory_t * memory, gx_image_enum_common_t ** pinfo) +{ + bbox_image_enum *pbe; + int code = + bbox_image_begin(pis, pmat, pic, prect, pcpath, memory, &pbe); + + if (code < 0) + return code; + /* + * If there is no target, we still have to call default_begin_image + * to get the correct num_planes and plane_depths. + */ + { + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gx_device *tdev = bdev->target; + dev_proc_begin_typed_image((*begin_typed_image)); + byte wanted[GS_IMAGE_MAX_COMPONENTS]; + + if (tdev == 0) { + tdev = dev; + begin_typed_image = gx_default_begin_typed_image; + } else { + begin_typed_image = dev_proc(tdev, begin_typed_image); + } + code = (*begin_typed_image) + (tdev, pis, pmat, pic, prect, pdcolor, pcpath, memory, + &pbe->target_info); + if (code) { + bbox_image_end_image((gx_image_enum_common_t *)pbe, false); + return code; + } + /* + * We fill in num_planes and plane_depths later. format is + * irrelevant. NOTE: we assume that if begin_typed_image returned + * 0, the image is a data image. + */ + code = gx_image_enum_common_init((gx_image_enum_common_t *) pbe, + (const gs_data_image_t *)pic, + &bbox_image_enum_procs, dev, + 0, gs_image_format_chunky); + if (code < 0) + return code; + bbox_image_copy_target_info(pbe); + pbe->params_are_const = + gx_image_planes_wanted(pbe->target_info, wanted); + } + *pinfo = (gx_image_enum_common_t *) pbe; + return 0; +} + +static int +bbox_image_plane_data(gx_image_enum_common_t * info, + const gx_image_plane_t * planes, int height, + int *rows_used) +{ + gx_device *dev = info->dev; + gx_device_bbox *const bdev = (gx_device_bbox *)dev; + gx_device *tdev = bdev->target; + bbox_image_enum *pbe = (bbox_image_enum *) info; + const gx_clip_path *pcpath = pbe->pcpath; + gs_rect sbox, dbox; + gs_point corners[4]; + gs_fixed_rect ibox; + int code; + + code = gx_image_plane_data_rows(pbe->target_info, planes, height, + rows_used); + if (code != 1 && !pbe->params_are_const) + bbox_image_copy_target_info(pbe); + sbox.p.x = pbe->x0; + sbox.p.y = pbe->y; + sbox.q.x = pbe->x1; + sbox.q.y = pbe->y = min(pbe->y + height, pbe->height); + gs_bbox_transform_only(&sbox, &pbe->matrix, corners); + gs_points_bbox(corners, &dbox); + ibox.p.x = float2fixed(dbox.p.x); + ibox.p.y = float2fixed(dbox.p.y); + ibox.q.x = float2fixed(dbox.q.x); + ibox.q.y = float2fixed(dbox.q.y); + if (pcpath != NULL && + !gx_cpath_includes_rectangle(pcpath, ibox.p.x, ibox.p.y, + ibox.q.x, ibox.q.y) + ) { + /* Let the target do the drawing, but drive two triangles */ + /* through the clipping path to get an accurate bounding box. */ + gx_device_clip cdev; + gx_drawing_color devc; + fixed x0 = float2fixed(corners[0].x), y0 = float2fixed(corners[0].y); + fixed bx2 = float2fixed(corners[2].x) - x0, by2 = float2fixed(corners[2].y) - y0; + + gx_make_clip_device_on_stack(&cdev, pcpath, dev); + set_nonclient_dev_color(&devc, bdev->black); /* any non-white color will do */ + bdev->target = NULL; + gx_default_fill_triangle((gx_device *) & cdev, x0, y0, + float2fixed(corners[1].x) - x0, + float2fixed(corners[1].y) - y0, + bx2, by2, &devc, lop_default); + gx_default_fill_triangle((gx_device *) & cdev, x0, y0, + float2fixed(corners[3].x) - x0, + float2fixed(corners[3].y) - y0, + bx2, by2, &devc, lop_default); + bdev->target = tdev; + } else { + /* Just use the bounding box. */ + BBOX_ADD_RECT(bdev, ibox.p.x, ibox.p.y, ibox.q.x, ibox.q.y); + } + return code; +} + +static int +bbox_image_end_image(gx_image_enum_common_t * info, bool draw_last) +{ + bbox_image_enum *pbe = (bbox_image_enum *) info; + int code = 0; + + if (pbe->target_info) + code = gx_image_end(pbe->target_info, draw_last); + + gx_image_free_enum(&info); + return code; +} + +static int +bbox_image_flush(gx_image_enum_common_t * info) +{ + bbox_image_enum *pbe = (bbox_image_enum *) info; + gx_image_enum_common_t *target_info = pbe->target_info; + + return (target_info ? gx_image_flush(target_info) : 0); +} + +static bool +bbox_image_planes_wanted(const gx_image_enum_common_t * info, byte *wanted) +{ + /* This is only used if target_info != 0. */ + const bbox_image_enum *pbe = (const bbox_image_enum *)info; + + return gx_image_planes_wanted(pbe->target_info, wanted); +} + +/* Compositing */ + +static bool +bbox_forward_init_box(void *pdata) +{ + gx_device_bbox *const bdev = (gx_device_bbox *)pdata; + + return BBOX_INIT_BOX(bdev); +} +static void +bbox_forward_get_box(const void *pdata, gs_fixed_rect *pbox) +{ + const gx_device_bbox *const bdev = (const gx_device_bbox *)pdata; + + BBOX_GET_BOX(bdev, pbox); +} +static void +bbox_forward_add_rect(void *pdata, fixed x0, fixed y0, fixed x1, fixed y1) +{ + gx_device_bbox *const bdev = (gx_device_bbox *)pdata; + + BBOX_ADD_RECT(bdev, x0, y0, x1, y1); +} +static bool +bbox_forward_in_rect(const void *pdata, const gs_fixed_rect *pbox) +{ + const gx_device_bbox *const bdev = (const gx_device_bbox *)pdata; + + return BBOX_IN_RECT(bdev, pbox); +} +static const gx_device_bbox_procs_t box_procs_forward = { + bbox_forward_init_box, bbox_forward_get_box, bbox_forward_add_rect, + bbox_forward_in_rect +}; + +static int +bbox_create_compositor(gx_device * dev, + gx_device ** pcdev, const gs_composite_t * pcte, + gs_imager_state * pis, gs_memory_t * memory, gx_device *cindev) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gx_device *target = bdev->target; + + /* + * If there isn't a target, all we care about is the bounding box, + * so don't bother with actually compositing. + */ + if (target == 0) { + *pcdev = dev; + return 0; + } + /* + * Create a compositor for the target, and then wrap another + * bbox device around it, but still accumulating the bounding + * box in the same place. + */ + { + gx_device *temp_cdev; + gx_device_bbox *bbcdev; + int code = (*dev_proc(target, create_compositor)) + (target, &temp_cdev, pcte, pis, memory, cindev); + + /* If the target did not create a new compositor then we are done. */ + if (code < 0 || target == temp_cdev) { + *pcdev = dev; + return code; + } + bbcdev = gs_alloc_struct_immovable(memory, gx_device_bbox, + &st_device_bbox, + "bbox_create_compositor"); + if (bbcdev == 0) { + (*dev_proc(temp_cdev, close_device)) (temp_cdev); + return_error(gs_error_VMerror); + } + gx_device_bbox_init(bbcdev, target, memory); + gx_device_set_target((gx_device_forward *)bbcdev, temp_cdev); + bbcdev->box_procs = box_procs_forward; + bbcdev->box_proc_data = bdev; + *pcdev = (gx_device *) bbcdev; + return 0; + } +} + +/* ------ Text imaging ------ */ + +static int +bbox_text_begin(gx_device * dev, gs_imager_state * pis, + const gs_text_params_t * text, gs_font * font, + gx_path * path, const gx_device_color * pdcolor, + const gx_clip_path * pcpath, + gs_memory_t * memory, gs_text_enum_t ** ppenum) +{ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + int code = gx_default_text_begin(dev, pis, text, font, path, pdcolor, + pcpath, memory, ppenum); + + if (code >=0 && bdev->target != NULL) { + /* See note on imaging_dev in gxtext.h */ + rc_assign((*ppenum)->imaging_dev, dev, "bbox_text_begin"); + } + + return code; +} + +/* --------------- fillpage ------------------- */ + +int bbox_fillpage(gx_device *dev, gs_imager_state * pis, gx_device_color *pdevc) +{ + /* Call the target's proc, but don't account the size. */ + gx_device_bbox *const bdev = (gx_device_bbox *) dev; + gx_device *tdev = bdev->target; + + BBOX_INIT_BOX(bdev); + if (tdev == NULL) + return 0; + return dev_proc(tdev, fillpage)(tdev, pis, pdevc); +} |