/* Copyright (C) 2000 artofcode LLC. All rights reserved. This program 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; either version 2 of the License, or (at your option) any later version. This program 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: gdevpdfb.c,v 1.6.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */ /* Low-level bitmap image handling for PDF-writing driver */ #include "string_.h" #include "gx.h" #include "gserrors.h" #include "gdevpdfx.h" #include "gdevpdff.h" /* for synthesized bitmap fonts */ #include "gdevpdfg.h" #include "gdevpdfo.h" /* for data stream */ #include "gxcspace.h" /* We need this color space type for constructing temporary color spaces. */ extern const gs_color_space_type gs_color_space_type_Indexed; /* ---------------- Utilities ---------------- */ /* Fill in the image parameters for a bitmap image. */ private void pdf_make_bitmap_image(gs_image_t * pim, int x, int y, int w, int h) { pim->Width = w; pim->Height = h; pdf_make_bitmap_matrix(&pim->ImageMatrix, x, y, w, h, h); } /* ---------------- Driver procedures ---------------- */ /* Copy a mask bitmap. for_pattern = -1 means put the image in-line, */ /* 1 means put the image in a resource. */ private int pdf_copy_mask_data(gx_device_pdf * pdev, const byte * base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h, gs_image_t *pim, pdf_image_writer *piw, int for_pattern) { ulong nbytes; int code; const byte *row_base; int row_step; long pos; bool in_line; gs_image_t_init_mask(pim, true); pdf_make_bitmap_image(pim, x, y, w, h); nbytes = ((ulong)w * h + 7) / 8; if (for_pattern) { /* * Patterns must be emitted in order of increasing user Y, i.e., * the opposite of PDF's standard image order. */ row_base = base + (h - 1) * raster; row_step = -raster; in_line = for_pattern < 0; } else { row_base = base; row_step = raster; in_line = nbytes <= MAX_INLINE_IMAGE_BYTES; pdf_put_image_matrix(pdev, &pim->ImageMatrix, 1.0); /* * Check whether we've already made an XObject resource for this * image. */ if (id != gx_no_bitmap_id) { piw->pres = pdf_find_resource_by_gs_id(pdev, resourceXObject, id); if (piw->pres) return 0; } } /* * We have to be able to control whether to put Pattern images in line, * to avoid trying to create an XObject resource while we're in the * middle of writing a Pattern resource. */ if (for_pattern < 0) stream_puts(pdev->strm, "q "); if ((code = pdf_begin_write_image(pdev, piw, id, w, h, NULL, in_line)) < 0 || (code = psdf_setup_lossless_filters((gx_device_psdf *) pdev, &piw->binary, (gs_pixel_image_t *)pim)) < 0 || (code = pdf_begin_image_data(pdev, piw, (const gs_pixel_image_t *)pim, NULL)) < 0 ) return code; pos = stell(pdev->streams.strm); pdf_copy_mask_bits(piw->binary.strm, row_base, sourcex, row_step, w, h, 0); cos_stream_add_since(piw->data, pos); pdf_end_image_binary(pdev, piw, piw->height); return pdf_end_write_image(pdev, piw); } /* Copy a monochrome bitmap or mask. */ private int pdf_copy_mono(gx_device_pdf *pdev, const byte *base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h, gx_color_index zero, gx_color_index one, const gx_clip_path *pcpath) { int code; gs_color_space cs; cos_value_t cs_value; cos_value_t *pcsvalue; byte palette[sizeof(gx_color_index) * 2]; gs_image_t image; pdf_image_writer writer; pdf_stream_position_t ipos; pdf_resource_t *pres = 0; byte invert = 0; bool in_line = false; long pos; /* Update clipping. */ if (pdf_must_put_clip_path(pdev, pcpath)) { code = pdf_open_page(pdev, PDF_IN_STREAM); if (code < 0) return code; pdf_put_clip_path(pdev, pcpath); } /* We have 3 cases: mask, inverse mask, and solid. */ if (zero == gx_no_color_index) { if (one == gx_no_color_index) return 0; /* If a mask has an id, assume it's a character. */ if (id != gx_no_bitmap_id && sourcex == 0) { pdf_set_pure_color(pdev, one, &pdev->fill_color, &psdf_set_fill_color_commands); pres = pdf_find_resource_by_gs_id(pdev, resourceCharProc, id); if (pres == 0) { /* Define the character in an embedded font. */ pdf_char_proc_t *pcp; int y_offset; int max_y_offset = (pdev->open_font == 0 ? 0 : pdev->open_font->max_y_offset); gs_image_t_init_mask(&image, false); invert = 0xff; pdf_make_bitmap_image(&image, x, y, w, h); y_offset = image.ImageMatrix.ty - (int)(pdev->text.current.y + 0.5); if (x < pdev->text.current.x || y_offset < -max_y_offset || y_offset > max_y_offset ) y_offset = 0; /* * The Y axis of the text matrix is inverted, * so we need to negate the Y offset appropriately. */ code = pdf_begin_char_proc(pdev, w, h, 0, y_offset, id, &pcp, &ipos); if (code < 0) return code; y_offset = -y_offset; pprintd3(pdev->strm, "0 0 0 %d %d %d d1\n", y_offset, w, h + y_offset); pprintd3(pdev->strm, "%d 0 0 %d 0 %d cm\n", w, h, y_offset); code = pdf_begin_write_image(pdev, &writer, gs_no_id, w, h, NULL, true); if (code < 0) return code; pcp->rid = id; pres = (pdf_resource_t *) pcp; goto wr; } pdf_make_bitmap_matrix(&image.ImageMatrix, x, y, w, h, h); goto rx; } pdf_set_pure_color(pdev, one, &pdev->fill_color, &psdf_set_fill_color_commands); gs_image_t_init_mask(&image, false); invert = 0xff; } else if (one == gx_no_color_index) { gs_image_t_init_mask(&image, false); pdf_set_pure_color(pdev, zero, &pdev->fill_color, &psdf_set_fill_color_commands); } else if (zero == pdev->black && one == pdev->white) { gs_cspace_init_DeviceGray(&cs); gs_image_t_init(&image, &cs); } else if (zero == pdev->white && one == pdev->black) { gs_cspace_init_DeviceGray(&cs); gs_image_t_init(&image, &cs); invert = 0xff; } else { /* * We think this code is never executed when interpreting PostScript * or PDF: the library never uses monobit non-mask images * internally, and high-level images don't go through this code. * However, we still want the code to work. */ gs_color_space cs_base; gx_color_index c[2]; int i, j; int ncomp = pdev->color_info.num_components; byte *p; code = pdf_cspace_init_Device(&cs_base, ncomp); if (code < 0) return code; c[0] = psdf_adjust_color_index((gx_device_vector *)pdev, zero); c[1] = psdf_adjust_color_index((gx_device_vector *)pdev, one); gs_cspace_init(&cs, &gs_color_space_type_Indexed, NULL); cs.params.indexed.base_space = *(gs_direct_color_space *)&cs_base; cs.params.indexed.hival = 1; p = palette; for (i = 0; i < 2; ++i) for (j = ncomp - 1; j >= 0; --j) *p++ = (byte)(c[i] >> (j * 8)); cs.params.indexed.lookup.table.data = palette; cs.params.indexed.lookup.table.size = p - palette; cs.params.indexed.use_proc = false; gs_image_t_init(&image, &cs); image.BitsPerComponent = 1; } pdf_make_bitmap_image(&image, x, y, w, h); { ulong nbytes = (ulong) ((w + 7) >> 3) * h; in_line = nbytes <= MAX_INLINE_IMAGE_BYTES; if (in_line) pdf_put_image_matrix(pdev, &image.ImageMatrix, 1.0); code = pdf_open_page(pdev, PDF_IN_STREAM); if (code < 0) return code; code = pdf_begin_write_image(pdev, &writer, gs_no_id, w, h, NULL, in_line); if (code < 0) return code; } wr: if (image.ImageMask) pcsvalue = NULL; else { code = pdf_color_space(pdev, &cs_value, &cs, &writer.pin->color_spaces, in_line); if (code < 0) return code; pcsvalue = &cs_value; } /* * There are 3 different cases at this point: * - Writing an in-line image (pres == 0, writer.pres == 0); * - Writing an XObject image (pres == 0, writer.pres != 0); * - Writing the image for a CharProc (pres != 0). * We handle them with in-line code followed by a switch, * rather than making the shared code into a procedure, * simply because there would be an awful lot of parameters * that would need to be passed. */ if (pres) { /* * Always use CCITTFax 2-D for character bitmaps. It takes less * space to invert the data with Decode than to set BlackIs1. */ float d0 = image.Decode[0]; image.Decode[0] = image.Decode[1]; image.Decode[1] = d0; psdf_CFE_binary(&writer.binary, image.Width, image.Height, true); invert ^= 0xff; } else { /* Use the Distiller compression parameters. */ psdf_setup_image_filters((gx_device_psdf *) pdev, &writer.binary, (gs_pixel_image_t *)&image, NULL, NULL); } pdf_begin_image_data(pdev, &writer, (const gs_pixel_image_t *)&image, pcsvalue); pos = stell(pdev->streams.strm); code = pdf_copy_mask_bits(writer.binary.strm, base, sourcex, raster, w, h, invert); if (code < 0) return code; code = cos_stream_add_since(writer.data, pos); pdf_end_image_binary(pdev, &writer, writer.height); if (!pres) { switch ((code = pdf_end_write_image(pdev, &writer))) { default: /* error */ return code; case 1: return 0; case 0: return pdf_do_image(pdev, writer.pres, &image.ImageMatrix, true); } } writer.end_string = ""; /* no Q */ switch ((code = pdf_end_write_image(pdev, &writer))) { default: /* error */ return code; case 0: /* not possible */ return_error(gs_error_Fatal); case 1: break; } code = pdf_end_char_proc(pdev, &ipos); if (code < 0) return code; rx:{ gs_matrix imat; imat = image.ImageMatrix; imat.xx /= w; imat.xy /= h; imat.yx /= w; imat.yy /= h; return pdf_do_char_image(pdev, (const pdf_char_proc_t *)pres, &imat); } } int gdev_pdf_copy_mono(gx_device * dev, const byte * base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h, gx_color_index zero, gx_color_index one) { gx_device_pdf *pdev = (gx_device_pdf *) dev; if (w <= 0 || h <= 0) return 0; return pdf_copy_mono(pdev, base, sourcex, raster, id, x, y, w, h, zero, one, NULL); } /* Copy a color bitmap. for_pattern = -1 means put the image in-line, */ /* 1 means put the image in a resource. */ private int pdf_copy_color_data(gx_device_pdf * pdev, const byte * base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h, gs_image_t *pim, pdf_image_writer *piw, int for_pattern) { int depth = pdev->color_info.depth; int bytes_per_pixel = depth >> 3; gs_color_space cs; cos_value_t cs_value; ulong nbytes; int code = pdf_cspace_init_Device(&cs, bytes_per_pixel); const byte *row_base; int row_step; long pos; bool in_line; if (code < 0) return code; /* can't happen */ gs_image_t_init(pim, &cs); pdf_make_bitmap_image(pim, x, y, w, h); pim->BitsPerComponent = 8; nbytes = (ulong)w * bytes_per_pixel * h; if (for_pattern) { /* * Patterns must be emitted in order of increasing user Y, i.e., * the opposite of PDF's standard image order. */ row_base = base + (h - 1) * raster; row_step = -raster; in_line = for_pattern < 0; } else { row_base = base; row_step = raster; in_line = nbytes <= MAX_INLINE_IMAGE_BYTES; pdf_put_image_matrix(pdev, &pim->ImageMatrix, 1.0); /* * Check whether we've already made an XObject resource for this * image. */ if (id != gx_no_bitmap_id) { piw->pres = pdf_find_resource_by_gs_id(pdev, resourceXObject, id); if (piw->pres) return 0; } } /* * We have to be able to control whether to put Pattern images in line, * to avoid trying to create an XObject resource while we're in the * middle of writing a Pattern resource. */ if (for_pattern < 0) stream_puts(pdev->strm, "q "); if ((code = pdf_begin_write_image(pdev, piw, id, w, h, NULL, in_line)) < 0 || (code = pdf_color_space(pdev, &cs_value, &cs, &piw->pin->color_spaces, in_line)) < 0 || (code = psdf_setup_lossless_filters((gx_device_psdf *) pdev, &piw->binary, (gs_pixel_image_t *)pim)) < 0 || (code = pdf_begin_image_data(pdev, piw, (const gs_pixel_image_t *)pim, &cs_value)) < 0 ) return code; pos = stell(pdev->streams.strm); pdf_copy_color_bits(piw->binary.strm, row_base, sourcex, row_step, w, h, bytes_per_pixel); cos_stream_add_since(piw->data, pos); pdf_end_image_binary(pdev, piw, piw->height); return pdf_end_write_image(pdev, piw); } int gdev_pdf_copy_color(gx_device * dev, const byte * base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h) { gx_device_pdf *pdev = (gx_device_pdf *) dev; gs_image_t image; pdf_image_writer writer; int code; if (w <= 0 || h <= 0) return 0; code = pdf_open_page(pdev, PDF_IN_STREAM); if (code < 0) return code; /* Make sure we aren't being clipped. */ pdf_put_clip_path(pdev, NULL); code = pdf_copy_color_data(pdev, base, sourcex, raster, id, x, y, w, h, &image, &writer, 0); switch (code) { default: return code; /* error */ case 1: return 0; case 0: return pdf_do_image(pdev, writer.pres, NULL, true); } } /* Fill a mask. */ int gdev_pdf_fill_mask(gx_device * dev, const byte * data, int data_x, int raster, gx_bitmap_id id, int x, int y, int width, int height, const gx_drawing_color * pdcolor, int depth, gs_logical_operation_t lop, const gx_clip_path * pcpath) { gx_device_pdf *pdev = (gx_device_pdf *) dev; if (width <= 0 || height <= 0) return 0; if (depth > 1 || !gx_dc_is_pure(pdcolor) != 0) return gx_default_fill_mask(dev, data, data_x, raster, id, x, y, width, height, pdcolor, depth, lop, pcpath); return pdf_copy_mono(pdev, data, data_x, raster, id, x, y, width, height, gx_no_color_index, gx_dc_pure_color(pdcolor), pcpath); } /* Tile with a bitmap. This is important for pattern fills. */ int gdev_pdf_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_pdf *const pdev = (gx_device_pdf *) dev; int tw = tiles->rep_width, th = tiles->rep_height; double xscale = pdev->HWResolution[0] / 72.0, yscale = pdev->HWResolution[1] / 72.0; bool mask; int depth; int (*copy_data)(P12(gx_device_pdf *, const byte *, int, int, gx_bitmap_id, int, int, int, int, gs_image_t *, pdf_image_writer *, int)); pdf_resource_t *pres; cos_value_t cs_value; int code; if (tiles->id == gx_no_bitmap_id || tiles->shift != 0 || (w < tw && h < th) || color0 != gx_no_color_index || /* Pattern fills are only available starting in PDF 1.2. */ pdev->CompatibilityLevel < 1.2 ) goto use_default; if (color1 != gx_no_color_index) { /* This is a mask pattern. */ mask = true; depth = 1; copy_data = pdf_copy_mask_data; code = pdf_cs_Pattern_uncolored(pdev, &cs_value); } else { /* This is a colored pattern. */ mask = false; depth = pdev->color_info.depth; copy_data = pdf_copy_color_data; code = pdf_cs_Pattern_colored(pdev, &cs_value); } if (code < 0) goto use_default; pres = pdf_find_resource_by_gs_id(pdev, resourcePattern, tiles->id); if (!pres) { /* Create the Pattern resource. */ int code; long image_id, length_id, start, end; stream *s; gs_image_t image; pdf_image_writer writer; long image_bytes = ((long)tw * depth + 7) / 8 * th; bool in_line = image_bytes <= MAX_INLINE_IMAGE_BYTES; ulong tile_id = (tw == tiles->size.x && th == tiles->size.y ? tiles->id : gx_no_bitmap_id); if (in_line) image_id = 0; else if (image_bytes > 65500) { /* * Acrobat Reader can't handle image Patterns with more than * 64K of data. :-( */ goto use_default; } else { /* Write out the image as an XObject resource now. */ code = copy_data(pdev, tiles->data, 0, tiles->raster, tile_id, 0, 0, tw, th, &image, &writer, 1); if (code < 0) goto use_default; image_id = pdf_resource_id(writer.pres); } code = pdf_begin_resource(pdev, resourcePattern, tiles->id, &pres); if (code < 0) goto use_default; s = pdev->strm; pprintd1(s, "/PatternType 1/PaintType %d/TilingType 1/Resources<<\n", (mask ? 2 : 1)); if (image_id) pprintld2(s, "/XObject<>", image_id, image_id); pprints1(s, "/ProcSet[/PDF/Image%s]>>\n", (mask ? "B" : "C")); /* * Because of bugs in Acrobat Reader's Print function, we can't use * the natural BBox and Step here: they have to be 1. */ pprintg2(s, "/Matrix[%g 0 0 %g 0 0]", tw / xscale, th / yscale); stream_puts(s, "/BBox[0 0 1 1]/XStep 1/YStep 1/Length "); if (image_id) { char buf[MAX_REF_CHARS + 6 + 1]; /* +6 for /R# Do\n */ sprintf(buf, "/R%ld Do\n", image_id); pprintd1(s, "%d>>stream\n", strlen(buf)); pprints1(s, "%sendstream\n", buf); pdf_end_resource(pdev); } else { length_id = pdf_obj_ref(pdev); pprintld1(s, "%ld 0 R>>stream\n", length_id); start = pdf_stell(pdev); code = copy_data(pdev, tiles->data, 0, tiles->raster, tile_id, 0, 0, tw, th, &image, &writer, -1); switch (code) { default: return code; /* error */ case 1: break; case 0: /* not possible */ return_error(gs_error_Fatal); } end = pdf_stell(pdev); stream_puts(s, "\nendstream\n"); pdf_end_resource(pdev); pdf_open_separate(pdev, length_id); pprintld1(pdev->strm, "%ld\n", end - start); pdf_end_separate(pdev); } pres->object->written = true; /* don't write at end of page */ } /* Fill the rectangle with the Pattern. */ { int code = pdf_open_page(pdev, PDF_IN_STREAM); stream *s; if (code < 0) goto use_default; /* Make sure we aren't being clipped. */ pdf_put_clip_path(pdev, NULL); s = pdev->strm; /* * Because of bugs in Acrobat Reader's Print function, we can't * leave the CTM alone here: we have to reset it to the default. */ pprintg2(s, "q %g 0 0 %g 0 0 cm\n", xscale, yscale); cos_value_write(&cs_value, pdev); stream_puts(s, " cs"); if (mask) pprintd3(s, " %d %d %d", (int)(color1 >> 16), (int)((color1 >> 8) & 0xff), (int)(color1 & 0xff)); pprintld1(s, "/R%ld scn", pdf_resource_id(pres)); pprintg4(s, " %g %g %g %g re f Q\n", x / xscale, y / yscale, w / xscale, h / xscale); } return 0; use_default: return gx_default_strip_tile_rectangle(dev, tiles, x, y, w, h, color0, color1, px, py); }