/* Copyright (C) 2001-2023 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., 39 Mesa Street, Suite 108A, San Francisco, CA 94129, USA, for further information. */ /* Graphics state operators */ #include "math_.h" #include "ghost.h" #include "oper.h" #include "ialloc.h" #include "icremap.h" #include "idict.h" #include "istruct.h" #include "igstate.h" #include "gsmatrix.h" #include "store.h" #include "gscspace.h" #include "iname.h" /* Structure descriptors */ private_st_int_gstate(); private_st_int_remap_color_info(); /* ------ Utilities ------ */ static int zset_real(i_ctx_t *i_ctx_p, int (*set_proc)(gs_gstate *, double)) { os_ptr op = osp; double param; int code = real_param(op, ¶m); if (code < 0) return_op_typecheck(op); code = set_proc(igs, param); if (code == 0) pop(1); return code; } static int zcurrent_real(i_ctx_t *i_ctx_p, double (*current_proc)(const gs_gstate *)) { os_ptr op = osp; push(1); make_real(op, current_proc(igs)); return 0; } static int zset_bool(i_ctx_t *i_ctx_p, void (*set_proc)(gs_gstate *, bool)) { os_ptr op = osp; check_type(*op, t_boolean); set_proc(igs, op->value.boolval); pop(1); return 0; } static int zcurrent_bool(i_ctx_t *i_ctx_p, bool (*current_proc)(const gs_gstate *)) { os_ptr op = osp; push(1); make_bool(op, current_proc(igs)); return 0; } static int zset_uint(i_ctx_t *i_ctx_p, void (*set_proc)(gs_gstate *, uint)) { os_ptr op = osp; check_type(*op, t_integer); set_proc(igs, op->value.intval); pop(1); return 0; } static int zcurrent_uint(i_ctx_t *i_ctx_p, uint (*current_proc)(const gs_gstate *)) { os_ptr op = osp; push(1); make_int(op, current_proc(igs)); return 0; } /* ------ Operations on the entire graphics state ------ */ /* "Client" procedures */ static void *gs_istate_alloc(gs_memory_t * mem); static int gs_istate_copy(void *to, const void *from); static void gs_istate_free(void *old, gs_memory_t * mem, gs_gstate *pgs); static const gs_gstate_client_procs istate_procs = { gs_istate_alloc, gs_istate_copy, gs_istate_free, 0, /* copy_for */ }; /* Initialize the graphics stack. */ gs_gstate * int_gstate_alloc(const gs_dual_memory_t * dmem) { int_gstate *iigs; ref proc0; int_remap_color_info_t *prci; gs_ref_memory_t *lmem = dmem->space_local; gs_ref_memory_t *gmem = dmem->space_global; gs_gstate *pgs = gs_gstate_alloc((gs_memory_t *)lmem); if (pgs == NULL) return NULL; iigs = gs_alloc_struct((gs_memory_t *)lmem, int_gstate, &st_int_gstate, "int_gstate_alloc(int_gstate)"); if (iigs == NULL) return NULL; int_gstate_map_refs(iigs, make_null); make_empty_array(&iigs->dash_pattern_array, a_all); if (gs_alloc_ref_array(lmem, &proc0, a_readonly + a_executable, 2, "int_gstate_alloc(proc0)") < 0) return NULL; make_oper(proc0.value.refs, 0, zpop); make_real(proc0.value.refs + 1, 0.0); iigs->black_generation = proc0; iigs->undercolor_removal = proc0; make_false(&iigs->use_cie_color); /* * Even though the gstate itself is allocated in local VM, the * container for the color remapping procedure must be allocated in * global VM so that the gstate can be copied into global VM. */ prci = gs_alloc_struct((gs_memory_t *)gmem, int_remap_color_info_t, &st_int_remap_color_info, "int_gstate_alloc(remap color info)"); if (prci == NULL) return NULL; make_struct(&iigs->remap_color_info, imemory_space(gmem), prci); clear_pagedevice(iigs); gs_gstate_set_client(pgs, iigs, &istate_procs, true); /* PostScript code wants limit clamping enabled. */ gs_setlimitclamp(pgs, true); /* * gsave and grestore only work properly * if there are always at least 2 entries on the stack. * We count on the PostScript initialization code to do a gsave. */ return pgs; } /* - gsave - */ int zgsave(i_ctx_t *i_ctx_p) { return gs_gsave(igs); } /* - grestore - */ int zgrestore(i_ctx_t *i_ctx_p) { return gs_grestore(igs); } /* - grestoreall - */ int zgrestoreall(i_ctx_t *i_ctx_p) { return gs_grestoreall(igs); } /* - initgraphics - */ static int zinitgraphics(i_ctx_t *i_ctx_p) { /* * Although gs_initgraphics resets the color space to DeviceGray, it does * not modify the 'interpreter' gstate, which stores a copy of the PostScript * object used to set the colour space. We could do this here, with effort, * but instead we choose t do it in gs_cspace.ps and handle it all in PostScript. */ make_empty_array(&istate->dash_pattern_array, a_all); return gs_initgraphics(igs); } /* ------ Operations on graphics state elements ------ */ /* setlinewidth - */ static int zsetlinewidth(i_ctx_t *i_ctx_p) { os_ptr op = osp; /* * The Red Book doesn't say anything about this, but Adobe * interpreters return (or perhaps store) the absolute value * of the width. */ double width; int code = real_param(op, &width); if (code < 0) return_op_typecheck(op); code = gs_setlinewidth(igs, fabs(width)); if (code >= 0) pop(1); return code; } /* - currentlinewidth */ static int zcurrentlinewidth(i_ctx_t *i_ctx_p) { os_ptr op = osp; push(1); make_real(op, gs_currentlinewidth(igs)); return 0; } /* .setlinecap - */ static int zsetlinecap(i_ctx_t *i_ctx_p) { os_ptr op = osp; int param; int code = int_param(op, max_int, ¶m); if (code < 0 || (code = gs_setlinecap(igs, (gs_line_cap) param)) < 0) return code; pop(1); return 0; } /* - currentlinecap */ static int zcurrentlinecap(i_ctx_t *i_ctx_p) { os_ptr op = osp; push(1); make_int(op, (int)gs_currentlinecap(igs)); return 0; } /* .setlinejoin - */ static int zsetlinejoin(i_ctx_t *i_ctx_p) { os_ptr op = osp; int param; int code = int_param(op, max_int, ¶m); if (code < 0 || (code = gs_setlinejoin(igs, (gs_line_join) param)) < 0) return code; pop(1); return 0; } /* - currentlinejoin */ static int zcurrentlinejoin(i_ctx_t *i_ctx_p) { os_ptr op = osp; push(1); make_int(op, (int)gs_currentlinejoin(igs)); return 0; } /* setmiterlimit - */ static int zsetmiterlimit(i_ctx_t *i_ctx_p) { return zset_real(i_ctx_p, gs_setmiterlimit); } /* - currentmiterlimit */ static int zcurrentmiterlimit(i_ctx_t *i_ctx_p) { os_ptr op = osp; push(1); make_real(op, gs_currentmiterlimit(igs)); return 0; } /* setdash - */ static int zsetdash(i_ctx_t *i_ctx_p) { os_ptr op = osp; os_ptr op1 = op - 1; double offset; int code = real_param(op, &offset); uint i, n; gs_memory_t *mem = imemory; float *pattern; if (code < 0) return_op_typecheck(op); if (!r_is_array(op1)) return_op_typecheck(op1); /* Adobe interpreters apparently don't check the array for */ /* read access, so we won't either. */ /*check_read(*op1); */ /* Unpack the dash pattern and check it */ n = r_size(op1); pattern = (float *)gs_alloc_byte_array(mem, n, sizeof(float), "setdash"); if (pattern == 0) return_error(gs_error_VMerror); for (i = 0, code = 0; i < n && code >= 0; ++i) { ref element; array_get(mem, op1, (long)i, &element); code = float_param(&element, &pattern[i]); } if (code >= 0) code = gs_setdash(igs, pattern, n, offset); gs_free_object(mem, pattern, "setdash"); /* gs_setdash copies this */ if (code < 0) return code; ref_assign(&istate->dash_pattern_array, op1); pop(2); return code; } /* - currentdash */ static int zcurrentdash(i_ctx_t *i_ctx_p) { os_ptr op = osp; push(2); ref_assign(op - 1, &istate->dash_pattern_array); make_real(op, gs_currentdash_offset(igs)); return 0; } /* setflat - */ static int zsetflat(i_ctx_t *i_ctx_p) { return zset_real(i_ctx_p, gs_setflat); } /* - currentflat */ static int zcurrentflat(i_ctx_t *i_ctx_p) { os_ptr op = osp; push(1); make_real(op, gs_currentflat(igs)); return 0; } /* ------ Extensions ------ */ /* .setaccuratecurves - */ static int zsetaccuratecurves(i_ctx_t *i_ctx_p) { return zset_bool(i_ctx_p, gs_setaccuratecurves); } /* - .currentaccuratecurves */ static int zcurrentaccuratecurves(i_ctx_t *i_ctx_p) { return zcurrent_bool(i_ctx_p, gs_currentaccuratecurves); } /* .setcurvejoin - */ static int zsetcurvejoin(i_ctx_t *i_ctx_p) { os_ptr op = osp; int code; check_type(*op, t_integer); if (op->value.intval < -1 || op->value.intval > max_int) return_error(gs_error_rangecheck); code = gs_setcurvejoin(igs, (int)op->value.intval); if (code < 0) return code; pop(1); return 0; } /* - .currentcurvejoin */ static int zcurrentcurvejoin(i_ctx_t *i_ctx_p) { os_ptr op = osp; push(1); make_int(op, gs_currentcurvejoin(igs)); return 0; } /* .setfilladjust2 - */ static int zsetfilladjust2(i_ctx_t *i_ctx_p) { os_ptr op = osp; double adjust[2]; int code = num_params(op, 2, adjust); if (code < 0) return code; code = gs_setfilladjust(igs, adjust[0], adjust[1]); if (code < 0) return code; pop(2); return 0; } /* - .currentfilladjust2 */ static int zcurrentfilladjust2(i_ctx_t *i_ctx_p) { os_ptr op = osp; gs_point adjust; push(2); gs_currentfilladjust(igs, &adjust); make_real(op - 1, adjust.x); make_real(op, adjust.y); return 0; } /* .setdashadapt - */ static int zsetdashadapt(i_ctx_t *i_ctx_p) { return zset_bool(i_ctx_p, gs_setdashadapt); } /* - .currentdashadapt */ static int zcurrentdashadapt(i_ctx_t *i_ctx_p) { return zcurrent_bool(i_ctx_p, gs_currentdashadapt); } /* .setdotlength - */ static int zsetdotlength(i_ctx_t *i_ctx_p) { os_ptr op = osp; double length; int code = real_param(op - 1, &length); if (code < 0) return code; check_type(*op, t_boolean); code = gs_setdotlength(igs, length, op->value.boolval); if (code < 0) return code; pop(2); return 0; } /* - .currentdotlength */ static int zcurrentdotlength(i_ctx_t *i_ctx_p) { os_ptr op = osp; push(2); make_real(op - 1, gs_currentdotlength(igs)); make_bool(op, gs_currentdotlength_absolute(igs)); return 0; } /* - .setdotorientation - */ static int zsetdotorientation(i_ctx_t *i_ctx_p) { return gs_setdotorientation(igs); } /* - .dotorientation - */ static int zdotorientation(i_ctx_t *i_ctx_p) { return gs_dotorientation(igs); } /* .setlimitclamp - */ static int zsetlimitclamp(i_ctx_t *i_ctx_p) { return zset_bool(i_ctx_p, gs_setlimitclamp); } /* - .currentlimitclamp */ static int zcurrentlimitclamp(i_ctx_t *i_ctx_p) { return zcurrent_bool(i_ctx_p, gs_currentlimitclamp); } /* .settextrenderingmode - */ static int zsettextrenderingmode(i_ctx_t *i_ctx_p) { return zset_uint(i_ctx_p, gs_settextrenderingmode); } /* - .currenttextrenderingmode */ static int zcurrenttextrenderingmode(i_ctx_t *i_ctx_p) { return zcurrent_uint(i_ctx_p, gs_currenttextrenderingmode); } static int zsettextspacing(i_ctx_t *i_ctx_p) { return zset_real(i_ctx_p, gs_settextspacing); } static int zcurrenttextspacing(i_ctx_t *i_ctx_p) { return zcurrent_real(i_ctx_p, gs_currenttextspacing); } static int zsettextleading(i_ctx_t *i_ctx_p) { return zset_real(i_ctx_p, gs_settextleading); } static int zcurrenttextleading(i_ctx_t *i_ctx_p) { return zcurrent_real(i_ctx_p, gs_currenttextleading); } static int zsettextrise(i_ctx_t *i_ctx_p) { return zset_real(i_ctx_p, gs_settextrise); } static int zcurrenttextrise(i_ctx_t *i_ctx_p) { return zcurrent_real(i_ctx_p, gs_currenttextrise); } static int zsetwordspacing(i_ctx_t *i_ctx_p) { return zset_real(i_ctx_p, gs_setwordspacing); } static int zcurrentwordspacing(i_ctx_t *i_ctx_p) { return zcurrent_real(i_ctx_p, gs_currentwordspacing); } static int zsettexthscaling(i_ctx_t *i_ctx_p) { return zset_real(i_ctx_p, gs_settexthscaling); } static int zcurrenttexthscaling(i_ctx_t *i_ctx_p) { return zcurrent_real(i_ctx_p, gs_currenttexthscaling); } static int zsetPDFfontsize(i_ctx_t *i_ctx_p) { return zset_real(i_ctx_p, gs_setPDFfontsize); } static int zcurrentPDFfontsize(i_ctx_t *i_ctx_p) { return zcurrent_real(i_ctx_p, gs_currentPDFfontsize); } /* .sethpglpathmode - */ static int zsethpglpathmode(i_ctx_t *i_ctx_p) { return zset_bool(i_ctx_p, gs_sethpglpathmode); } /* - .currenthpglpathmode */ static int zcurrenthpglpathmode(i_ctx_t *i_ctx_p) { return zcurrent_bool(i_ctx_p, gs_currenthpglpathmode); } /* ------ Initialization procedure ------ */ /* We need to split the table because of the 16-element limit. */ const op_def zgstate1_op_defs[] = { {"0.currentaccuratecurves", zcurrentaccuratecurves}, {"0.currentcurvejoin", zcurrentcurvejoin}, {"0currentdash", zcurrentdash}, {"0.currentdashadapt", zcurrentdashadapt}, {"0.currentdotlength", zcurrentdotlength}, {"0.currentfilladjust2", zcurrentfilladjust2}, {"0currentflat", zcurrentflat}, {"0.currentlimitclamp", zcurrentlimitclamp}, {"0currentlinecap", zcurrentlinecap}, {"0currentlinejoin", zcurrentlinejoin}, {"0currentlinewidth", zcurrentlinewidth}, {"0currentmiterlimit", zcurrentmiterlimit}, {"0.dotorientation", zdotorientation}, {"0grestore", zgrestore}, {"0grestoreall", zgrestoreall}, op_def_end(0) }; const op_def zgstate2_op_defs[] = { {"0gsave", zgsave}, {"0initgraphics", zinitgraphics}, {"1.setaccuratecurves", zsetaccuratecurves}, {"1.setcurvejoin", zsetcurvejoin}, {"2setdash", zsetdash}, {"1.setdashadapt", zsetdashadapt}, {"2.setdotlength", zsetdotlength}, {"0.setdotorientation", zsetdotorientation}, {"2.setfilladjust2", zsetfilladjust2}, {"1.setlimitclamp", zsetlimitclamp}, {"1setflat", zsetflat}, {"1.setlinecap", zsetlinecap}, {"1.setlinejoin", zsetlinejoin}, {"1setlinewidth", zsetlinewidth}, {"1setmiterlimit", zsetmiterlimit}, op_def_end(0) }; const op_def zgstate3_op_defs[] = { {"1.settextrenderingmode", zsettextrenderingmode}, {"0.currenttextrenderingmode", zcurrenttextrenderingmode}, {"1.settextspacing", zsettextspacing}, {"0.currenttextspacing", zcurrenttextspacing}, {"1.settextleading", zsettextleading}, {"0.currenttextleading", zcurrenttextleading}, {"1.settextrise", zsettextrise}, {"0.currenttextrise", zcurrenttextrise}, {"1.setwordspacing", zsetwordspacing}, {"0.currentwordspacing", zcurrentwordspacing}, {"1.settexthscaling", zsettexthscaling}, {"0.currenttexthscaling", zcurrenttexthscaling}, {"0.sethpglpathmode", zsethpglpathmode}, {"0.currenthpglpathmode", zcurrenthpglpathmode}, op_def_end(0) }; const op_def zgstate4_op_defs[] = { {"1.setPDFfontsize", zsetPDFfontsize}, {"0.currentPDFfontsize", zcurrentPDFfontsize}, op_def_end(0) }; /* ------ Internal routines ------ */ /* Allocate the interpreter's part of a graphics state. */ static void * gs_istate_alloc(gs_memory_t * mem) { return gs_alloc_struct(mem, int_gstate, &st_int_gstate, "int_gsave"); } /* Copy the interpreter's part of a graphics state. */ static int gs_istate_copy(void *to, const void *from) { *(int_gstate *) to = *(const int_gstate *)from; return 0; } /* Free the interpreter's part of a graphics state. */ static void gs_istate_free(void *old, gs_memory_t * mem, gs_gstate *pgs) { gs_free_object(mem, old, "int_grestore"); }