/* Copyright (C) 1989, 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: gsdevice.c,v 1.11.2.1.2.1 2003/01/17 00:49:02 giles Exp $ */ /* Device operators for Ghostscript library */ #include "ctype_.h" #include "memory_.h" /* for memchr, memcpy */ #include "string_.h" #include "gx.h" #include "gp.h" #include "gscdefs.h" /* for gs_lib_device_list */ #include "gserrors.h" #include "gsfname.h" #include "gsstruct.h" #include "gspath.h" /* gs_initclip prototype */ #include "gspaint.h" /* gs_erasepage prototype */ #include "gsmatrix.h" /* for gscoord.h */ #include "gscoord.h" /* for gs_initmatrix */ #include "gzstate.h" #include "gxcmap.h" #include "gxdevice.h" #include "gxdevmem.h" #include "gxiodev.h" /* Include the extern for the device list. */ extern_gs_lib_device_list(); /* * Finalization for devices: do any special finalization first, then * close the device if it is open, and finally free the structure * descriptor if it is dynamic. */ void gx_device_finalize(void *vptr) { gx_device * const dev = (gx_device *)vptr; if (dev->finalize) dev->finalize(dev); discard(gs_closedevice(dev)); if (dev->stype_is_dynamic) gs_free_const_object(&gs_memory_default, dev->stype, "gx_device_finalize"); } /* "Free" a device locally allocated on the stack, by finalizing it. */ void gx_device_free_local(gx_device *dev) { gx_device_finalize(dev); } /* GC procedures */ private ENUM_PTRS_WITH(device_forward_enum_ptrs, gx_device_forward *fdev) return 0; case 0: ENUM_RETURN(gx_device_enum_ptr(fdev->target)); ENUM_PTRS_END private RELOC_PTRS_WITH(device_forward_reloc_ptrs, gx_device_forward *fdev) { fdev->target = gx_device_reloc_ptr(fdev->target, gcst); } RELOC_PTRS_END /* * Structure descriptors. These must follow the procedures, because * we can't conveniently forward-declare the procedures. * (See gxdevice.h for details.) */ public_st_device(); public_st_device_forward(); public_st_device_null(); /* GC utilities */ /* Enumerate or relocate a device pointer for a client. */ gx_device * gx_device_enum_ptr(gx_device * dev) { if (dev == 0 || dev->memory == 0) return 0; return dev; } gx_device * gx_device_reloc_ptr(gx_device * dev, gc_state_t * gcst) { if (dev == 0 || dev->memory == 0) return dev; return RELOC_OBJ(dev); /* gcst implicit */ } /* Set up the device procedures in the device structure. */ /* Also copy old fields to new ones. */ void gx_device_set_procs(gx_device * dev) { if (dev->static_procs != 0) { /* 0 if already populated */ dev->procs = *dev->static_procs; dev->static_procs = 0; } } /* Flush buffered output to the device */ int gs_flushpage(gs_state * pgs) { gx_device *dev = gs_currentdevice(pgs); return (*dev_proc(dev, sync_output)) (dev); } /* Make the device output the accumulated page description */ int gs_copypage(gs_state * pgs) { return gs_output_page(pgs, 1, 0); } int gs_output_page(gs_state * pgs, int num_copies, int flush) { gx_device *dev = gs_currentdevice(pgs); if (dev->IgnoreNumCopies) num_copies = 1; return (*dev_proc(dev, output_page)) (dev, num_copies, flush); } /* * Do generic work for output_page. All output_page procedures must call * this as the last thing they do, unless an error has occurred earlier. */ int gx_finish_output_page(gx_device *dev, int num_copies, int flush) { dev->PageCount += num_copies; return 0; } /* Copy scan lines from an image device */ int gs_copyscanlines(gx_device * dev, int start_y, byte * data, uint size, int *plines_copied, uint * pbytes_copied) { uint line_size = gx_device_raster(dev, 0); uint count = size / line_size; uint i; byte *dest = data; for (i = 0; i < count; i++, dest += line_size) { int code = (*dev_proc(dev, get_bits)) (dev, start_y + i, dest, NULL); if (code < 0) { /* Might just be an overrun. */ if (start_y + i == dev->height) break; return_error(code); } } if (plines_copied != NULL) *plines_copied = i; if (pbytes_copied != NULL) *pbytes_copied = i * line_size; return 0; } /* Get the current device from the graphics state. */ gx_device * gs_currentdevice(const gs_state * pgs) { return pgs->device; } /* Get the name of a device. */ const char * gs_devicename(const gx_device * dev) { return dev->dname; } /* Get the initial matrix of a device. */ void gs_deviceinitialmatrix(gx_device * dev, gs_matrix * pmat) { fill_dev_proc(dev, get_initial_matrix, gx_default_get_initial_matrix); (*dev_proc(dev, get_initial_matrix)) (dev, pmat); } /* Get the N'th device from the known device list */ const gx_device * gs_getdevice(int index) { const gx_device *const *list; int count = gs_lib_device_list(&list, NULL); if (index < 0 || index >= count) return 0; /* index out of range */ return list[index]; } /* Fill in the GC structure descriptor for a device. */ private void gx_device_make_struct_type(gs_memory_struct_type_t *st, const gx_device *dev) { const gx_device_procs *procs = dev->static_procs; /* * Try to figure out whether this is a forwarding device. For printer * devices, we rely on the prototype referencing the correct structure * descriptor; for other devices, we look for a likely forwarding * procedure in the vector. The algorithm isn't foolproof, but it's the * best we can come up with. */ if (procs == 0) procs = &dev->procs; if (dev->stype) *st = *dev->stype; else if (procs->get_xfont_procs == gx_forward_get_xfont_procs) *st = st_device_forward; else *st = st_device; st->ssize = dev->params_size; } /* Clone an existing device. */ int gs_copydevice2(gx_device ** pnew_dev, const gx_device * dev, bool keep_open, gs_memory_t * mem) { gx_device *new_dev; const gs_memory_struct_type_t *std = dev->stype; const gs_memory_struct_type_t *new_std; gs_memory_struct_type_t *a_std = 0; int code; if (dev->stype_is_dynamic) { /* * We allocated the stype for this device previously. * Just allocate a new stype and copy the old one into it. */ a_std = (gs_memory_struct_type_t *) gs_alloc_bytes_immovable(&gs_memory_default, sizeof(*std), "gs_copydevice(stype)"); if (!a_std) return_error(gs_error_VMerror); *a_std = *std; new_std = a_std; } else if (std != 0 && std->ssize == dev->params_size) { /* Use the static stype. */ new_std = std; } else { /* We need to figure out or adjust the stype. */ a_std = (gs_memory_struct_type_t *) gs_alloc_bytes_immovable(&gs_memory_default, sizeof(*std), "gs_copydevice(stype)"); if (!a_std) return_error(gs_error_VMerror); gx_device_make_struct_type(a_std, dev); new_std = a_std; } /* * Because command list devices have complicated internal pointer * structures, we allocate all device instances as immovable. */ new_dev = gs_alloc_struct_immovable(mem, gx_device, new_std, "gs_copydevice(device)"); if (new_dev == 0) return_error(gs_error_VMerror); gx_device_init(new_dev, dev, mem, false); gx_device_set_procs(new_dev); new_dev->stype = new_std; new_dev->stype_is_dynamic = new_std != std; /* * keep_open is very dangerous. On the other hand, so is copydevice in * general, since it just copies the bits without any regard to pointers * (including self-pointers) that they may contain. We handle this by * making the default finish_copydevice forbid copying of anything other * than the device prototype. */ new_dev->is_open = dev->is_open && keep_open; fill_dev_proc(new_dev, finish_copydevice, gx_default_finish_copydevice); code = dev_proc(new_dev, finish_copydevice)(new_dev, dev); if (code < 0) { gs_free_object(mem, new_dev, "gs_copydevice(device)"); if (a_std) gs_free_object(&gs_memory_default, a_std, "gs_copydevice(stype)"); return code; } *pnew_dev = new_dev; return 0; } int gs_copydevice(gx_device ** pnew_dev, const gx_device * dev, gs_memory_t * mem) { return gs_copydevice2(pnew_dev, dev, false, mem); } /* Open a device if not open already. Return 0 if the device was open, */ /* 1 if it was closed. */ int gs_opendevice(gx_device *dev) { if (dev->is_open) return 0; gx_device_fill_in_procs(dev); { int code = (*dev_proc(dev, open_device))(dev); if (code < 0) return_error(code); dev->is_open = true; return 1; } } /* Set device parameters, updating a graphics state or imager state. */ int gs_imager_putdeviceparams(gs_imager_state *pis, gx_device *dev, gs_param_list *plist) { int code = gs_putdeviceparams(dev, plist); if (code >= 0) gx_set_cmap_procs(pis, dev); return code; } private void gs_state_update_device(gs_state *pgs) { gx_set_cmap_procs((gs_imager_state *)pgs, pgs->device); gx_unset_dev_color(pgs); } int gs_state_putdeviceparams(gs_state *pgs, gs_param_list *plist) { int code = gs_putdeviceparams(pgs->device, plist); if (code >= 0) gs_state_update_device(pgs); return code; } /* Set the device in the graphics state */ int gs_setdevice(gs_state * pgs, gx_device * dev) { int code = gs_setdevice_no_erase(pgs, dev); if (code == 1) code = gs_erasepage(pgs); return code; } int gs_setdevice_no_erase(gs_state * pgs, gx_device * dev) { int open_code = 0, code; /* Initialize the device */ if (!dev->is_open) { gx_device_fill_in_procs(dev); if (gs_device_is_memory(dev)) { /* Set the target to the current device. */ gx_device *odev = gs_currentdevice_inline(pgs); while (odev != 0 && gs_device_is_memory(odev)) odev = ((gx_device_memory *)odev)->target; gx_device_set_target(((gx_device_forward *)dev), odev); } code = open_code = gs_opendevice(dev); if (code < 0) return code; } gs_setdevice_no_init(pgs, dev); pgs->ctm_default_set = false; if ((code = gs_initmatrix(pgs)) < 0 || (code = gs_initclip(pgs)) < 0 ) return code; /* If we were in a charpath or a setcachedevice, */ /* we aren't any longer. */ pgs->in_cachedevice = 0; pgs->in_charpath = (gs_char_path_mode) 0; return open_code; } int gs_setdevice_no_init(gs_state * pgs, gx_device * dev) { /* * Just set the device, possibly changing color space but no other * device parameters. */ rc_assign(pgs->device, dev, "gs_setdevice_no_init"); gs_state_update_device(pgs); return 0; } /* Initialize a just-allocated device. */ void gx_device_init(gx_device * dev, const gx_device * proto, gs_memory_t * mem, bool internal) { memcpy(dev, proto, proto->params_size); dev->memory = mem; dev->retained = !internal; rc_init(dev, mem, (internal ? 0 : 1)); } /* Make a null device. */ void gs_make_null_device(gx_device_null *dev_null, gx_device *dev, gs_memory_t * mem) { gx_device_init((gx_device *)dev_null, (const gx_device *)&gs_null_device, mem, true); gx_device_set_target((gx_device_forward *)dev_null, dev); if (dev) gx_device_copy_color_params((gx_device *)dev_null, dev); } /* Mark a device as retained or not retained. */ void gx_device_retain(gx_device *dev, bool retained) { int delta = (int)retained - (int)dev->retained; if (delta) { dev->retained = retained; /* do first in case dev is freed */ rc_adjust_only(dev, delta, "gx_device_retain"); } } /* Select a null device. */ int gs_nulldevice(gs_state * pgs) { if (pgs->device == 0 || !gx_device_is_null(pgs->device)) { gx_device *ndev; int code = gs_copydevice(&ndev, (const gx_device *)&gs_null_device, pgs->memory); if (code < 0) return code; /* * Internal devices have a reference count of 0, not 1, * aside from references from graphics states. */ rc_init(ndev, pgs->memory, 0); return gs_setdevice_no_erase(pgs, ndev); } return 0; } /* Close a device. The client is responsible for ensuring that */ /* this device is not current in any graphics state. */ int gs_closedevice(gx_device * dev) { int code = 0; if (dev->is_open) { code = (*dev_proc(dev, close_device))(dev); if (code < 0) return_error(code); dev->is_open = false; } return code; } /* * Just set the device without any reinitializing. * (For internal use only.) */ void gx_set_device_only(gs_state * pgs, gx_device * dev) { rc_assign(pgs->device, dev, "gx_set_device_only"); } /* Compute the size of one scan line for a device, */ /* with or without padding to a word boundary. */ uint gx_device_raster(const gx_device * dev, bool pad) { ulong bits = (ulong) dev->width * dev->color_info.depth; return (pad ? bitmap_raster(bits) : (uint) ((bits + 7) >> 3)); } /* Adjust the resolution for devices that only have a fixed set of */ /* geometries, so that the apparent size in inches remains constant. */ /* If fit=1, the resolution is adjusted so that the entire image fits; */ /* if fit=0, one dimension fits, but the other one is clipped. */ int gx_device_adjust_resolution(gx_device * dev, int actual_width, int actual_height, int fit) { double width_ratio = (double)actual_width / dev->width; double height_ratio = (double)actual_height / dev->height; double ratio = (fit ? min(width_ratio, height_ratio) : max(width_ratio, height_ratio)); dev->HWResolution[0] *= ratio; dev->HWResolution[1] *= ratio; gx_device_set_width_height(dev, actual_width, actual_height); return 0; } /* Set the HWMargins to values defined in inches. */ /* If move_origin is true, also reset the Margins. */ /* Note that this assumes a printer-type device (Y axis inverted). */ void gx_device_set_margins(gx_device * dev, const float *margins /*[4] */ , bool move_origin) { int i; for (i = 0; i < 4; ++i) dev->HWMargins[i] = margins[i] * 72.0; if (move_origin) { dev->Margins[0] = -margins[0] * dev->MarginsHWResolution[0]; dev->Margins[1] = -margins[3] * dev->MarginsHWResolution[1]; } } /* Set the width and height, updating MediaSize to remain consistent. */ void gx_device_set_width_height(gx_device * dev, int width, int height) { dev->width = width; dev->height = height; dev->MediaSize[0] = width * 72.0 / dev->HWResolution[0]; dev->MediaSize[1] = height * 72.0 / dev->HWResolution[1]; } /* Set the resolution, updating width and height to remain consistent. */ void gx_device_set_resolution(gx_device * dev, floatp x_dpi, floatp y_dpi) { dev->HWResolution[0] = x_dpi; dev->HWResolution[1] = y_dpi; dev->width = dev->MediaSize[0] * x_dpi / 72.0 + 0.5; dev->height = dev->MediaSize[1] * y_dpi / 72.0 + 0.5; } /* Set the MediaSize, updating width and height to remain consistent. */ void gx_device_set_media_size(gx_device * dev, floatp media_width, floatp media_height) { dev->MediaSize[0] = media_width; dev->MediaSize[1] = media_height; dev->width = media_width * dev->HWResolution[0] / 72.0 + 0.499; dev->height = media_height * dev->HWResolution[1] / 72.0 + 0.499; } /* * Copy the color mapping procedures from the target if they are * standard ones (saving a level of procedure call at mapping time). */ void gx_device_copy_color_procs(gx_device *dev, const gx_device *target) { dev_proc_map_cmyk_color((*from_cmyk)) = dev_proc(dev, map_cmyk_color); dev_proc_map_rgb_color((*from_rgb)) = dev_proc(dev, map_rgb_color); dev_proc_map_color_rgb((*to_rgb)) = dev_proc(dev, map_color_rgb); if (from_cmyk == gx_forward_map_cmyk_color || from_cmyk == cmyk_1bit_map_cmyk_color || from_cmyk == cmyk_8bit_map_cmyk_color) { from_cmyk = dev_proc(target, map_cmyk_color); set_dev_proc(dev, map_cmyk_color, (from_cmyk == cmyk_1bit_map_cmyk_color || from_cmyk == cmyk_8bit_map_cmyk_color ? from_cmyk : gx_forward_map_cmyk_color)); } if (from_rgb == gx_forward_map_rgb_color || from_rgb == gx_default_rgb_map_rgb_color) { from_rgb = dev_proc(target, map_rgb_color); set_dev_proc(dev, map_rgb_color, (from_rgb == gx_default_rgb_map_rgb_color ? from_rgb : gx_forward_map_rgb_color)); } if (to_rgb == gx_forward_map_color_rgb || to_rgb == cmyk_1bit_map_color_rgb || to_rgb == cmyk_8bit_map_color_rgb) { to_rgb = dev_proc(target, map_color_rgb); set_dev_proc(dev, map_color_rgb, (to_rgb == cmyk_1bit_map_color_rgb || to_rgb == cmyk_8bit_map_color_rgb ? to_rgb : gx_forward_map_color_rgb)); } } #define COPY_PARAM(p) dev->p = target->p /* * Copy the color-related device parameters back from the target: * color_info and color mapping procedures. */ void gx_device_copy_color_params(gx_device *dev, const gx_device *target) { COPY_PARAM(color_info); COPY_PARAM(cached_colors); gx_device_copy_color_procs(dev, target); } /* * Copy device parameters back from a target. This copies all standard * parameters related to page size and resolution, plus color_info * and (if appropriate) color mapping procedures. */ void gx_device_copy_params(gx_device *dev, const gx_device *target) { #define COPY_ARRAY_PARAM(p) memcpy(dev->p, target->p, sizeof(dev->p)) COPY_PARAM(width); COPY_PARAM(height); COPY_ARRAY_PARAM(MediaSize); COPY_ARRAY_PARAM(ImagingBBox); COPY_PARAM(ImagingBBox_set); COPY_ARRAY_PARAM(HWResolution); COPY_ARRAY_PARAM(MarginsHWResolution); COPY_ARRAY_PARAM(Margins); COPY_ARRAY_PARAM(HWMargins); COPY_PARAM(PageCount); #undef COPY_ARRAY_PARAM gx_device_copy_color_params(dev, target); } #undef COPY_PARAM /* * Parse the output file name detecting and validating any %nnd format * for inserting the page count. If a format is present, store a pointer * to its last character in *pfmt, otherwise store 0 there. * Note that we assume devices have already been scanned, and any % must * precede a valid format character. * * If there was a format, then return the max_width */ private int gx_parse_output_format(gs_parsed_file_name_t *pfn, const char **pfmt) { int code; bool have_format = false, field = 0; int width[2], int_width = sizeof(int) * 3, w = 0; uint i; /* Scan the file name for a format string, and validate it if present. */ width[0] = width[1] = 0; for (i = 0; i < pfn->len; ++i) if (pfn->fname[i] == '%') { if (i + 1 < pfn->len && pfn->fname[i + 1] == '%') continue; if (have_format) /* more than one % */ return_error(gs_error_undefinedfilename); have_format = true; sw: if (++i == pfn->len) return_error(gs_error_undefinedfilename); switch (pfn->fname[i]) { case 'l': int_width = sizeof(long) * 3; case ' ': case '#': case '+': case '-': goto sw; case '.': if (field) return_error(gs_error_undefinedfilename); field = 1; continue; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': width[field] = width[field] * 10 + pfn->fname[i] - '0'; goto sw; case 'd': case 'i': case 'u': case 'o': case 'x': case 'X': *pfmt = &pfn->fname[i]; continue; default: return_error(gs_error_undefinedfilename); } } if (have_format) { /* Calculate a conservative maximum width. */ w = max(width[0], width[1]); w = max(w, int_width) + 5; } return w; } /* * Parse the output file name for a device, recognizing "-" and "|command", * and also detecting and validating any %nnd format for inserting the * page count. If a format is present, store a pointer to its last * character in *pfmt, otherwise store 0 there. Note that an empty name * is currently allowed. */ int gx_parse_output_file_name(gs_parsed_file_name_t *pfn, const char **pfmt, const char *fname, uint fnlen) { int code; int i; *pfmt = 0; pfn->memory = 0; pfn->iodev = NULL; pfn->fname = NULL; /* irrelevant since length = 0 */ pfn->len = 0; if (fnlen == 0) /* allow null name */ return 0; /* * If the file name begins with a %, it might be either an IODevice * or a %nnd format. Check (carefully) for this case. */ code = gs_parse_file_name(pfn, fname, fnlen); if (code < 0) { if (fname[0] == '%') { /* not a recognized iodev -- may be a leading format descriptor */ pfn->len = fnlen; pfn->fname = fname; code = gx_parse_output_format(pfn, pfmt); } if (code < 0) return code; } if (!pfn->iodev) { if ( (pfn->len == 1) && (pfn->fname[0] == '-') ) { pfn->iodev = gs_findiodevice((const byte *)"%stdout", 7); pfn->fname = NULL; } else if (pfn->fname[0] == '|') { pfn->iodev = gs_findiodevice((const byte *)"%pipe", 5); pfn->fname++, pfn->len--; } else pfn->iodev = iodev_default; if (!pfn->iodev) return_error(gs_error_undefinedfilename); } if (!pfn->fname) return 0; code = gx_parse_output_format(pfn, pfmt); if (code < 0) return code; if (strlen(pfn->iodev->dname) + pfn->len + code >= gp_file_name_sizeof) return_error(gs_error_undefinedfilename); return 0; } /* Open the output file for a device. */ int gx_device_open_output_file(const gx_device * dev, char *fname, bool binary, bool positionable, FILE ** pfile) { gs_parsed_file_name_t parsed; const char *fmt; char pfname[gp_file_name_sizeof]; int code = gx_parse_output_file_name(&parsed, &fmt, fname, strlen(fname)); if (code < 0) return code; if (parsed.iodev && !strcmp(parsed.iodev->dname, "%stdout%")) { if (parsed.fname) return_error(gs_error_undefinedfilename); *pfile = gs_stdout; /* Force stdout to binary. */ return gp_setmode_binary(*pfile, true); } if (fmt) { long count1 = dev->PageCount + 1; while (*fmt != 'l' && *fmt != '%') --fmt; if (*fmt == 'l') sprintf(pfname, parsed.fname, count1); else sprintf(pfname, parsed.fname, (int)count1); parsed.fname = pfname; parsed.len = strlen(parsed.fname); } if (positionable || (parsed.iodev && parsed.iodev != iodev_default)) { char fmode[4]; if (!parsed.fname) return_error(gs_error_undefinedfilename); strcpy(fmode, gp_fmode_wb); if (positionable) strcat(fmode, "+"); return parsed.iodev->procs.fopen(parsed.iodev, parsed.fname, fmode, pfile, NULL, 0); } *pfile = gp_open_printer((fmt ? pfname : fname), binary); if (*pfile) return 0; return_error(gs_error_invalidfileaccess); } /* Close the output file for a device. */ int gx_device_close_output_file(const gx_device * dev, const char *fname, FILE *file) { gs_parsed_file_name_t parsed; const char *fmt; int code = gx_parse_output_file_name(&parsed, &fmt, fname, strlen(fname)); if (code < 0) return code; if (parsed.iodev) { if (!strcmp(parsed.iodev->dname, "%stdout%")) return 0; /* NOTE: fname is unsubstituted if the name has any %nnd formats. */ if (parsed.iodev != iodev_default) return parsed.iodev->procs.fclose(parsed.iodev, file); } gp_close_printer(file, (parsed.fname ? parsed.fname : fname)); return 0; }