diff options
Diffstat (limited to 'devices/vector/gdevpdfd.c')
-rw-r--r-- | devices/vector/gdevpdfd.c | 1552 |
1 files changed, 1552 insertions, 0 deletions
diff --git a/devices/vector/gdevpdfd.c b/devices/vector/gdevpdfd.c new file mode 100644 index 000000000..2f17411a2 --- /dev/null +++ b/devices/vector/gdevpdfd.c @@ -0,0 +1,1552 @@ +/* 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. +*/ + + +/* Path drawing procedures for pdfwrite driver */ +#include "math_.h" +#include "memory_.h" +#include "gx.h" +#include "gxdevice.h" +#include "gxfixed.h" +#include "gxistate.h" +#include "gxpaint.h" +#include "gxcoord.h" +#include "gxdevmem.h" +#include "gxcolor2.h" +#include "gxhldevc.h" +#include "gsstate.h" +#include "gxstate.h" +#include "gserrors.h" +#include "gsptype2.h" +#include "gsshade.h" +#include "gzpath.h" +#include "gzcpath.h" +#include "gdevpdfx.h" +#include "gdevpdfg.h" +#include "gdevpdfo.h" +#include "gsutil.h" +#include "gdevpdtf.h" +#include "gdevpdts.h" +#include "gxdevsop.h" + +/* ---------------- Drawing ---------------- */ + +/* Fill a rectangle. */ +int +gdev_pdf_fill_rectangle(gx_device * dev, int x, int y, int w, int h, + gx_color_index color) +{ + gx_device_pdf *pdev = (gx_device_pdf *) dev; + int code; + + if (pdev->Eps2Write) { + float x0, y0, x1, y1; + gs_rect *Box; + + if (!pdev->accumulating_charproc) { + Box = &pdev->BBox; + x0 = x / (pdev->HWResolution[0] / 72.0); + y0 = y / (pdev->HWResolution[1] / 72.0); + x1 = x0 + (w / (pdev->HWResolution[0] / 72.0)); + y1 = y0 + (h / (pdev->HWResolution[1] / 72.0)); + } + else { + Box = &pdev->charproc_BBox; + x0 = (float)x / 100; + y0 = (float)y / 100; + x1 = x0 + (w / 100); + y1 = y0 + (h / 100); + } + + if (Box->p.x > x0) + Box->p.x = x0; + if (Box->p.y > y0) + Box->p.y = y0; + if (Box->q.x < x1) + Box->q.x = x1; + if (Box->q.y < y1) + Box->q.y = y1; + if (pdev->AccumulatingBBox) + return 0; + } + code = pdf_open_page(pdev, PDF_IN_STREAM); + if (code < 0) + return code; + /* Make sure we aren't being clipped. */ + code = pdf_put_clip_path(pdev, NULL); + if (code < 0) + return code; + pdf_set_pure_color(pdev, color, &pdev->saved_fill_color, + &pdev->fill_used_process_color, + &psdf_set_fill_color_commands); + if (!pdev->HaveStrokeColor) + pdev->saved_stroke_color = pdev->saved_fill_color; + pprintd4(pdev->strm, "%d %d %d %d re f\n", x, y, w, h); + return 0; +} + +/* ---------------- Path drawing ---------------- */ + +/* ------ Vector device implementation ------ */ + +static int +pdf_setlinewidth(gx_device_vector * vdev, double width) +{ + /* Acrobat Reader doesn't accept negative line widths. */ + return psdf_setlinewidth(vdev, fabs(width)); +} + +static int +pdf_can_handle_hl_color(gx_device_vector * vdev, const gs_imager_state * pis, + const gx_drawing_color * pdc) +{ + return pis != NULL; +} + +static int +pdf_setfillcolor(gx_device_vector * vdev, const gs_imager_state * pis, + const gx_drawing_color * pdc) +{ + gx_device_pdf *const pdev = (gx_device_pdf *)vdev; + bool hl_color = (*vdev_proc(vdev, can_handle_hl_color)) (vdev, pis, pdc); + const gs_imager_state *pis_for_hl_color = (hl_color ? pis : NULL); + + if (!pdev->HaveStrokeColor) { + /* opdfread.ps assumes same color for stroking and non-stroking operations. */ + int code = pdf_set_drawing_color(pdev, pis_for_hl_color, pdc, &pdev->saved_stroke_color, + &pdev->stroke_used_process_color, + &psdf_set_stroke_color_commands); + if (code < 0) + return code; + } + return pdf_set_drawing_color(pdev, pis_for_hl_color, pdc, &pdev->saved_fill_color, + &pdev->fill_used_process_color, + &psdf_set_fill_color_commands); +} + +static int +pdf_setstrokecolor(gx_device_vector * vdev, const gs_imager_state * pis, + const gx_drawing_color * pdc) +{ + gx_device_pdf *const pdev = (gx_device_pdf *)vdev; + bool hl_color = (*vdev_proc(vdev, can_handle_hl_color)) (vdev, pis, pdc); + const gs_imager_state *pis_for_hl_color = (hl_color ? pis : NULL); + + if (!pdev->HaveStrokeColor) { + /* opdfread.ps assumes same color for stroking and non-stroking operations. */ + int code = pdf_set_drawing_color(pdev, pis_for_hl_color, pdc, &pdev->saved_fill_color, + &pdev->fill_used_process_color, + &psdf_set_fill_color_commands); + if (code < 0) + return code; + } + return pdf_set_drawing_color(pdev, pis_for_hl_color, pdc, &pdev->saved_stroke_color, + &pdev->stroke_used_process_color, + &psdf_set_stroke_color_commands); +} + +static int +pdf_dorect(gx_device_vector * vdev, fixed x0, fixed y0, fixed x1, fixed y1, + gx_path_type_t type) +{ + gx_device_pdf *pdev = (gx_device_pdf *)vdev; + fixed xmax = int2fixed(32766), ymax = int2fixed(32766); + int bottom = (pdev->ResourcesBeforeUsage ? 1 : 0); + fixed xmin = (pdev->sbstack_depth > bottom ? -xmax : 0); + fixed ymin = (pdev->sbstack_depth > bottom ? -ymax : 0); + + /* + * If we're doing a stroke operation, expand the checking box by the + * stroke width. + */ + if (type & gx_path_type_stroke) { + double w = vdev->state.line_params.half_width; + double xw = w * (fabs(vdev->state.ctm.xx) + fabs(vdev->state.ctm.yx)); + int d = float2fixed(xw) + fixed_1; + + xmin -= d; + xmax += d; + ymin -= d; + ymax += d; + } + if (!(type & gx_path_type_clip) && + (x0 > xmax || x1 < xmin || y0 > ymax || y1 < ymin || + x0 > x1 || y0 > y1) + ) + return 0; /* nothing to fill or stroke */ + /* + * Clamp coordinates to avoid tripping over Acrobat Reader's limit + * of 32K on user coordinate values. + */ + if (x0 < xmin) + x0 = xmin; + if (x1 > xmax) + x1 = xmax; + if (y0 < ymin) + y0 = ymin; + if (y1 > ymax) + y1 = ymax; + return psdf_dorect(vdev, x0, y0, x1, y1, type); +} + +static int +pdf_endpath(gx_device_vector * vdev, gx_path_type_t type) +{ + return 0; /* always handled by caller */ +} + +const gx_device_vector_procs pdf_vector_procs = { + /* Page management */ + NULL, + /* Imager state */ + pdf_setlinewidth, + psdf_setlinecap, + psdf_setlinejoin, + psdf_setmiterlimit, + psdf_setdash, + psdf_setflat, + psdf_setlogop, + /* Other state */ + pdf_can_handle_hl_color, + pdf_setfillcolor, + pdf_setstrokecolor, + /* Paths */ + psdf_dopath, + pdf_dorect, + psdf_beginpath, + psdf_moveto, + psdf_lineto, + psdf_curveto, + psdf_closepath, + pdf_endpath +}; + +/* ------ Utilities ------ */ + +/* Store a copy of clipping path. */ +int +pdf_remember_clip_path(gx_device_pdf * pdev, const gx_clip_path * pcpath) +{ + /* Used for skipping redundant clip paths. SF bug #624168. */ + if (pdev->clip_path != 0) { + gx_path_free(pdev->clip_path, "pdf clip path"); + } + if (pcpath == 0) { + pdev->clip_path = 0; + return 0; + } + pdev->clip_path = gx_path_alloc(pdev->pdf_memory, "pdf clip path"); + if (pdev->clip_path == 0) + return_error(gs_error_VMerror); + return gx_cpath_to_path((gx_clip_path *)pcpath, pdev->clip_path); +} + +/* Check if same clipping path. */ +static int +pdf_is_same_clip_path(gx_device_pdf * pdev, const gx_clip_path * pcpath) +{ + /* Used for skipping redundant clip paths. SF bug #624168. */ + gs_cpath_enum cenum; + gs_path_enum penum; + gs_fixed_point vs0[3], vs1[3]; + int code, pe_op; + + if ((pdev->clip_path != 0) != (pcpath != 0)) + return 0; + /* Both clip paths are empty, so the same */ + if (pdev->clip_path == 0) + return 1; + code = gx_path_enum_init(&penum, pdev->clip_path); + if (code < 0) + return code; + code = gx_cpath_enum_init(&cenum, (gx_clip_path *)pcpath); + if (code < 0) + return code; + /* This flags a warning in Coverity, uninitialised variable cenum.first_visit */ + /* This is because gx_cpath_enum_init doesn't initialise first_visit, but the */ + /* variable can be used in enum_next. However, this is not truly used this */ + /* way. The enum_init sets the 'state' to 'scan', and the first thing that happens */ + /* in enum_next when state is 'scan' is to set first_visit. */ + while ((code = gx_cpath_enum_next(&cenum, vs0)) > 0) { + pe_op = gx_path_enum_next(&penum, vs1); + if (pe_op < 0) + return pe_op; + if (pe_op != code) + return 0; + switch (pe_op) { + case gs_pe_curveto: + if (vs0[1].x != vs1[1].x || vs0[1].y != vs1[1].y || + vs0[2].x != vs1[2].x || vs0[2].y != vs1[2].y) + return 0; + case gs_pe_moveto: + case gs_pe_lineto: + case gs_pe_gapto: + if (vs0[0].x != vs1[0].x || vs0[0].y != vs1[0].y) + return 0; + } + } + if (code < 0) + return code; + code = gx_path_enum_next(&penum, vs1); + if (code < 0) + return code; + return (code == 0); +} + +/* Test whether we will need to put the clipping path. */ +bool +pdf_must_put_clip_path(gx_device_pdf * pdev, const gx_clip_path * pcpath) +{ + if (pcpath == NULL) { + if (pdev->clip_path_id == pdev->no_clip_path_id) + return false; + } else { + if (pdev->clip_path_id == pcpath->id) + return false; + if (gx_cpath_includes_rectangle(pcpath, fixed_0, fixed_0, + int2fixed(pdev->width), + int2fixed(pdev->height))) + if (pdev->clip_path_id == pdev->no_clip_path_id) + return false; + if (pdf_is_same_clip_path(pdev, pcpath) > 0) { + pdev->clip_path_id = pcpath->id; + return false; + } + } + return true; +} + +/* Put a single element of a clipping path list. */ +static int +pdf_put_clip_path_list_elem(gx_device_pdf * pdev, gx_cpath_path_list *e, + gs_path_enum *cenum, gdev_vector_dopath_state_t *state, + gs_fixed_point vs[3]) +{ /* This recursive function provides a reverse order of the list elements. */ + int pe_op; + + if (e->next != NULL) { + int code = pdf_put_clip_path_list_elem(pdev, e->next, cenum, state, vs); + + if (code != 0) + return code; + } + gx_path_enum_init(cenum, &e->path); + while ((pe_op = gx_path_enum_next(cenum, vs)) > 0) + gdev_vector_dopath_segment(state, pe_op, vs); + pprints1(pdev->strm, "%s n\n", (e->rule <= 0 ? "W" : "W*")); + if (pe_op < 0) + return pe_op; + return 0; +} + +/* Put a clipping path on the output file. */ +int +pdf_put_clip_path(gx_device_pdf * pdev, const gx_clip_path * pcpath) +{ + int code; + stream *s = pdev->strm; + gs_id new_id; + + /* Check for no update needed. */ + if (pcpath == NULL) { + if (pdev->clip_path_id == pdev->no_clip_path_id) + return 0; + new_id = pdev->no_clip_path_id; + } else { + if (pdev->clip_path_id == pcpath->id) + return 0; + new_id = pcpath->id; + if (gx_cpath_includes_rectangle(pcpath, fixed_0, fixed_0, + int2fixed(pdev->width), + int2fixed(pdev->height)) + ) { + if (pdev->clip_path_id == pdev->no_clip_path_id) + return 0; + new_id = pdev->no_clip_path_id; + } + code = pdf_is_same_clip_path(pdev, pcpath); + if (code < 0) + return code; + if (code) { + pdev->clip_path_id = new_id; + return 0; + } + } + /* + * The contents must be open already, so the following will only exit + * text or string context. + */ + code = pdf_open_contents(pdev, PDF_IN_STREAM); + if (code < 0) + return code; + /* Use Q to unwind the old clipping path. */ + if (pdev->vgstack_depth > pdev->vgstack_bottom) { + code = pdf_restore_viewer_state(pdev, s); + if (code < 0) + return code; + } + if (new_id != pdev->no_clip_path_id) { + gs_fixed_rect rect; + + /* Use q to allow the new clipping path to unwind. */ + code = pdf_save_viewer_state(pdev, s); + if (code < 0) + return code; + if (cpath_is_rectangle(pcpath, &rect)) { + /* Use unrounded coordinates. */ + pprintg4(s, "%g %g %g %g re", + fixed2float(rect.p.x), fixed2float(rect.p.y), + fixed2float(rect.q.x - rect.p.x), + fixed2float(rect.q.y - rect.p.y)); + pprints1(s, " %s n\n", (pcpath->rule <= 0 ? "W" : "W*")); + } else { + gdev_vector_dopath_state_t state; + gs_fixed_point vs[3]; + int pe_op; + + gdev_vector_dopath_init(&state, (gx_device_vector *)pdev, + gx_path_type_fill, NULL); + if (pcpath->path_list == NULL) { + /* + * We think this should be never executed. + * This obsolete branch writes a clip path intersection + * as a set of rectangles computed by + * gx_cpath_intersect_path_slow. + * Those rectangles use coordinates rounded to pixels, + * therefore the precision may be unsatisfactory - + * see Bug 688407. + */ + gs_cpath_enum cenum; + + /* + * We have to break 'const' here because the clip path + * enumeration logic uses some internal mark bits. + * This is very unfortunate, but until we can come up with + * a better algorithm, it's necessary. + */ + gx_cpath_enum_init(&cenum, (gx_clip_path *) pcpath); + while ((pe_op = gx_cpath_enum_next(&cenum, vs)) > 0) + gdev_vector_dopath_segment(&state, pe_op, vs); + pprints1(s, "%s n\n", (pcpath->rule <= 0 ? "W" : "W*")); + if (pe_op < 0) + return pe_op; + } else { + gs_path_enum cenum; + + code = pdf_put_clip_path_list_elem(pdev, pcpath->path_list, &cenum, &state, vs); + if (code < 0) + return code; + } + } + } + pdev->clip_path_id = new_id; + return pdf_remember_clip_path(pdev, + (pdev->clip_path_id == pdev->no_clip_path_id ? NULL : pcpath)); +} + +/* + * Compute the scaling to ensure that user coordinates for a path are within + * Acrobat's range. Return true if scaling was needed. In this case, the + * CTM will be multiplied by *pscale, and all coordinates will be divided by + * *pscale. + */ +static bool +make_rect_scaling(const gx_device_pdf *pdev, const gs_fixed_rect *bbox, + double prescale, double *pscale) +{ + double bmin, bmax; + + bmin = min(bbox->p.x / pdev->scale.x, bbox->p.y / pdev->scale.y) * prescale; + bmax = max(bbox->q.x / pdev->scale.x, bbox->q.y / pdev->scale.y) * prescale; + if (bmin <= int2fixed(-MAX_USER_COORD) || + bmax > int2fixed(MAX_USER_COORD) + ) { + /* Rescale the path. */ + *pscale = max(bmin / int2fixed(-MAX_USER_COORD), + bmax / int2fixed(MAX_USER_COORD)); + return true; + } else { + *pscale = 1; + return false; + } +} + +/* + * Prepare a fill with a color anc a clipping path. + * Return 1 if there is nothing to paint. + * Changes *box to the clipping box. + */ +static int +prepare_fill_with_clip(gx_device_pdf *pdev, const gs_imager_state * pis, + gs_fixed_rect *box, bool have_path, + const gx_drawing_color * pdcolor, const gx_clip_path * pcpath) +{ + bool new_clip; + int code; + + /* + * Check for an empty clipping path. + */ + if (pcpath) { + gs_fixed_rect cbox; + + gx_cpath_outer_box(pcpath, &cbox); + if (cbox.p.x >= cbox.q.x || cbox.p.y >= cbox.q.y) + return 1; /* empty clipping path */ + *box = cbox; + } + new_clip = pdf_must_put_clip_path(pdev, pcpath); + if (have_path || pdev->context == PDF_IN_NONE || new_clip) { + if (new_clip) + code = pdf_unclip(pdev); + else + code = pdf_open_page(pdev, PDF_IN_STREAM); + if (code < 0) + return code; + } + code = pdf_prepare_fill(pdev, pis); + if (code < 0) + return code; + return pdf_put_clip_path(pdev, pcpath); +} + +/* -------------A local image converter device. -----------------------------*/ + +public_st_pdf_lcvd_t(); + +static int +lcvd_copy_color_shifted(gx_device * dev, + const byte * base, int sourcex, int sraster, gx_bitmap_id id, + int x, int y, int w, int h) +{ + pdf_lcvd_t *cvd = (pdf_lcvd_t *)dev; + + return cvd->std_copy_color((gx_device *)&cvd->mdev, base, sourcex, sraster, id, + x - cvd->mdev.mapped_x, y - cvd->mdev.mapped_y, w, h); +} + +static int +lcvd_fill_rectangle_shifted(gx_device *dev, int x, int y, int width, int height, gx_color_index color) +{ + pdf_lcvd_t *cvd = (pdf_lcvd_t *)dev; + + return cvd->std_fill_rectangle((gx_device *)&cvd->mdev, + x - cvd->mdev.mapped_x, y - cvd->mdev.mapped_y, width, height, color); +} +static int +lcvd_fill_rectangle_shifted2(gx_device *dev, int x, int y, int width, int height, gx_color_index color) +{ + pdf_lcvd_t *cvd = (pdf_lcvd_t *)dev; + int code; + + code = (*dev_proc(cvd->mask, fill_rectangle))((gx_device *)cvd->mask, + x - cvd->mdev.mapped_x, y - cvd->mdev.mapped_y, width, height, (gx_color_index)1); + if (code < 0) + return code; + return cvd->std_fill_rectangle((gx_device *)&cvd->mdev, + x - cvd->mdev.mapped_x, y - cvd->mdev.mapped_y, width, height, color); +} +static void +lcvd_get_clipping_box_shifted_from_mdev(gx_device *dev, gs_fixed_rect *pbox) +{ + fixed ofs; + pdf_lcvd_t *cvd = (pdf_lcvd_t *)dev; + + cvd->std_get_clipping_box((gx_device *)&cvd->mdev, pbox); + ofs = int2fixed(cvd->mdev.mapped_x); + pbox->p.x += ofs; + pbox->q.x += ofs; + ofs = int2fixed(cvd->mdev.mapped_y); + pbox->p.y += ofs; + pbox->q.y += ofs; +} +static int +lcvd_dev_spec_op(gx_device *pdev1, int dev_spec_op, + void *data, int size) +{ + switch (dev_spec_op) { + case gxdso_pattern_shading_area: + return 1; /* Request shading area. */ + case gxdso_pattern_can_accum: + case gxdso_pattern_start_accum: + case gxdso_pattern_finish_accum: + case gxdso_pattern_load: + case gxdso_pattern_is_cpath_accum: + case gxdso_pattern_shfill_doesnt_need_path: + case gxdso_pattern_handles_clip_path: + return 0; + } + return gx_default_dev_spec_op(pdev1, dev_spec_op, data, size); +} +static int +lcvd_close_device_with_writing(gx_device *pdev) +{ + /* Assuming 'mdev' is being closed before 'mask' - see gx_image3_end_image. */ + pdf_lcvd_t *cvd = (pdf_lcvd_t *)pdev; + int code, code1; + + code = pdf_dump_converted_image(cvd->pdev, cvd); + code1 = cvd->std_close_device((gx_device *)&cvd->mdev); + return code < 0 ? code : code1; +} + +static int +write_image(gx_device_pdf *pdev, gx_device_memory *mdev, gs_matrix *m) +{ + gs_image_t image; + pdf_image_writer writer; + const int sourcex = 0; + int code; + + if (m != NULL) + pdf_put_matrix(pdev, NULL, m, " cm\n"); + code = pdf_copy_color_data(pdev, mdev->base, sourcex, + mdev->raster, gx_no_bitmap_id, 0, 0, mdev->width, mdev->height, + &image, &writer, 2); + if (code == 1) + code = 0; /* Empty image. */ + else if (code == 0) + code = pdf_do_image(pdev, writer.pres, NULL, true); + return code; +} +static int +write_mask(gx_device_pdf *pdev, gx_device_memory *mdev, gs_matrix *m) +{ + const int sourcex = 0; + gs_id save_clip_id = pdev->clip_path_id; + bool save_skip_color = pdev->skip_colors; + int code; + + if (m != NULL) + pdf_put_matrix(pdev, NULL, m, " cm\n"); + pdev->clip_path_id = pdev->no_clip_path_id; + pdev->skip_colors = true; + code = gdev_pdf_copy_mono((gx_device *)pdev, mdev->base, sourcex, + mdev->raster, gx_no_bitmap_id, 0, 0, mdev->width, mdev->height, + gx_no_color_index, (gx_color_index)0); + pdev->clip_path_id = save_clip_id; + pdev->skip_colors = save_skip_color; + return code; +} + +static void +max_subimage_width(int width, byte *base, int x0, long count1, int *x1, long *count) +{ + long c = 0, c1 = count1 - 1; + int x = x0; + byte p = 1; /* The inverse of the previous bit. */ + byte r; /* The inverse of the current bit. */ + byte *q = base + (x / 8), m = 0x80 >> (x % 8); + + for (; x < width; x++) { + r = !(*q & m); + if (p != r) { + if (c >= c1) { + if (!r) + goto ex; /* stop before the upgrade. */ + } + c++; + } + p = r; + m >>= 1; + if (!m) { + m = 0x80; + q++; + } + } + if (p) + c++; /* Account the last downgrade. */ +ex: + *count = c; + *x1 = x; +} + +static void +compute_subimage(int width, int height, int raster, byte *base, + int x0, int y0, long MaxClipPathSize, int *x1, int *y1) +{ + int bytes = (width + 7) / 8; + /* Returns a semiopen range : [x0:x1)*[y0:y1). */ + if (x0 != 0) { + long count; + + /* A partial single scanline. */ + max_subimage_width(width, base + y0 * raster, x0, MaxClipPathSize / 4, x1, &count); + *y1 = y0; + } else { + int xx, y = y0, yy; + long count, count1 = MaxClipPathSize / 4; + + for(; y < height && count1 > 0; ) { + max_subimage_width(width, base + y * raster, 0, count1, &xx, &count); + if (xx < width) { + if (y == y0) { + /* Partial single scanline. */ + *y1 = y + 1; + *x1 = xx; + return; + } else { + /* Full lines before this scanline. */ + break; + } + } + count1 -= count; + yy = y + 1; + for (; yy < height; yy++) + if (memcmp(base + raster * y, base + raster * yy, bytes)) + break; + y = yy; + + } + *y1 = y; + *x1 = width; + } +} + +static int +image_line_to_clip(gx_device_pdf *pdev, byte *base, int x0, int x1, int y0, int y1, bool started) +{ /* returns the number of segments or error code. */ + int x = x0, xx; + byte *q = base + (x / 8), m = 0x80 >> (x % 8); + long c = 0; + + for (;;) { + /* Look for upgrade : */ + for (; x < x1; x++) { + if (*q & m) + break; + m >>= 1; + if (!m) { + m = 0x80; + q++; + } + } + if (x == x1) + return c; + xx = x; + /* Look for downgrade : */ + for (; x < x1; x++) { + if (!(*q & m)) + break; + m >>= 1; + if (!m) { + m = 0x80; + q++; + } + } + /* Found the interval [xx:x). */ + if (!started) { + stream_puts(pdev->strm, "n\n"); + started = true; + } + pprintld2(pdev->strm, "%ld %ld m ", xx, y0); + pprintld2(pdev->strm, "%ld %ld l ", x, y0); + pprintld2(pdev->strm, "%ld %ld l ", x, y1); + pprintld2(pdev->strm, "%ld %ld l h\n", xx, y1); + c += 4; + } + return c; +} + +static int +mask_to_clip(gx_device_pdf *pdev, int width, int height, + int raster, byte *base, int x0, int y0, int x1, int y1) +{ + int y, yy, code = 0, bytes = (width + 7) / 8; + bool has_segments = false; + + for (y = y0; y < y1 && code >= 0;) { + yy = y + 1; + if (x0 == 0) { + for (; yy < y1; yy++) + if (memcmp(base + raster * y, base + raster * yy, bytes)) + break; + } + code = image_line_to_clip(pdev, base + raster * y, x0, x1, y, yy, has_segments); + if (code > 0) + has_segments = true; + y = yy; + } + if (has_segments) + stream_puts(pdev->strm, "W n\n"); + return code < 0 ? code : has_segments ? 1 : 0; +} + +static int +write_subimage(gx_device_pdf *pdev, gx_device_memory *mdev, int x, int y, int x1, int y1) +{ + gs_image_t image; + pdf_image_writer writer; + /* expand in 1 pixel to provide a proper color interpolation */ + int X = max(0, x - 1); + int Y = max(0, y - 1); + int X1 = min(mdev->width, x1 + 1); + int Y1 = min(mdev->height, y1 + 1); + int code; + + code = pdf_copy_color_data(pdev, mdev->base + mdev->raster * Y, X, + mdev->raster, gx_no_bitmap_id, + X, Y, X1 - X, Y1 - Y, + &image, &writer, 2); + if (code < 0) + return code; + if (!writer.pres) + return 0; /* inline image. */ + return pdf_do_image(pdev, writer.pres, NULL, true); +} + +static int +write_image_with_clip(gx_device_pdf *pdev, pdf_lcvd_t *cvd) +{ + int x = 0, y = 0; + int code, code1; + + if (cvd->write_matrix) + pdf_put_matrix(pdev, NULL, &cvd->m, " cm q\n"); + for(;;) { + int x1, y1; + + compute_subimage(cvd->mask->width, cvd->mask->height, + cvd->mask->raster, cvd->mask->base, + x, y, max(pdev->MaxClipPathSize, 100), &x1, &y1); + code = mask_to_clip(pdev, + cvd->mask->width, cvd->mask->height, + cvd->mask->raster, cvd->mask->base, + x, y, x1, y1); + if (code < 0) + return code; + if (code > 0) { + code1 = write_subimage(pdev, &cvd->mdev, x, y, x1, y1); + if (code1 < 0) + return code1; + } + if (x1 >= cvd->mdev.width && y1 >= cvd->mdev.height) + break; + if (code > 0) + stream_puts(pdev->strm, "Q q\n"); + if (x1 == cvd->mask->width) { + x = 0; + y = y1; + } else { + x = x1; + y = y1; + } + } + if (cvd->write_matrix) + stream_puts(pdev->strm, "Q\n"); + return 0; +} + +int +pdf_dump_converted_image(gx_device_pdf *pdev, pdf_lcvd_t *cvd) +{ + int code = 0; + + if (!cvd->path_is_empty || cvd->has_background) { + if (!cvd->has_background) + stream_puts(pdev->strm, "W n\n"); + code = write_image(pdev, &cvd->mdev, (cvd->write_matrix ? &cvd->m : NULL)); + cvd->path_is_empty = true; + } else if (!cvd->mask_is_empty && pdev->PatternImagemask) { + /* Convert to imagemask with a pattern color. */ + /* See also use_image_as_pattern in gdevpdfi.c . */ + gs_imager_state s; + gs_pattern1_instance_t inst; + gs_id id = gs_next_ids(cvd->mdev.memory, 1); + cos_value_t v; + const pdf_resource_t *pres; + + memset(&s, 0, sizeof(s)); + s.ctm.xx = cvd->m.xx; + s.ctm.xy = cvd->m.xy; + s.ctm.yx = cvd->m.yx; + s.ctm.yy = cvd->m.yy; + s.ctm.tx = cvd->m.tx; + s.ctm.ty = cvd->m.ty; + memset(&inst, 0, sizeof(inst)); + inst.saved = (gs_state *)&s; /* HACK : will use s.ctm only. */ + inst.templat.PaintType = 1; + inst.templat.TilingType = 1; + inst.templat.BBox.p.x = inst.templat.BBox.p.y = 0; + inst.templat.BBox.q.x = cvd->mdev.width; + inst.templat.BBox.q.y = cvd->mdev.height; + inst.templat.XStep = (float)cvd->mdev.width; + inst.templat.YStep = (float)cvd->mdev.height; + + { + pattern_accum_param_s param; + param.pinst = (void *)&inst; + param.graphics_state = (void *)&s; + param.pinst_id = inst.id; + + code = (*dev_proc(pdev, dev_spec_op))((gx_device *)pdev, + gxdso_pattern_start_accum, ¶m, sizeof(pattern_accum_param_s)); + } + + if (code >= 0) { + stream_puts(pdev->strm, "W n\n"); + code = write_image(pdev, &cvd->mdev, NULL); + } + pres = pdev->accumulating_substream_resource; + if (code >= 0) { + pattern_accum_param_s param; + param.pinst = (void *)&inst; + param.graphics_state = (void *)&s; + param.pinst_id = inst.id; + + code = (*dev_proc(pdev, dev_spec_op))((gx_device *)pdev, + gxdso_pattern_finish_accum, ¶m, id); + } + if (code >= 0) + code = (*dev_proc(pdev, dev_spec_op))((gx_device *)pdev, + gxdso_pattern_load, &inst, id); + if (code >= 0) + code = pdf_cs_Pattern_colored(pdev, &v); + if (code >= 0) { + cos_value_write(&v, pdev); + pprintld1(pdev->strm, " cs /R%ld scn ", pdf_resource_id(pres)); + } + if (code >= 0) + code = write_mask(pdev, cvd->mask, (cvd->write_matrix ? &cvd->m : NULL)); + cvd->mask_is_empty = true; + } else if (!cvd->mask_is_empty && !pdev->PatternImagemask) { + /* Convert to image with a clipping path. */ + stream_puts(pdev->strm, "q\n"); + code = write_image_with_clip(pdev, cvd); + stream_puts(pdev->strm, "Q\n"); + } + if (code > 0) + code = (*dev_proc(&cvd->mdev, fill_rectangle))((gx_device *)&cvd->mdev, + 0, 0, cvd->mdev.width, cvd->mdev.height, (gx_color_index)0); + return code; +} +static int +lcvd_handle_fill_path_as_shading_coverage(gx_device *dev, + const gs_imager_state *pis, gx_path *ppath, + const gx_fill_params *params, + const gx_drawing_color *pdcolor, const gx_clip_path *pcpath) +{ + pdf_lcvd_t *cvd = (pdf_lcvd_t *)dev; + gx_device_pdf *pdev = (gx_device_pdf *)cvd->mdev.target; + int code; + + if (cvd->has_background) + return 0; + if (gx_path_is_null(ppath)) { + /* use the mask. */ + if (!cvd->path_is_empty) { + code = pdf_dump_converted_image(pdev, cvd); + if (code < 0) + return code; + stream_puts(pdev->strm, "Q q\n"); + dev_proc(&cvd->mdev, fill_rectangle) = lcvd_fill_rectangle_shifted2; + } + if (!cvd->mask_is_clean || !cvd->path_is_empty) { + code = (*dev_proc(cvd->mask, fill_rectangle))((gx_device *)cvd->mask, + 0, 0, cvd->mask->width, cvd->mask->height, (gx_color_index)0); + if (code < 0) + return code; + cvd->mask_is_clean = true; + } + cvd->path_is_empty = true; + cvd->mask_is_empty = false; + } else { + gs_matrix m; + + gs_make_translation(cvd->path_offset.x, cvd->path_offset.y, &m); + /* use the clipping. */ + if (!cvd->mask_is_empty) { + code = pdf_dump_converted_image(pdev, cvd); + if (code < 0) + return code; + stream_puts(pdev->strm, "Q q\n"); + dev_proc(&cvd->mdev, fill_rectangle) = lcvd_fill_rectangle_shifted; + cvd->mask_is_empty = true; + } + code = gdev_vector_dopath((gx_device_vector *)pdev, ppath, + gx_path_type_fill | gx_path_type_optimize, &m); + if (code < 0) + return code; + stream_puts(pdev->strm, "h\n"); + cvd->path_is_empty = false; + } + return 0; +} + +int +pdf_setup_masked_image_converter(gx_device_pdf *pdev, gs_memory_t *mem, const gs_matrix *m, pdf_lcvd_t **pcvd, + bool need_mask, int x, int y, int w, int h, bool write_on_close) +{ + int code; + gx_device_memory *mask = 0; + pdf_lcvd_t *cvd = *pcvd; + + if (cvd == NULL) { + cvd = gs_alloc_struct(mem, pdf_lcvd_t, &st_pdf_lcvd_t, "pdf_setup_masked_image_converter"); + if (cvd == NULL) + return_error(gs_error_VMerror); + *pcvd = cvd; + } + cvd->pdev = pdev; + gs_make_mem_device(&cvd->mdev, gdev_mem_device_for_bits(pdev->color_info.depth), + mem, 0, (gx_device *)pdev); + cvd->mdev.width = w; + cvd->mdev.height = h; + cvd->mdev.mapped_x = x; + cvd->mdev.mapped_y = y; + cvd->mdev.bitmap_memory = mem; + cvd->mdev.color_info = pdev->color_info; + cvd->path_is_empty = true; + cvd->mask_is_empty = true; + cvd->mask_is_clean = false; + cvd->has_background = false; + cvd->mask = 0; + cvd->write_matrix = true; + code = (*dev_proc(&cvd->mdev, open_device))((gx_device *)&cvd->mdev); + if (code < 0) + return code; + code = (*dev_proc(&cvd->mdev, fill_rectangle))((gx_device *)&cvd->mdev, + 0, 0, cvd->mdev.width, cvd->mdev.height, (gx_color_index)0); + if (code < 0) + return code; + if (need_mask) { + mask = gs_alloc_struct(mem, gx_device_memory, &st_device_memory, "pdf_setup_masked_image_converter"); + if (mask == NULL) + return_error(gs_error_VMerror); + cvd->mask = mask; + gs_make_mem_mono_device(mask, mem, (gx_device *)pdev); + mask->width = cvd->mdev.width; + mask->height = cvd->mdev.height; + mask->raster = gx_device_raster((gx_device *)mask, 1); + mask->bitmap_memory = mem; + code = (*dev_proc(mask, open_device))((gx_device *)mask); + if (code < 0) + return code; + if (write_on_close) { + code = (*dev_proc(mask, fill_rectangle))((gx_device *)mask, + 0, 0, mask->width, mask->height, (gx_color_index)0); + if (code < 0) + return code; + } + } + cvd->std_copy_color = dev_proc(&cvd->mdev, copy_color); + cvd->std_fill_rectangle = dev_proc(&cvd->mdev, fill_rectangle); + cvd->std_close_device = dev_proc(&cvd->mdev, close_device); + cvd->std_get_clipping_box = dev_proc(&cvd->mdev, get_clipping_box); + if (!write_on_close) { + /* Type 3 images will write to the mask directly. */ + dev_proc(&cvd->mdev, fill_rectangle) = (need_mask ? lcvd_fill_rectangle_shifted2 + : lcvd_fill_rectangle_shifted); + dev_proc(&cvd->mdev, get_clipping_box) = lcvd_get_clipping_box_shifted_from_mdev; + } else { + dev_proc(&cvd->mdev, fill_rectangle) = lcvd_fill_rectangle_shifted; + dev_proc(&cvd->mdev, get_clipping_box) = lcvd_get_clipping_box_shifted_from_mdev; + } + dev_proc(&cvd->mdev, copy_color) = lcvd_copy_color_shifted; + dev_proc(&cvd->mdev, dev_spec_op) = lcvd_dev_spec_op; + dev_proc(&cvd->mdev, fill_path) = lcvd_handle_fill_path_as_shading_coverage; + cvd->m = *m; + if (write_on_close) { + cvd->mdev.is_open = true; + if (mask) + mask->is_open = true; + dev_proc(&cvd->mdev, close_device) = lcvd_close_device_with_writing; + } + return 0; +} + +void +pdf_remove_masked_image_converter(gx_device_pdf *pdev, pdf_lcvd_t *cvd, bool need_mask) +{ + (*dev_proc(&cvd->mdev, close_device))((gx_device *)&cvd->mdev); + if (cvd->mask) { + (*dev_proc(cvd->mask, close_device))((gx_device *)cvd->mask); + gs_free_object(cvd->mask->memory, cvd->mask, "pdf_remove_masked_image_converter"); + } +} + +/* ------ Driver procedures ------ */ + +/* Fill a path. */ +int +gdev_pdf_fill_path(gx_device * dev, const gs_imager_state * pis, gx_path * ppath, + const gx_fill_params * params, + const gx_drawing_color * pdcolor, const gx_clip_path * pcpath) +{ + gx_device_pdf *pdev = (gx_device_pdf *) dev; + int code; + /* + * HACK: we fill an empty path in order to set the clipping path + * and the color for writing text. If it weren't for this, we + * could detect and skip empty paths before putting out the clip + * path or the color. We also clip with an empty path in order + * to advance currentpoint for show operations without actually + * drawing anything. + */ + bool have_path; + gs_fixed_rect box = {{0, 0}, {0, 0}}, box1; + + if (pdev->Eps2Write) { + gx_path_bbox(ppath, &box1); + if (box1.p.x != 0 || box1.p.y != 0 || box1.q.x != 0 || box1.q.y != 0){ + if (pcpath != 0) + rect_intersect(box1, pcpath->outer_box); + if (fixed2int(box1.p.x) < pdev->BBox.p.x) + pdev->BBox.p.x = fixed2int(box1.p.x); + if (fixed2int(box1.p.y) < pdev->BBox.p.y) + pdev->BBox.p.y = fixed2int(box1.p.y); + if (fixed2int(box1.q.x) > pdev->BBox.q.x) + pdev->BBox.q.x = fixed2int(box1.q.x); + if (fixed2int(box1.q.y) > pdev->BBox.q.y) + pdev->BBox.q.y = fixed2int(box1.q.y); + } + if (pdev->AccumulatingBBox) + return 0; + } + have_path = !gx_path_is_void(ppath); + if (!have_path && !pdev->vg_initial_set) { + /* See lib/gs_pdfwr.ps about "initial graphic state". */ + pdf_prepare_initial_viewer_state(pdev, pis); + pdf_reset_graphics(pdev); + return 0; + } + if (have_path) { + code = gx_path_bbox(ppath, &box); + if (code < 0) + return code; + } + box1 = box; + + code = prepare_fill_with_clip(pdev, pis, &box, have_path, pdcolor, pcpath); + if (code == gs_error_rangecheck) { + /* Fallback to the default implermentation for handling + a transparency with CompatibilityLevel<=1.3 . */ + return gx_default_fill_path((gx_device *)pdev, pis, ppath, params, pdcolor, pcpath); + } + if (code < 0) + return code; + if (code == 1) + return 0; /* Nothing to paint. */ + if (!have_path) + return 0; + code = pdf_setfillcolor((gx_device_vector *)pdev, pis, pdcolor); + if (code == gs_error_rangecheck) { + const bool convert_to_image = ((pdev->CompatibilityLevel <= 1.2 || + pdev->params.ColorConversionStrategy != ccs_LeaveColorUnchanged) && + gx_dc_is_pattern2_color(pdcolor)); + + if (!convert_to_image) { + /* Fallback to the default implermentation for handling + a shading with CompatibilityLevel<=1.2 . */ + return gx_default_fill_path(dev, pis, ppath, params, pdcolor, pcpath); + } else { + /* Convert a shading into a bitmap + with CompatibilityLevel<=1.2 . */ + pdf_lcvd_t cvd, *pcvd = &cvd; + int sx, sy; + gs_fixed_rect bbox, bbox1; + bool need_mask = gx_dc_pattern2_can_overlap(pdcolor); + gs_matrix m, save_ctm = ctm_only(pis), ms, msi, mm; + gs_int_point rect_size; + /* double scalex = 1.9, scaley = 1.4; debug purpose only. */ + double scale, scalex, scaley; + int log2_scale_x = 0, log2_scale_y = 0; + gx_drawing_color dc = *pdcolor; + gs_pattern2_instance_t pi = *(gs_pattern2_instance_t *)dc.ccolor.pattern; + gs_state *pgs = gs_state_copy(pi.saved, gs_state_memory(pi.saved)); + + if (pgs == NULL) + return_error(gs_error_VMerror); + dc.ccolor.pattern = (gs_pattern_instance_t *)π + pi.saved = pgs; + code = gx_path_bbox(ppath, &bbox); + if (code < 0) + return code; + rect_intersect(bbox, box); + code = gx_dc_pattern2_get_bbox(pdcolor, &bbox1); + if (code < 0) + return code; + if (code) + rect_intersect(bbox, bbox1); + if (bbox.p.x >= bbox.q.x || bbox.p.y >= bbox.q.y) + return 0; + sx = fixed2int(bbox.p.x); + sy = fixed2int(bbox.p.y); + gs_make_identity(&m); + rect_size.x = fixed2int(bbox.q.x + fixed_half) - sx; + rect_size.y = fixed2int(bbox.q.y + fixed_half) - sy; + if (rect_size.x == 0 || rect_size.y == 0) + return 0; + m.tx = (float)sx; + m.ty = (float)sy; + cvd.path_offset.x = sx; + cvd.path_offset.y = sy; + scale = (double)rect_size.x * rect_size.y * pdev->color_info.num_components / + pdev->MaxShadingBitmapSize; + if (scale > 1) { + /* This section (together with the call to 'path_scale' below) + sets up a downscaling when converting the shading into bitmap. + We used floating point numbers to debug it, but in production + we prefer to deal only with integers being powers of 2 + in order to avoid possible distorsions when scaling paths. + */ + log2_scale_x = log2_scale_y = ilog2((int)ceil(sqrt(scale))); + if ((double)(1 << log2_scale_x) * (1 << log2_scale_y) < scale) + log2_scale_y++; + if ((double)(1 << log2_scale_x) * (1 << log2_scale_y) < scale) + log2_scale_x++; + scalex = (double)(1 << log2_scale_x); + scaley = (double)(1 << log2_scale_y); + rect_size.x = (int)floor(rect_size.x / scalex + 0.5); + rect_size.y = (int)floor(rect_size.y / scaley + 0.5); + gs_make_scaling(1.0 / scalex, 1.0 / scaley, &ms); + gs_make_scaling(scalex, scaley, &msi); + gs_matrix_multiply(&msi, &m, &m); + gs_matrix_multiply(&ctm_only(pis), &ms, &mm); + gs_setmatrix((gs_state *)pis, &mm); + gs_matrix_multiply(&ctm_only((gs_imager_state *)pgs), &ms, &mm); + gs_setmatrix((gs_state *)pgs, &mm); + sx = fixed2int(bbox.p.x / (int)scalex); + sy = fixed2int(bbox.p.y / (int)scaley); + cvd.path_offset.x = sx; /* m.tx / scalex */ + cvd.path_offset.y = sy; + } + code = pdf_setup_masked_image_converter(pdev, pdev->memory, &m, &pcvd, need_mask, sx, sy, + rect_size.x, rect_size.y, false); + pcvd->has_background = gx_dc_pattern2_has_background(pdcolor); + stream_puts(pdev->strm, "q\n"); + if (code >= 0) { + code = gdev_vector_dopath((gx_device_vector *)pdev, ppath, + gx_path_type_clip, NULL); + if (code >= 0) + stream_puts(pdev->strm, (params->rule < 0 ? "W n\n" : "W* n\n")); + } + pdf_put_matrix(pdev, NULL, &cvd.m, " cm q\n"); + cvd.write_matrix = false; + if (code >= 0) + code = gs_shading_do_fill_rectangle(pi.templat.Shading, + NULL, (gx_device *)&cvd.mdev, (gs_imager_state *)pgs, !pi.shfill); + if (code >= 0) + code = pdf_dump_converted_image(pdev, &cvd); + stream_puts(pdev->strm, "Q Q\n"); + pdf_remove_masked_image_converter(pdev, &cvd, need_mask); + gs_setmatrix((gs_state *)pis, &save_ctm); + gs_state_free(pgs); + return code; + } + } + if (code < 0) + return code; + { + stream *s = pdev->strm; + double scale; + gs_matrix smat; + gs_matrix *psmat = NULL; + + if (pcpath) { + rect_intersect(box1, box); + if (box1.p.x > box1.q.x || box1.p.y > box1.q.y) + return 0; /* outside the clipping path */ + } + if (params->flatness != pdev->state.flatness) { + pprintg1(s, "%g i\n", params->flatness); + pdev->state.flatness = params->flatness; + } + if (make_rect_scaling(pdev, &box1, 1.0, &scale)) { + gs_make_scaling(pdev->scale.x * scale, pdev->scale.y * scale, + &smat); + pdf_put_matrix(pdev, "q ", &smat, "cm\n"); + psmat = &smat; + } + gdev_vector_dopath((gx_device_vector *)pdev, ppath, + gx_path_type_fill | gx_path_type_optimize, + psmat); + stream_puts(s, (params->rule < 0 ? "f\n" : "f*\n")); + if (psmat) + stream_puts(s, "Q\n"); + } + return 0; +} + +/* Stroke a path. */ +int +gdev_pdf_stroke_path(gx_device * dev, const gs_imager_state * pis, + gx_path * ppath, const gx_stroke_params * params, + const gx_drawing_color * pdcolor, const gx_clip_path * pcpath) +{ + gx_device_pdf *pdev = (gx_device_pdf *) dev; + stream *s; + int code; + double scale, path_scale; + bool set_ctm; + gs_matrix mat; + double prescale = 1; + gs_fixed_rect bbox; + + if (gx_path_is_void(ppath)) + return 0; /* won't mark the page */ + if (pdf_must_put_clip_path(pdev, pcpath)) + code = pdf_unclip(pdev); + else if ((pdev->last_charpath_op & TEXT_DO_FALSE_CHARPATH) && ppath->current_subpath && + (ppath->last_charpath_segment == ppath->current_subpath->last) && !pdev->ForOPDFRead) { + bool hl_color = pdf_can_handle_hl_color((gx_device_vector *)pdev, pis, pdcolor); + const gs_imager_state *pis_for_hl_color = (hl_color ? pis : NULL); + + if (pdf_modify_text_render_mode(pdev->text->text_state, 1)) { + /* Set the colour for the stroke */ + code = pdf_reset_color(pdev, pis_for_hl_color, pdcolor, &pdev->saved_stroke_color, + &pdev->stroke_used_process_color, &psdf_set_stroke_color_commands); + if (code == 0) { + s = pdev->strm; + /* Text is emitted scaled so that the CTM is an identity matrix, the line width + * needs to be scaled to match otherwise we will get the default, or the current + * width scaled by the CTM before the text, either of which would be wrong. + */ + scale = 72 / pdev->HWResolution[0]; + scale *= pis->ctm.xx; + pprintg1(s, "%g w\n", (pis->line_params.half_width * 2) * (float)scale); + /* Some trickery here. We have altered the colour, text render mode and linewidth, + * we don't want those to persist. By switching to a stream context we will flush the + * pending text. This has the beneficial side effect of executing a grestore. So + * everything works out neatly. + */ + code = pdf_open_page(pdev, PDF_IN_STREAM); + return(code); + } + } + /* Can only get here if any of the above steps fail, in which case we proceed to + * emit the charpath as a normal path, and stroke it. + */ + code = pdf_open_page(pdev, PDF_IN_STREAM); + } else + code = pdf_open_page(pdev, PDF_IN_STREAM); + if (code < 0) + return code; + code = pdf_prepare_stroke(pdev, pis); + if (code == gs_error_rangecheck) { + /* Fallback to the default implermentation for handling + a transparency with CompatibilityLevel<=1.3 . */ + return gx_default_stroke_path((gx_device *)dev, pis, ppath, params, pdcolor, pcpath); + } + if (code < 0) + return code; + code = pdf_put_clip_path(pdev, pcpath); + if (code < 0) + return code; + /* + * If the CTM is not uniform, stroke width depends on angle. + * We'd like to avoid resetting the CTM, so we check for uniform + * CTMs explicitly. Note that in PDF, unlike PostScript, it is + * the CTM at the time of the stroke operation, not the CTM at + * the time the path was constructed, that is used for transforming + * the points of the path; so if we have to reset the CTM, we must + * do it before constructing the path, and inverse-transform all + * the coordinates. + */ + set_ctm = (bool)gdev_vector_stroke_scaling((gx_device_vector *)pdev, + pis, &scale, &mat); + if (set_ctm && ((pis->ctm.xx == 0 && pis->ctm.xy == 0) || + (pis->ctm.yx == 0 && pis->ctm.yy == 0))) { + /* Acrobat Reader 5 and Adobe Reader 6 issues + the "Wrong operand type" error with matrices, which have 3 zero coefs. + Besides that, we found that Acrobat Reader 4, Acrobat Reader 5 + and Adobe Reader 6 all store the current path in user space + and apply CTM in the time of stroking - See the bug 687901. + Therefore a precise conversion of Postscript to PDF isn't possible in this case. + Adobe viewers render a line with a constant width instead. + At last, with set_ctm == true we need the inverse matrix in + gdev_vector_dopath. Therefore we exclude projection matrices + (see bug 688363). */ + set_ctm = false; + scale = fabs(pis->ctm.xx + pis->ctm.xy + pis->ctm.yx + pis->ctm.yy) /* Using the non-zero coeff. */ + / sqrt(2); /* Empirically from Adobe. */ + } + if (set_ctm) { + /* + * We want a scaling factor that will bring the largest reasonable + * user coordinate within bounds. We choose a factor based on the + * minor axis of the transformation. Thanks to Raph Levien for + * the following formula. + */ + double a = mat.xx, b = mat.xy, c = mat.yx, d = mat.yy; + double u = fabs(a * d - b * c); + double v = a * a + b * b + c * c + d * d; + double minor = (sqrt(v + 2 * u) - sqrt(v - 2 * u)) * 0.5; + + prescale = (minor == 0 || minor > 1 ? 1 : 1 / minor); + } + gx_path_bbox(ppath, &bbox); + { + /* Check whether a painting appears inside the clipping box. + Doing so after writing the clipping path due to /SP pdfmark + uses a special hack with painting outside the clipping box + for synchronizing the clipping path (see lib/gs_pdfwr.ps). + That hack appeared because there is no way to pass + the imager state through gdev_pdf_put_params, + which pdfmark is implemented with. + */ + gs_fixed_rect clip_box, stroke_bbox = bbox; + gs_point d0, d1; + gs_fixed_point p0, p1; + fixed bbox_expansion_x, bbox_expansion_y; + + gs_distance_transform(pis->line_params.half_width, 0, &ctm_only(pis), &d0); + gs_distance_transform(0, pis->line_params.half_width, &ctm_only(pis), &d1); + p0.x = float2fixed(any_abs(d0.x)); + p0.y = float2fixed(any_abs(d0.y)); + p1.x = float2fixed(any_abs(d1.x)); + p1.y = float2fixed(any_abs(d1.y)); + bbox_expansion_x = max(p0.x, p1.x) + fixed_1 * 2; + bbox_expansion_y = max(p0.y, p1.y) + fixed_1 * 2; + stroke_bbox.p.x -= bbox_expansion_x; + stroke_bbox.p.y -= bbox_expansion_y; + stroke_bbox.q.x += bbox_expansion_x; + stroke_bbox.q.y += bbox_expansion_y; + gx_cpath_outer_box(pcpath, &clip_box); + rect_intersect(stroke_bbox, clip_box); + if (stroke_bbox.q.x < stroke_bbox.p.x || stroke_bbox.q.y < stroke_bbox.p.y) + return 0; + } + if (make_rect_scaling(pdev, &bbox, prescale, &path_scale)) { + scale /= path_scale; + if (set_ctm) + gs_matrix_scale(&mat, path_scale, path_scale, &mat); + else { + gs_make_scaling(path_scale, path_scale, &mat); + set_ctm = true; + } + } + code = gdev_vector_prepare_stroke((gx_device_vector *)pdev, pis, params, + pdcolor, scale); + if (code < 0) + return gx_default_stroke_path(dev, pis, ppath, params, pdcolor, + pcpath); + if (!pdev->HaveStrokeColor) + pdev->saved_fill_color = pdev->saved_stroke_color; + if (set_ctm) + pdf_put_matrix(pdev, "q ", &mat, "cm\n"); + code = gdev_vector_dopath((gx_device_vector *)pdev, ppath, + gx_path_type_stroke | gx_path_type_optimize, + (set_ctm ? &mat : (const gs_matrix *)0)); + if (code < 0) + return code; + s = pdev->strm; + stream_puts(s, (code ? "s" : "S")); + stream_puts(s, (set_ctm ? " Q\n" : "\n")); + if (pdev->Eps2Write) { + pdev->AccumulatingBBox++; + code = gx_default_stroke_path(dev, pis, ppath, params, pdcolor, + pcpath); + pdev->AccumulatingBBox--; + if (code < 0) + return code; + } + return 0; +} + +/* + The fill_rectangle_hl_color device method. + See gxdevcli.h about return codes. + */ +int +gdev_pdf_fill_rectangle_hl_color(gx_device *dev, const gs_fixed_rect *rect, + const gs_imager_state *pis, const gx_drawing_color *pdcolor, + const gx_clip_path *pcpath) +{ + int code; + gs_fixed_rect box1 = *rect, box = box1; + gx_device_pdf *pdev = (gx_device_pdf *) dev; + double scale; + gs_matrix smat; + gs_matrix *psmat = NULL; + const bool convert_to_image = (pdev->CompatibilityLevel <= 1.2 && + gx_dc_is_pattern2_color(pdcolor)); + + if (rect->p.x == rect->q.x) + return 0; + if (!convert_to_image) { + code = prepare_fill_with_clip(pdev, pis, &box, true, pdcolor, pcpath); + if (code < 0) + return code; + if (code == 1) + return 0; /* Nothing to paint. */ + code = pdf_setfillcolor((gx_device_vector *)pdev, pis, pdcolor); + if (code < 0) + return code; + if (pcpath) + rect_intersect(box1, box); + if (box1.p.x > box1.q.x || box1.p.y > box1.q.y) + return 0; /* outside the clipping path */ + if (make_rect_scaling(pdev, &box1, 1.0, &scale)) { + gs_make_scaling(pdev->scale.x * scale, pdev->scale.y * scale, &smat); + pdf_put_matrix(pdev, "q ", &smat, "cm\n"); + psmat = &smat; + } + pprintg4(pdev->strm, "%g %g %g %g re f\n", + fixed2float(box1.p.x) / scale, fixed2float(box1.p.y) / scale, + fixed2float(box1.q.x - box1.p.x) / scale, fixed2float(box1.q.y - box1.p.y) / scale); + if (psmat) + stream_puts(pdev->strm, "Q\n"); + if (pdev->Eps2Write) { + gs_rect *Box; + + if (!pdev->accumulating_charproc) + Box = &pdev->BBox; + else + Box = &pdev->charproc_BBox; + + if (fixed2float(box1.p.x) / (pdev->HWResolution[0] / 72.0) < Box->p.x) + Box->p.x = fixed2float(box1.p.x) / (pdev->HWResolution[0] / 72.0); + if (fixed2float(box1.p.y) / (pdev->HWResolution[1] / 72.0) < Box->p.y) + Box->p.y = fixed2float(box1.p.y) / (pdev->HWResolution[1] / 72.0); + if (fixed2float(box1.q.x) / (pdev->HWResolution[0] / 72.0) > Box->q.x) + Box->q.x = fixed2float(box1.q.x) / (pdev->HWResolution[0] / 72.0); + if (fixed2float(box1.q.y) / (pdev->HWResolution[1] / 72.0) > Box->q.y) + Box->q.y = fixed2float(box1.q.y) / (pdev->HWResolution[1] / 72.0); + } + return 0; + } else { + gx_fill_params params; + gx_path path; + + params.rule = 1; /* Not important because the path is a rectange. */ + params.adjust.x = params.adjust.y = 0; + params.flatness = pis->flatness; + gx_path_init_local(&path, pis->memory); + code = gx_path_add_rectangle(&path, rect->p.x, rect->p.y, rect->q.x, rect->q.y); + if (code < 0) + return code; + code = gdev_pdf_fill_path(dev, pis, &path, ¶ms, pdcolor, pcpath); + if (code < 0) + return code; + gx_path_free(&path, "gdev_pdf_fill_rectangle_hl_color"); + return code; + + } +} + +int +gdev_pdf_fillpage(gx_device *dev, gs_imager_state * pis, gx_device_color *pdevc) +{ + gx_device_pdf *pdev = (gx_device_pdf *) dev; + int bottom = (pdev->ResourcesBeforeUsage ? 1 : 0); + + if (gx_dc_pure_color(pdevc) == pdev->white && !is_in_page(pdev) && pdev->sbstack_depth <= bottom) { + /* PDF doesn't need to erase the page if its plain white */ + return 0; + } + else + return gx_default_fillpage(dev, pis, pdevc); +} |