/* Copyright (C) 1997, 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: gdevpx.c,v 1.6.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */ /* H-P PCL XL driver */ #include "math_.h" #include "memory_.h" #include "string_.h" #include "gx.h" #include "gserrors.h" #include "gsccolor.h" #include "gsdcolor.h" #include "gxcspace.h" /* for color mapping for images */ #include "gxdevice.h" #include "gxpath.h" #include "gdevvec.h" #include "strimpl.h" #include "srlx.h" #include "gdevpxat.h" #include "gdevpxen.h" #include "gdevpxop.h" #include "gdevpxut.h" /* ---------------- Device definition ---------------- */ /* Define the default resolution. */ #ifndef X_DPI # define X_DPI 600 #endif #ifndef Y_DPI # define Y_DPI 600 #endif /* Structure definition */ #define NUM_POINTS 40 /* must be >= 3 and <= 255 */ typedef enum { POINTS_NONE, POINTS_LINES, POINTS_CURVES } point_type_t; typedef struct gx_device_pclxl_s { gx_device_vector_common; /* Additional state information */ pxeMediaSize_t media_size; gx_path_type_t fill_rule; /* ...winding_number or ...even_odd */ gx_path_type_t clip_rule; /* ditto */ pxeColorSpace_t color_space; struct pal_ { int size; /* # of bytes */ byte data[256 * 3]; /* up to 8-bit samples */ } palette; struct pts_ { /* buffer for accumulating path points */ gs_int_point current; /* current point as of start of data */ point_type_t type; int count; gs_int_point data[NUM_POINTS]; } points; struct ch_ { /* cache for downloaded characters */ #define MAX_CACHED_CHARS 400 #define MAX_CHAR_DATA 500000 #define MAX_CHAR_SIZE 5000 #define CHAR_HASH_FACTOR 247 ushort table[MAX_CACHED_CHARS * 3 / 2]; struct cd_ { gs_id id; /* key */ uint size; } data[MAX_CACHED_CHARS]; int next_in; /* next data element to fill in */ int next_out; /* next data element to discard */ int count; /* of occupied data elements */ ulong used; } chars; bool font_set; } gx_device_pclxl; gs_public_st_suffix_add0_final(st_device_pclxl, gx_device_pclxl, "gx_device_pclxl", device_pclxl_enum_ptrs, device_pclxl_reloc_ptrs, gx_device_finalize, st_device_vector); #define pclxl_device_body(dname, depth)\ std_device_dci_type_body(gx_device_pclxl, 0, dname, &st_device_pclxl,\ DEFAULT_WIDTH_10THS * X_DPI / 10,\ DEFAULT_HEIGHT_10THS * Y_DPI / 10,\ X_DPI, Y_DPI,\ (depth > 8 ? 3 : 1), depth,\ (depth > 1 ? 255 : 1), (depth > 8 ? 255 : 0),\ (depth > 1 ? 256 : 2), (depth > 8 ? 256 : 1)) /* Driver procedures */ private dev_proc_open_device(pclxl_open_device); private dev_proc_output_page(pclxl_output_page); private dev_proc_close_device(pclxl_close_device); private dev_proc_copy_mono(pclxl_copy_mono); private dev_proc_copy_color(pclxl_copy_color); private dev_proc_fill_mask(pclxl_fill_mask); /*private dev_proc_draw_thin_line(pclxl_draw_thin_line); */ private dev_proc_begin_image(pclxl_begin_image); private dev_proc_strip_copy_rop(pclxl_strip_copy_rop); #define pclxl_device_procs(map_rgb_color, map_color_rgb)\ {\ pclxl_open_device,\ NULL, /* get_initial_matrix */\ NULL, /* sync_output */\ pclxl_output_page,\ pclxl_close_device,\ map_rgb_color, /* differs */\ map_color_rgb, /* differs */\ gdev_vector_fill_rectangle,\ NULL, /* tile_rectangle */\ pclxl_copy_mono,\ pclxl_copy_color,\ NULL, /* draw_line */\ NULL, /* get_bits */\ gdev_vector_get_params,\ gdev_vector_put_params,\ NULL, /* map_cmyk_color */\ NULL, /* get_xfont_procs */\ NULL, /* get_xfont_device */\ NULL, /* map_rgb_alpha_color */\ gx_page_device_get_page_device,\ NULL, /* get_alpha_bits */\ NULL, /* copy_alpha */\ NULL, /* get_band */\ NULL, /* copy_rop */\ gdev_vector_fill_path,\ gdev_vector_stroke_path,\ pclxl_fill_mask,\ gdev_vector_fill_trapezoid,\ gdev_vector_fill_parallelogram,\ gdev_vector_fill_triangle,\ NULL /****** WRONG ******/, /* draw_thin_line */\ pclxl_begin_image,\ NULL, /* image_data */\ NULL, /* end_image */\ NULL, /* strip_tile_rectangle */\ pclxl_strip_copy_rop\ } const gx_device_pclxl gs_pxlmono_device = { pclxl_device_body("pxlmono", 8), pclxl_device_procs(gx_default_gray_map_rgb_color, gx_default_gray_map_color_rgb) }; const gx_device_pclxl gs_pxlcolor_device = { pclxl_device_body("pxlcolor", 24), pclxl_device_procs(gx_default_rgb_map_rgb_color, gx_default_rgb_map_color_rgb) }; /* ---------------- Other utilities ---------------- */ inline private stream * pclxl_stream(gx_device_pclxl *xdev) { return gdev_vector_stream((gx_device_vector *)xdev); } /* Initialize for a page. */ private void pclxl_page_init(gx_device_pclxl * xdev) { gdev_vector_init((gx_device_vector *)xdev); xdev->in_page = false; xdev->fill_rule = gx_path_type_winding_number; xdev->clip_rule = gx_path_type_winding_number; xdev->color_space = eNoColorSpace; xdev->palette.size = 0; xdev->font_set = false; } /* Test whether a RGB color is actually a gray shade. */ #define RGB_IS_GRAY(ci) ((ci) >> 8 == ((ci) & 0xffff)) /* Set the color space and (optionally) palette. */ private void pclxl_set_color_space(gx_device_pclxl * xdev, pxeColorSpace_t color_space) { if (xdev->color_space != color_space) { stream *s = pclxl_stream(xdev); px_put_ub(s, color_space); px_put_ac(s, pxaColorSpace, pxtSetColorSpace); xdev->color_space = color_space; } } private void pclxl_set_color_palette(gx_device_pclxl * xdev, pxeColorSpace_t color_space, const byte * palette, uint palette_size) { if (xdev->color_space != color_space || xdev->palette.size != palette_size || memcmp(xdev->palette.data, palette, palette_size) ) { stream *s = pclxl_stream(xdev); static const byte csp_[] = { DA(pxaColorSpace), DUB(e8Bit), DA(pxaPaletteDepth), pxt_ubyte_array }; px_put_ub(s, color_space); PX_PUT_LIT(s, csp_); px_put_u(s, palette_size); px_put_bytes(s, palette, palette_size); px_put_ac(s, pxaPaletteData, pxtSetColorSpace); xdev->color_space = color_space; xdev->palette.size = palette_size; memcpy(xdev->palette.data, palette, palette_size); } } /* Set a drawing RGB color. */ private int pclxl_set_color(gx_device_pclxl * xdev, const gx_drawing_color * pdc, px_attribute_t null_source, px_tag_t op) { stream *s = pclxl_stream(xdev); if (gx_dc_is_pure(pdc)) { gx_color_index color = gx_dc_pure_color(pdc); if (xdev->color_info.num_components == 1 || RGB_IS_GRAY(color)) { pclxl_set_color_space(xdev, eGray); px_put_uba(s, (byte) color, pxaGrayLevel); } else { pclxl_set_color_space(xdev, eRGB); spputc(s, pxt_ubyte_array); px_put_ub(s, 3); spputc(s, (byte) (color >> 16)); spputc(s, (byte) (color >> 8)); spputc(s, (byte) color); px_put_a(s, pxaRGBColor); } } else if (gx_dc_is_null(pdc)) px_put_uba(s, 0, null_source); else return_error(gs_error_rangecheck); spputc(s, op); return 0; } /* Test whether we can handle a given color space in an image. */ /* We cannot handle ICCBased color spaces. */ private bool pclxl_can_handle_color_space(const gs_color_space * pcs) { gs_color_space_index index = gs_color_space_get_index(pcs); if (index == gs_color_space_index_Indexed) { if (pcs->params.indexed.use_proc) return false; index = gs_color_space_get_index(gs_color_space_indexed_base_space(pcs)); } return !(index == gs_color_space_index_Separation || index == gs_color_space_index_Pattern || index == gs_color_space_index_CIEICC); } /* Set brush, pen, and mode for painting a path. */ private void pclxl_set_paints(gx_device_pclxl * xdev, gx_path_type_t type) { stream *s = pclxl_stream(xdev); gx_path_type_t rule = type & gx_path_type_rule; if (!(type & gx_path_type_fill) && !gx_dc_is_null(&xdev->fill_color) ) { static const byte nac_[] = { DUB(0), DA(pxaNullBrush), pxtSetBrushSource }; PX_PUT_LIT(s, nac_); color_set_null(&xdev->fill_color); if (rule != xdev->fill_rule) { px_put_ub(s, (rule == gx_path_type_even_odd ? eEvenOdd : eNonZeroWinding)); px_put_ac(s, pxaFillMode, pxtSetFillMode); xdev->fill_rule = rule; } } if (!(type & gx_path_type_stroke) && !gx_dc_is_null(&xdev->stroke_color) ) { static const byte nac_[] = { DUB(0), DA(pxaNullPen), pxtSetPenSource }; PX_PUT_LIT(s, nac_); color_set_null(&xdev->stroke_color); } } /* Set the cursor. */ private int pclxl_set_cursor(gx_device_pclxl * xdev, int x, int y) { stream *s = pclxl_stream(xdev); px_put_ssp(s, x, y); px_put_ac(s, pxaPoint, pxtSetCursor); return 0; } /* ------ Paths ------ */ /* Flush any buffered path points. */ private void px_put_np(stream * s, int count, pxeDataType_t dtype) { px_put_uba(s, count, pxaNumberOfPoints); px_put_uba(s, dtype, pxaPointType); } private int pclxl_flush_points(gx_device_pclxl * xdev) { int count = xdev->points.count; if (count) { stream *s = pclxl_stream(xdev); px_tag_t op; int x = xdev->points.current.x, y = xdev->points.current.y; int uor = 0, sor = 0; pxeDataType_t data_type; int i, di; byte diffs[NUM_POINTS * 2]; /* * Writing N lines using a point list requires 11 + 4*N or 11 + * 2*N bytes, as opposed to 8*N bytes using separate commands; * writing N curves requires 11 + 12*N or 11 + 6*N bytes * vs. 22*N. So it's always shorter to write curves with a * list (except for N = 1 with full-size coordinates, but since * the difference is only 1 byte, we don't bother to ever use * the non-list form), but lines are shorter only if N >= 3 * (again, with a 1-byte difference if N = 2 and byte * coordinates). */ switch (xdev->points.type) { case POINTS_NONE: return 0; case POINTS_LINES: op = pxtLinePath; if (count < 3) { for (i = 0; i < count; ++i) { px_put_ssp(s, xdev->points.data[i].x, xdev->points.data[i].y); px_put_a(s, pxaEndPoint); spputc(s, op); } goto zap; } /* See if we can use byte values. */ for (i = di = 0; i < count; ++i, di += 2) { int dx = xdev->points.data[i].x - x; int dy = xdev->points.data[i].y - y; diffs[di] = (byte) dx; diffs[di + 1] = (byte) dy; uor |= dx | dy; sor |= (dx + 0x80) | (dy + 0x80); x += dx, y += dy; } if (!(uor & ~0xff)) data_type = eUByte; else if (!(sor & ~0xff)) data_type = eSByte; else break; op = pxtLineRelPath; /* Use byte values. */ useb:px_put_np(s, count, data_type); spputc(s, op); px_put_data_length(s, count * 2); /* 2 bytes per point */ px_put_bytes(s, diffs, count * 2); goto zap; case POINTS_CURVES: op = pxtBezierPath; /* See if we can use byte values. */ for (i = di = 0; i < count; i += 3, di += 6) { int dx1 = xdev->points.data[i].x - x; int dy1 = xdev->points.data[i].y - y; int dx2 = xdev->points.data[i + 1].x - x; int dy2 = xdev->points.data[i + 1].y - y; int dx = xdev->points.data[i + 2].x - x; int dy = xdev->points.data[i + 2].y - y; diffs[di] = (byte) dx1; diffs[di + 1] = (byte) dy1; diffs[di + 2] = (byte) dx2; diffs[di + 3] = (byte) dy2; diffs[di + 4] = (byte) dx; diffs[di + 5] = (byte) dy; uor |= dx1 | dy1 | dx2 | dy2 | dx | dy; sor |= (dx1 + 0x80) | (dy1 + 0x80) | (dx2 + 0x80) | (dy2 + 0x80) | (dx + 0x80) | (dy + 0x80); x += dx, y += dy; } if (!(uor & ~0xff)) data_type = eUByte; else if (!(sor & ~0xff)) data_type = eSByte; else break; op = pxtBezierRelPath; goto useb; default: /* can't happen */ return_error(gs_error_unknownerror); } px_put_np(s, count, eSInt16); spputc(s, op); px_put_data_length(s, count * 4); /* 2 UInt16s per point */ for (i = 0; i < count; ++i) { px_put_s(s, xdev->points.data[i].x); px_put_s(s, xdev->points.data[i].y); } zap:xdev->points.type = POINTS_NONE; xdev->points.count = 0; } return 0; } /* ------ Images ------ */ private image_enum_proc_plane_data(pclxl_image_plane_data); private image_enum_proc_end_image(pclxl_image_end_image); private const gx_image_enum_procs_t pclxl_image_enum_procs = { pclxl_image_plane_data, pclxl_image_end_image }; /* Begin an image. */ private void pclxl_write_begin_image(gx_device_pclxl * xdev, uint width, uint height, uint dest_width, uint dest_height) { stream *s = pclxl_stream(xdev); px_put_usa(s, width, pxaSourceWidth); px_put_usa(s, height, pxaSourceHeight); px_put_usp(s, dest_width, dest_height); px_put_ac(s, pxaDestinationSize, pxtBeginImage); } /* Write rows of an image. */ /****** IGNORES data_bit ******/ private void pclxl_write_image_data(gx_device_pclxl * xdev, const byte * data, int data_bit, uint raster, uint width_bits, int y, int height) { stream *s = pclxl_stream(xdev); uint width_bytes = (width_bits + 7) >> 3; uint num_bytes = ROUND_UP(width_bytes, 4) * height; bool compress = num_bytes >= 8; int i; px_put_usa(s, y, pxaStartLine); px_put_usa(s, height, pxaBlockHeight); if (compress) { stream_RLE_state rlstate; stream_cursor_write w; stream_cursor_read r; /* * H-P printers require that all the data for an operator be * contained in a single data block. Thus, we must allocate a * temporary buffer for the compressed data. Currently we don't go * to the trouble of doing two passes if we can't allocate a buffer * large enough for the entire transfer. */ byte *buf = gs_alloc_bytes(xdev->v_memory, num_bytes, "pclxl_write_image_data"); if (buf == 0) goto nc; s_RLE_set_defaults_inline(&rlstate); rlstate.EndOfData = false; s_RLE_init_inline(&rlstate); w.ptr = buf - 1; w.limit = w.ptr + num_bytes; /* * If we ever overrun the buffer, it means that the compressed * data was larger than the uncompressed. If this happens, * write the data uncompressed. */ for (i = 0; i < height; ++i) { r.ptr = data + i * raster - 1; r.limit = r.ptr + width_bytes; if ((*s_RLE_template.process) ((stream_state *) & rlstate, &r, &w, false) != 0 || r.ptr != r.limit ) goto ncfree; r.ptr = (const byte *)"\000\000\000\000\000"; r.limit = r.ptr + (-width_bytes & 3); if ((*s_RLE_template.process) ((stream_state *) & rlstate, &r, &w, false) != 0 || r.ptr != r.limit ) goto ncfree; } r.ptr = r.limit; if ((*s_RLE_template.process) ((stream_state *) & rlstate, &r, &w, true) != 0 ) goto ncfree; { uint count = w.ptr + 1 - buf; px_put_ub(s, eRLECompression); px_put_ac(s, pxaCompressMode, pxtReadImage); px_put_data_length(s, count); px_put_bytes(s, buf, count); } gs_free_object(xdev->v_memory, buf, "pclxl_write_image_data"); return; ncfree:gs_free_object(xdev->v_memory, buf, "pclxl_write_image_data"); } nc: /* Write the data uncompressed. */ px_put_ub(s, eNoCompression); px_put_ac(s, pxaCompressMode, pxtReadImage); px_put_data_length(s, num_bytes); for (i = 0; i < height; ++i) { px_put_bytes(s, data + i * raster, width_bytes); px_put_bytes(s, (const byte *)"\000\000\000\000", -width_bytes & 3); } } /* End an image. */ private void pclxl_write_end_image(gx_device_pclxl * xdev) { spputc(xdev->strm, pxtEndImage); } /* ------ Fonts ------ */ /* Write a string (single- or double-byte). */ private void px_put_string(stream * s, const byte * data, uint len, bool wide) { if (wide) { spputc(s, pxt_uint16_array); px_put_u(s, len); px_put_bytes(s, data, len * 2); } else { spputc(s, pxt_ubyte_array); px_put_u(s, len); px_put_bytes(s, data, len); } } /* Write a 16-bit big-endian value. */ private void px_put_us_be(stream * s, uint i) { spputc(s, (byte) (i >> 8)); spputc(s, (byte) i); } /* Define a bitmap font. The client must call px_put_string */ /* with the font name immediately before calling this procedure. */ private void pclxl_define_bitmap_font(gx_device_pclxl * xdev) { stream *s = pclxl_stream(xdev); static const byte bfh_[] = { DA(pxaFontName), DUB(0), DA(pxaFontFormat), pxtBeginFontHeader, DUS(8 + 6 + 4 + 6), DA(pxaFontHeaderLength), pxtReadFontHeader, pxt_dataLengthByte, 8 + 6 + 4 + 6, 0, 0, 0, 0, 254, 0, (MAX_CACHED_CHARS + 255) >> 8, 0, 'B', 'R', 0, 0, 0, 4 }; static const byte efh_[] = { 0xff, 0xff, 0, 0, 0, 0, pxtEndFontHeader }; PX_PUT_LIT(s, bfh_); px_put_us_be(s, (uint) (xdev->HWResolution[0] + 0.5)); px_put_us_be(s, (uint) (xdev->HWResolution[1] + 0.5)); PX_PUT_LIT(s, efh_); } /* Set the font. The client must call px_put_string */ /* with the font name immediately before calling this procedure. */ private void pclxl_set_font(gx_device_pclxl * xdev) { stream *s = pclxl_stream(xdev); static const byte sf_[] = { DA(pxaFontName), DUB(1), DA(pxaCharSize), DUS(0), DA(pxaSymbolSet), pxtSetFont }; PX_PUT_LIT(s, sf_); } /* Define a character in a bitmap font. The client must call px_put_string */ /* with the font name immediately before calling this procedure. */ private void pclxl_define_bitmap_char(gx_device_pclxl * xdev, uint ccode, const byte * data, uint raster, uint width_bits, uint height) { stream *s = pclxl_stream(xdev); uint width_bytes = (width_bits + 7) >> 3; uint size = 10 + width_bytes * height; uint i; px_put_ac(s, pxaFontName, pxtBeginChar); px_put_u(s, ccode); px_put_a(s, pxaCharCode); if (size > 0xffff) { spputc(s, pxt_uint32); px_put_l(s, (ulong) size); } else px_put_us(s, size); px_put_ac(s, pxaCharDataSize, pxtReadChar); px_put_data_length(s, size); px_put_bytes(s, (const byte *)"\000\000\000\000\000\000", 6); px_put_us_be(s, width_bits); px_put_us_be(s, height); for (i = 0; i < height; ++i) px_put_bytes(s, data + i * raster, width_bytes); spputc(s, pxtEndChar); } /* Write the name of the only font we define. */ private void pclxl_write_font_name(gx_device_pclxl * xdev) { stream *s = pclxl_stream(xdev); px_put_string(s, (const byte *)"@", 1, false); } /* Look up a bitmap id, return the index in the character table. */ /* If the id is missing, return an index for inserting. */ private int pclxl_char_index(gx_device_pclxl * xdev, gs_id id) { int i, i_empty = -1; uint ccode; for (i = (id * CHAR_HASH_FACTOR) % countof(xdev->chars.table);; i = (i == 0 ? countof(xdev->chars.table) : i) - 1 ) { ccode = xdev->chars.table[i]; if (ccode == 0) return (i_empty >= 0 ? i_empty : i); else if (ccode == 1) { if (i_empty < 0) i_empty = i; else if (i == i_empty) /* full table */ return i; } else if (xdev->chars.data[ccode].id == id) return i; } } /* Remove the character table entry at a given index. */ private void pclxl_remove_char(gx_device_pclxl * xdev, int index) { uint ccode = xdev->chars.table[index]; int i; if (ccode < 2) return; xdev->chars.count--; xdev->chars.used -= xdev->chars.data[ccode].size; xdev->chars.table[index] = 1; /* mark as deleted */ i = (index == 0 ? countof(xdev->chars.table) : index) - 1; if (xdev->chars.table[i] == 0) { /* The next slot in probe order is empty. */ /* Mark this slot and any deleted predecessors as empty. */ for (i = index; xdev->chars.table[i] == 1; i = (i == countof(xdev->chars.table) - 1 ? 0 : i + 1) ) xdev->chars.table[i] = 0; } } /* Write a bitmap as a text character if possible. */ /* The caller must set the color, cursor, and RasterOp. */ /* We know id != gs_no_id. */ private int pclxl_copy_text_char(gx_device_pclxl * xdev, const byte * data, int raster, gx_bitmap_id id, int w, int h) { uint width_bytes = (w + 7) >> 3; uint size = width_bytes * h; int index; uint ccode; stream *s = pclxl_stream(xdev); if (size > MAX_CHAR_SIZE) return -1; index = pclxl_char_index(xdev, id); if ((ccode = xdev->chars.table[index]) < 2) { /* Enter the character in the table. */ while (xdev->chars.used + size > MAX_CHAR_DATA || xdev->chars.count >= MAX_CACHED_CHARS - 2 ) { ccode = xdev->chars.next_out; index = pclxl_char_index(xdev, xdev->chars.data[ccode].id); pclxl_remove_char(xdev, index); xdev->chars.next_out = (ccode == MAX_CACHED_CHARS - 1 ? 2 : ccode + 1); } index = pclxl_char_index(xdev, id); ccode = xdev->chars.next_in; xdev->chars.data[ccode].id = id; xdev->chars.data[ccode].size = size; xdev->chars.table[index] = ccode; xdev->chars.next_in = (ccode == MAX_CACHED_CHARS - 1 ? 2 : ccode + 1); if (!xdev->chars.count++) { /* This is the very first character. */ pclxl_write_font_name(xdev); pclxl_define_bitmap_font(xdev); } xdev->chars.used += size; pclxl_write_font_name(xdev); pclxl_define_bitmap_char(xdev, ccode, data, raster, w, h); } if (!xdev->font_set) { pclxl_write_font_name(xdev); pclxl_set_font(xdev); xdev->font_set = true; } { byte cc_bytes[2]; cc_bytes[0] = (byte) ccode; cc_bytes[1] = ccode >> 8; px_put_string(s, cc_bytes, 1, cc_bytes[1] != 0); } px_put_ac(s, pxaTextData, pxtText); return 0; } /* ---------------- Vector implementation procedures ---------------- */ private int pclxl_beginpage(gx_device_vector * vdev) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; /* * We can't use gdev_vector_stream here, because this may be called * from there before in_page is set. */ stream *s = vdev->strm; px_write_page_header(s, (const gx_device *)vdev); px_write_select_media(s, (const gx_device *)vdev, &xdev->media_size); spputc(s, pxtBeginPage); return 0; } private int pclxl_setlinewidth(gx_device_vector * vdev, floatp width) { stream *s = gdev_vector_stream(vdev); px_put_us(s, (uint) width); px_put_ac(s, pxaPenWidth, pxtSetPenWidth); return 0; } private int pclxl_setlinecap(gx_device_vector * vdev, gs_line_cap cap) { stream *s = gdev_vector_stream(vdev); /* The PCL XL cap styles just happen to be identical to PostScript. */ px_put_ub(s, (byte) cap); px_put_ac(s, pxaLineCapStyle, pxtSetLineCap); return 0; } private int pclxl_setlinejoin(gx_device_vector * vdev, gs_line_join join) { stream *s = gdev_vector_stream(vdev); /* The PCL XL join styles just happen to be identical to PostScript. */ px_put_ub(s, (byte) join); px_put_ac(s, pxaLineJoinStyle, pxtSetLineJoin); return 0; } private int pclxl_setmiterlimit(gx_device_vector * vdev, floatp limit) { stream *s = gdev_vector_stream(vdev); /* * Amazingly enough, the PCL XL specification doesn't allow real * numbers for the miter limit. */ int i_limit = (int)(limit + 0.5); px_put_u(s, max(i_limit, 1)); px_put_ac(s, pxaMiterLength, pxtSetMiterLimit); return 0; } private int pclxl_setdash(gx_device_vector * vdev, const float *pattern, uint count, floatp offset) { stream *s = gdev_vector_stream(vdev); if (count == 0) { static const byte nac_[] = { DUB(0), DA(pxaSolidLine) }; PX_PUT_LIT(s, nac_); } else if (count > 255) return_error(gs_error_limitcheck); else { uint i; /* * Astoundingly, PCL XL doesn't allow real numbers here. * Do the best we can. */ spputc(s, pxt_uint16_array); px_put_ub(s, count); for (i = 0; i < count; ++i) px_put_s(s, (uint)pattern[i]); px_put_a(s, pxaLineDashStyle); if (offset != 0) { px_put_rl(s, offset); px_put_a(s, pxaDashOffset); } } spputc(s, pxtSetLineDash); return 0; } private int pclxl_setlogop(gx_device_vector * vdev, gs_logical_operation_t lop, gs_logical_operation_t diff) { stream *s = gdev_vector_stream(vdev); if (diff & lop_S_transparent) { px_put_ub(s, (lop & lop_S_transparent ? 1 : 0)); px_put_ac(s, pxaTxMode, pxtSetSourceTxMode); } if (diff & lop_T_transparent) { px_put_ub(s, (lop & lop_T_transparent ? 1 : 0)); px_put_ac(s, pxaTxMode, pxtSetPaintTxMode); } if (lop_rop(diff)) { px_put_ub(s, lop_rop(lop)); px_put_ac(s, pxaROP3, pxtSetROP); } return 0; } private int pclxl_setfillcolor(gx_device_vector * vdev, const gx_drawing_color * pdc) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; return pclxl_set_color(xdev, pdc, pxaNullBrush, pxtSetBrushSource); } private int pclxl_setstrokecolor(gx_device_vector * vdev, const gx_drawing_color * pdc) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; return pclxl_set_color(xdev, pdc, pxaNullPen, pxtSetPenSource); } private int pclxl_dorect(gx_device_vector * vdev, fixed x0, fixed y0, fixed x1, fixed y1, gx_path_type_t type) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; stream *s = gdev_vector_stream(vdev); /* Check for out-of-range points. */ #define OUT_OF_RANGE(v) (v < 0 || v >= int2fixed(0x10000)) if (OUT_OF_RANGE(x0) || OUT_OF_RANGE(y0) || OUT_OF_RANGE(x1) || OUT_OF_RANGE(y1) ) return_error(gs_error_rangecheck); #undef OUT_OF_RANGE if (type & (gx_path_type_fill | gx_path_type_stroke)) { pclxl_set_paints(xdev, type); px_put_usq_fixed(s, x0, y0, x1, y1); px_put_ac(s, pxaBoundingBox, pxtRectangle); } if (type & gx_path_type_clip) { static const byte cr_[] = { DA(pxaBoundingBox), DUB(eInterior), DA(pxaClipRegion), pxtSetClipRectangle }; px_put_usq_fixed(s, x0, y0, x1, y1); PX_PUT_LIT(s, cr_); } return 0; } private int pclxl_beginpath(gx_device_vector * vdev, gx_path_type_t type) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; stream *s = gdev_vector_stream(vdev); spputc(s, pxtNewPath); xdev->points.type = POINTS_NONE; xdev->points.count = 0; return 0; } private int pclxl_moveto(gx_device_vector * vdev, floatp x0, floatp y0, floatp x, floatp y, gx_path_type_t type) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; int code = pclxl_flush_points(xdev); if (code < 0) return code; return pclxl_set_cursor(xdev, xdev->points.current.x = (int)x, xdev->points.current.y = (int)y); } private int pclxl_lineto(gx_device_vector * vdev, floatp x0, floatp y0, floatp x, floatp y, gx_path_type_t type) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; if (xdev->points.type != POINTS_LINES || xdev->points.count >= NUM_POINTS ) { if (xdev->points.type != POINTS_NONE) { int code = pclxl_flush_points(xdev); if (code < 0) return code; } xdev->points.current.x = (int)x0; xdev->points.current.y = (int)y0; xdev->points.type = POINTS_LINES; } { gs_int_point *ppt = &xdev->points.data[xdev->points.count++]; ppt->x = (int)x, ppt->y = (int)y; } return 0; } private int pclxl_curveto(gx_device_vector * vdev, floatp x0, floatp y0, floatp x1, floatp y1, floatp x2, floatp y2, floatp x3, floatp y3, gx_path_type_t type) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; if (xdev->points.type != POINTS_CURVES || xdev->points.count >= NUM_POINTS - 2 ) { if (xdev->points.type != POINTS_NONE) { int code = pclxl_flush_points(xdev); if (code < 0) return code; } xdev->points.current.x = (int)x0; xdev->points.current.y = (int)y0; xdev->points.type = POINTS_CURVES; } { gs_int_point *ppt = &xdev->points.data[xdev->points.count]; ppt->x = (int)x1, ppt->y = (int)y1, ++ppt; ppt->x = (int)x2, ppt->y = (int)y2, ++ppt; ppt->x = (int)x3, ppt->y = (int)y3; } xdev->points.count += 3; return 0; } private int pclxl_closepath(gx_device_vector * vdev, floatp x, floatp y, floatp x_start, floatp y_start, gx_path_type_t type) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; stream *s = gdev_vector_stream(vdev); int code = pclxl_flush_points(xdev); if (code < 0) return code; spputc(s, pxtCloseSubPath); xdev->points.current.x = (int)x_start; xdev->points.current.y = (int)y_start; return 0; } private int pclxl_endpath(gx_device_vector * vdev, gx_path_type_t type) { gx_device_pclxl *const xdev = (gx_device_pclxl *)vdev; stream *s = gdev_vector_stream(vdev); int code = pclxl_flush_points(xdev); gx_path_type_t rule = type & gx_path_type_rule; if (code < 0) return code; if (type & (gx_path_type_fill | gx_path_type_stroke)) { pclxl_set_paints(xdev, type); spputc(s, pxtPaintPath); } if (type & gx_path_type_clip) { static const byte scr_[] = { DUB(eInterior), DA(pxaClipRegion), pxtSetClipReplace }; if (rule != xdev->clip_rule) { px_put_ub(s, (rule == gx_path_type_even_odd ? eEvenOdd : eNonZeroWinding)); px_put_ac(s, pxaClipMode, pxtSetClipMode); xdev->clip_rule = rule; } PX_PUT_LIT(s, scr_); } return 0; } /* Vector implementation procedures */ private const gx_device_vector_procs pclxl_vector_procs = { /* Page management */ pclxl_beginpage, /* Imager state */ pclxl_setlinewidth, pclxl_setlinecap, pclxl_setlinejoin, pclxl_setmiterlimit, pclxl_setdash, gdev_vector_setflat, pclxl_setlogop, /* Other state */ pclxl_setfillcolor, pclxl_setstrokecolor, /* Paths */ gdev_vector_dopath, pclxl_dorect, pclxl_beginpath, pclxl_moveto, pclxl_lineto, pclxl_curveto, pclxl_closepath, pclxl_endpath }; /* ---------------- Driver procedures ---------------- */ /* ------ Open/close/page ------ */ /* Open the device. */ private int pclxl_open_device(gx_device * dev) { gx_device_vector *const vdev = (gx_device_vector *)dev; gx_device_pclxl *const xdev = (gx_device_pclxl *)dev; int code; vdev->v_memory = dev->memory; /****** WRONG ******/ vdev->vec_procs = &pclxl_vector_procs; code = gdev_vector_open_file_options(vdev, 512, VECTOR_OPEN_FILE_SEQUENTIAL); if (code < 0) return code; pclxl_page_init(xdev); px_write_file_header(vdev->strm, dev); xdev->media_size = pxeMediaSize_next; /* no size selected */ memset(&xdev->chars, 0, sizeof(xdev->chars)); xdev->chars.next_in = xdev->chars.next_out = 2; return 0; } /* Wrap up ("output") a page. */ /* We only support flush = true, and we don't support num_copies != 1. */ private int pclxl_output_page(gx_device * dev, int num_copies, int flush) { gx_device_pclxl *const xdev = (gx_device_pclxl *)dev; stream *s; /* Note that unlike close_device, end_page must not omit blank pages. */ if (!xdev->in_page) pclxl_beginpage((gx_device_vector *)dev); s = xdev->strm; spputc(s, pxtEndPage); sflush(s); pclxl_page_init(xdev); if (ferror(xdev->file)) return_error(gs_error_ioerror); return gx_finish_output_page(dev, num_copies, flush); } /* Close the device. */ /* Note that if this is being called as a result of finalization, */ /* the stream may no longer exist. */ private int pclxl_close_device(gx_device * dev) { gx_device_pclxl *const xdev = (gx_device_pclxl *)dev; FILE *file = xdev->file; if (xdev->in_page) fputc(pxtEndPage, file); px_write_file_trailer(file); return gdev_vector_close_file((gx_device_vector *)dev); } /* ------ One-for-one images ------ */ private const byte eBit_values[] = { 0, e1Bit, 0, 0, e4Bit, 0, 0, 0, e8Bit }; /* Copy a monochrome bitmap. */ private int pclxl_copy_mono(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 zero, gx_color_index one) { gx_device_vector *const vdev = (gx_device_vector *)dev; gx_device_pclxl *const xdev = (gx_device_pclxl *)dev; int code; stream *s; gx_color_index color0 = zero, color1 = one; gs_logical_operation_t lop; byte palette[2 * 3]; int palette_size; pxeColorSpace_t color_space; fit_copy(dev, data, data_x, raster, id, x, y, w, h); code = gdev_vector_update_clip_path(vdev, NULL); if (code < 0) return code; pclxl_set_cursor(xdev, x, y); if (id != gs_no_id && zero == gx_no_color_index && one != gx_no_color_index && data_x == 0 ) { gx_drawing_color dcolor; color_set_pure(&dcolor, one); pclxl_setfillcolor(vdev, &dcolor); if (pclxl_copy_text_char(xdev, data, raster, id, w, h) >= 0) return 0; } /* * The following doesn't work if we're writing white with a mask. * We'll fix it eventually. */ if (zero == gx_no_color_index) { if (one == gx_no_color_index) return 0; lop = rop3_S | lop_S_transparent; color0 = (1 << dev->color_info.depth) - 1; } else if (one == gx_no_color_index) { lop = rop3_S | lop_S_transparent; color1 = (1 << dev->color_info.depth) - 1; } else { lop = rop3_S; } if (dev->color_info.num_components == 1 || (RGB_IS_GRAY(color0) && RGB_IS_GRAY(color1)) ) { palette[0] = (byte) color0; palette[1] = (byte) color1; palette_size = 2; color_space = eGray; } else { palette[0] = (byte) (color0 >> 16); palette[1] = (byte) (color0 >> 8); palette[2] = (byte) color0; palette[3] = (byte) (color1 >> 16); palette[4] = (byte) (color1 >> 8); palette[5] = (byte) color1; palette_size = 6; color_space = eRGB; } code = gdev_vector_update_log_op(vdev, lop); if (code < 0) return 0; pclxl_set_color_palette(xdev, color_space, palette, palette_size); s = pclxl_stream(xdev); { static const byte mi_[] = { DUB(e1Bit), DA(pxaColorDepth), DUB(eIndexedPixel), DA(pxaColorMapping) }; PX_PUT_LIT(s, mi_); } pclxl_write_begin_image(xdev, w, h, w, h); pclxl_write_image_data(xdev, data, data_x, raster, w, 0, h); pclxl_write_end_image(xdev); return 0; } /* Copy a color bitmap. */ private int pclxl_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_vector *const vdev = (gx_device_vector *)dev; gx_device_pclxl *const xdev = (gx_device_pclxl *)dev; stream *s; uint source_bit; int code; fit_copy(dev, base, sourcex, raster, id, x, y, w, h); code = gdev_vector_update_clip_path(vdev, NULL); if (code < 0) return code; source_bit = sourcex * dev->color_info.depth; if ((source_bit & 7) != 0) return gx_default_copy_color(dev, base, sourcex, raster, id, x, y, w, h); gdev_vector_update_log_op(vdev, rop3_S); pclxl_set_cursor(xdev, x, y); s = pclxl_stream(xdev); { static const byte ci_[] = { DA(pxaColorDepth), DUB(eDirectPixel), DA(pxaColorMapping) }; px_put_ub(s, eBit_values[dev->color_info.depth / dev->color_info.num_components]); PX_PUT_LIT(s, ci_); } pclxl_write_begin_image(xdev, w, h, w, h); pclxl_write_image_data(xdev, base, source_bit, raster, w * dev->color_info.depth, 0, h); pclxl_write_end_image(xdev); return 0; } /* Fill a mask. */ private int pclxl_fill_mask(gx_device * dev, const byte * data, int data_x, 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_vector *const vdev = (gx_device_vector *)dev; gx_device_pclxl *const xdev = (gx_device_pclxl *)dev; int code; stream *s; fit_copy(dev, data, data_x, raster, id, x, y, w, h); if ((data_x & 7) != 0 || !gx_dc_is_pure(pdcolor) || depth > 1) return gx_default_fill_mask(dev, data, data_x, raster, id, x, y, w, h, pdcolor, depth, lop, pcpath); code = gdev_vector_update_clip_path(vdev, pcpath); if (code < 0) return code; code = gdev_vector_update_fill_color(vdev, pdcolor); if (code < 0) return 0; pclxl_set_cursor(xdev, x, y); if (id != gs_no_id && data_x == 0) { code = gdev_vector_update_log_op(vdev, lop); if (code < 0) return 0; if (pclxl_copy_text_char(xdev, data, raster, id, w, h) >= 0) return 0; } code = gdev_vector_update_log_op(vdev, lop | rop3_S | lop_S_transparent); if (code < 0) return 0; pclxl_set_color_palette(xdev, eGray, (const byte *)"\377\000", 2); s = pclxl_stream(xdev); { static const byte mi_[] = { DUB(e1Bit), DA(pxaColorDepth), DUB(eIndexedPixel), DA(pxaColorMapping) }; PX_PUT_LIT(s, mi_); } pclxl_write_begin_image(xdev, w, h, w, h); pclxl_write_image_data(xdev, data, data_x, raster, w, 0, h); pclxl_write_end_image(xdev); return 0; } /* Do a RasterOp. */ private int pclxl_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 width, int height, int phase_x, int phase_y, gs_logical_operation_t lop) { /* We can't do general RasterOps yet. */ /****** WORK IN PROGRESS ******/ return 0; } /* ------ High-level images ------ */ #define MAX_ROW_DATA 4000 /* arbitrary */ typedef struct pclxl_image_enum_s { gdev_vector_image_enum_common; gs_matrix mat; struct ir_ { byte *data; int num_rows; /* # of allocated rows */ int first_y; uint raster; } rows; } pclxl_image_enum_t; gs_private_st_suffix_add1(st_pclxl_image_enum, pclxl_image_enum_t, "pclxl_image_enum_t", pclxl_image_enum_enum_ptrs, pclxl_image_enum_reloc_ptrs, st_vector_image_enum, rows.data); /* Start processing an image. */ private int pclxl_begin_image(gx_device * dev, const gs_imager_state * pis, const gs_image_t * pim, gs_image_format_t format, const gs_int_rect * prect, const gx_drawing_color * pdcolor, const gx_clip_path * pcpath, gs_memory_t * mem, gx_image_enum_common_t ** pinfo) { gx_device_vector *const vdev = (gx_device_vector *)dev; gx_device_pclxl *const xdev = (gx_device_pclxl *)dev; const gs_color_space *pcs = pim->ColorSpace; pclxl_image_enum_t *pie; byte *row_data; int num_rows; uint row_raster; /* * Following should divide by num_planes, but we only handle chunky * images, i.e., num_planes = 1. */ int bits_per_pixel = (pim->ImageMask ? 1 : pim->BitsPerComponent * gs_color_space_num_components(pcs)); gs_matrix mat; int code; /* * Check whether we can handle this image. PCL XL 1.0 and 2.0 only * handle orthogonal transformations. */ gs_matrix_invert(&pim->ImageMatrix, &mat); gs_matrix_multiply(&mat, &ctm_only(pis), &mat); /* Currently we only handle portrait transformations. */ if (mat.xx <= 0 || mat.xy != 0 || mat.yx != 0 || mat.yy <= 0 || (pim->ImageMask ? (!gx_dc_is_pure(pdcolor) || pim->CombineWithColor) : (!pclxl_can_handle_color_space(pim->ColorSpace) || (bits_per_pixel != 1 && bits_per_pixel != 4 && bits_per_pixel != 8))) || format != gs_image_format_chunky || prect ) goto use_default; row_raster = (bits_per_pixel * pim->Width + 7) >> 3; num_rows = MAX_ROW_DATA / row_raster; if (num_rows > pim->Height) num_rows = pim->Height; if (num_rows <= 0) num_rows = 1; pie = gs_alloc_struct(mem, pclxl_image_enum_t, &st_pclxl_image_enum, "pclxl_begin_image"); row_data = gs_alloc_bytes(mem, num_rows * row_raster, "pclxl_begin_image(rows)"); if (pie == 0 || row_data == 0) { code = gs_note_error(gs_error_VMerror); goto fail; } code = gdev_vector_begin_image(vdev, pis, pim, format, prect, pdcolor, pcpath, mem, &pclxl_image_enum_procs, (gdev_vector_image_enum_t *)pie); if (code < 0) return code; pie->mat = mat; pie->rows.data = row_data; pie->rows.num_rows = num_rows; pie->rows.first_y = 0; pie->rows.raster = row_raster; *pinfo = (gx_image_enum_common_t *) pie; { gs_logical_operation_t lop = pis->log_op; if (pim->ImageMask) { const byte *palette = (const byte *) (pim->Decode[0] ? "\377\000" : "\000\377"); code = gdev_vector_update_fill_color(vdev, pdcolor); if (code < 0) goto fail; code = gdev_vector_update_log_op (vdev, lop | rop3_S | lop_S_transparent); if (code < 0) goto fail; pclxl_set_color_palette(xdev, eGray, palette, 2); } else { int bpc = pim->BitsPerComponent; int num_components = pie->plane_depths[0] * pie->num_planes / bpc; int sample_max = (1 << bpc) - 1; byte palette[256 * 3]; int i; code = gdev_vector_update_log_op (vdev, (pim->CombineWithColor ? lop : rop3_know_T_0(lop))); if (code < 0) goto fail; for (i = 0; i < 1 << bits_per_pixel; ++i) { gs_client_color cc; gx_device_color devc; int cv = i, j; gx_color_index ci; for (j = num_components - 1; j >= 0; cv >>= bpc, --j) cc.paint.values[j] = pim->Decode[j * 2] + (cv & sample_max) * (pim->Decode[j * 2 + 1] - pim->Decode[j * 2]) / sample_max; (*pcs->type->remap_color) (&cc, pcs, &devc, pis, dev, gs_color_select_source); if (!gx_dc_is_pure(&devc)) return_error(gs_error_Fatal); ci = gx_dc_pure_color(&devc); if (dev->color_info.num_components == 1) { palette[i] = (byte)ci; } else { byte *ppal = &palette[i * 3]; ppal[0] = (byte) (ci >> 16); ppal[1] = (byte) (ci >> 8); ppal[2] = (byte) ci; } } if (dev->color_info.num_components == 1) pclxl_set_color_palette(xdev, eGray, palette, 1 << bits_per_pixel); else pclxl_set_color_palette(xdev, eRGB, palette, 3 << bits_per_pixel); } } return 0; fail: gs_free_object(mem, row_data, "pclxl_begin_image(rows)"); gs_free_object(mem, pie, "pclxl_begin_image"); use_default: return gx_default_begin_image(dev, pis, pim, format, prect, pdcolor, pcpath, mem, pinfo); } /* Write one strip of an image, from pie->rows.first_y to pie->y. */ private int image_transform_x(const pclxl_image_enum_t *pie, int sx) { return (int)((pie->mat.tx + sx * pie->mat.xx + 0.5) / ((const gx_device_pclxl *)pie->dev)->scale.x); } private int image_transform_y(const pclxl_image_enum_t *pie, int sy) { return (int)((pie->mat.ty + sy * pie->mat.yy + 0.5) / ((const gx_device_pclxl *)pie->dev)->scale.y); } private int pclxl_image_write_rows(pclxl_image_enum_t *pie) { gx_device_pclxl *const xdev = (gx_device_pclxl *)pie->dev; stream *s = pclxl_stream(xdev); int y = pie->rows.first_y; int h = pie->y - y; int xo = image_transform_x(pie, 0); int yo = image_transform_y(pie, y); int dw = image_transform_x(pie, pie->width) - xo; int dh = image_transform_y(pie, y + h) - yo; static const byte ii_[] = { DA(pxaColorDepth), DUB(eIndexedPixel), DA(pxaColorMapping) }; if (dw <= 0 || dh <= 0) return 0; pclxl_set_cursor(xdev, xo, yo); px_put_ub(s, eBit_values[pie->bits_per_pixel]); PX_PUT_LIT(s, ii_); pclxl_write_begin_image(xdev, pie->width, h, dw, dh); pclxl_write_image_data(xdev, pie->rows.data, 0, pie->rows.raster, pie->rows.raster << 3, 0, h); pclxl_write_end_image(xdev); return 0; } /* Process the next piece of an image. */ private int pclxl_image_plane_data(gx_image_enum_common_t * info, const gx_image_plane_t * planes, int height, int *rows_used) { pclxl_image_enum_t *pie = (pclxl_image_enum_t *) info; int data_bit = planes[0].data_x * info->plane_depths[0]; int width_bits = pie->width * info->plane_depths[0]; int i; /****** SHOULD HANDLE NON-BYTE-ALIGNED DATA ******/ if (width_bits != pie->bits_per_row || (data_bit & 7) != 0) return_error(gs_error_rangecheck); if (height > pie->height - pie->y) height = pie->height - pie->y; for (i = 0; i < height; pie->y++, ++i) { if (pie->y - pie->rows.first_y == pie->rows.num_rows) { int code = pclxl_image_write_rows(pie); if (code < 0) return code; pie->rows.first_y = pie->y; } memcpy(pie->rows.data + pie->rows.raster * (pie->y - pie->rows.first_y), planes[0].data + planes[0].raster * i + (data_bit >> 3), pie->rows.raster); } *rows_used = height; return pie->y >= pie->height; } /* Clean up by releasing the buffers. */ private int pclxl_image_end_image(gx_image_enum_common_t * info, bool draw_last) { pclxl_image_enum_t *pie = (pclxl_image_enum_t *) info; int code = 0; /* Write the final strip, if any. */ if (pie->y > pie->rows.first_y && draw_last) code = pclxl_image_write_rows(pie); gs_free_object(pie->memory, pie->rows.data, "pclxl_end_image(rows)"); gs_free_object(pie->memory, pie, "pclxl_end_image"); return code; }