/src/ghostpdl/psi/zdevice2.c
Line | Count | Source |
1 | | /* Copyright (C) 2001-2026 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
13 | | CA 94129, USA, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* Level 2 device operators */ |
18 | | #include "math_.h" |
19 | | #include "memory_.h" |
20 | | #include "ghost.h" |
21 | | #include "oper.h" |
22 | | #include "dstack.h" /* for dict_find_name */ |
23 | | #include "estack.h" |
24 | | #include "idict.h" |
25 | | #include "idparam.h" |
26 | | #include "igstate.h" |
27 | | #include "iname.h" |
28 | | #include "iutil.h" |
29 | | #include "isave.h" |
30 | | #include "store.h" |
31 | | #include "gxdevice.h" |
32 | | #include "gsstate.h" |
33 | | |
34 | | /* Exported for zfunc4.c */ |
35 | | int z2copy(i_ctx_t *); |
36 | | |
37 | | /* Forward references */ |
38 | | static int z2copy_gstate(i_ctx_t *); |
39 | | static int push_callout(i_ctx_t *, const char *); |
40 | | |
41 | | /* Extend the `copy' operator to deal with gstates. */ |
42 | | /* This is done with a hack -- we know that gstates are the only */ |
43 | | /* t_astruct subtype that implements copy. */ |
44 | | /* We export this for recognition in FunctionType 4 functions. */ |
45 | | int |
46 | | z2copy(i_ctx_t *i_ctx_p) |
47 | 410M | { |
48 | 410M | os_ptr op = osp; |
49 | 410M | int code = zcopy(i_ctx_p); |
50 | | |
51 | 410M | if (code >= 0) |
52 | 410M | return code; |
53 | 71 | if (!r_has_type(op, t_astruct)) |
54 | 71 | return code; |
55 | 0 | return z2copy_gstate(i_ctx_p); |
56 | 71 | } |
57 | | |
58 | | /* - .currentshowpagecount <count> true */ |
59 | | /* - .currentshowpagecount false */ |
60 | | static int |
61 | | zcurrentshowpagecount(i_ctx_t *i_ctx_p) |
62 | 1.72M | { |
63 | 1.72M | os_ptr op = osp; |
64 | 1.72M | gx_device *dev1, *dev = gs_currentdevice(igs); |
65 | | |
66 | 1.72M | if ((*dev_proc(dev, get_page_device))(dev) == 0) { |
67 | 132 | push(1); |
68 | 132 | make_false(op); |
69 | 1.72M | } else { |
70 | 1.72M | dev1 = (*dev_proc(dev, get_page_device))(dev); |
71 | 1.72M | push(2); |
72 | 1.72M | make_int(op - 1, dev1->ShowpageCount); |
73 | 1.72M | make_true(op); |
74 | 1.72M | } |
75 | 1.72M | return 0; |
76 | 1.72M | } |
77 | | |
78 | | /* - .currentpagedevice <dict> <bool> */ |
79 | | static int |
80 | | zcurrentpagedevice(i_ctx_t *i_ctx_p) |
81 | 4.96M | { |
82 | 4.96M | os_ptr op = osp; |
83 | 4.96M | gx_device *dev = gs_currentdevice(igs); |
84 | | |
85 | 4.96M | push(2); |
86 | 4.96M | if ((*dev_proc(dev, get_page_device))(dev) != 0) { |
87 | 4.96M | op[-1] = istate->pagedevice; |
88 | 4.96M | make_true(op); |
89 | 4.96M | } else { |
90 | 307 | make_null(op - 1); |
91 | 307 | make_false(op); |
92 | 307 | } |
93 | 4.96M | return 0; |
94 | 4.96M | } |
95 | | |
96 | | /* <local_dict|null> .setpagedevice - */ |
97 | | static int |
98 | | zsetpagedevice(i_ctx_t *i_ctx_p) |
99 | 2.38M | { |
100 | 2.38M | os_ptr op = osp; |
101 | 2.38M | int code; |
102 | | |
103 | 2.38M | check_op(1); |
104 | | /****** |
105 | | if ( igs->in_cachedevice ) |
106 | | return_error(gs_error_undefined); |
107 | | ******/ |
108 | 2.38M | if (r_has_type(op, t_dictionary)) { |
109 | 2.38M | check_dict_read(*op); |
110 | | #if 0 /****************/ |
111 | | /* |
112 | | * In order to avoid invalidaccess errors on setpagedevice, |
113 | | * the dictionary must be allocated in local VM. |
114 | | */ |
115 | | if (!(r_is_local(op))) |
116 | | return_error(gs_error_invalidaccess); |
117 | | /* Make the dictionary read-only. */ |
118 | | /* This is now done in PostScript in the .postinstall procedure (gs_setpd.ps) |
119 | | * in order to avoid having to use .forceput to override the access |
120 | | * if a new /Install procedure fails. |
121 | | */ |
122 | | code = zreadonly(i_ctx_p); |
123 | | if (code < 0) |
124 | | return code; |
125 | | #endif /****************/ |
126 | 2.38M | } else { |
127 | 101 | check_type(*op, t_null); |
128 | 101 | } |
129 | 2.38M | istate->pagedevice = *op; |
130 | 2.38M | pop(1); |
131 | 2.38M | return 0; |
132 | 2.38M | } |
133 | | |
134 | | /* Default Install/BeginPage/EndPage procedures */ |
135 | | /* that just call the procedure in the device. */ |
136 | | |
137 | | /* - .callinstall - */ |
138 | | static int |
139 | | zcallinstall(i_ctx_t *i_ctx_p) |
140 | 595k | { |
141 | 595k | gx_device *dev = gs_currentdevice(igs); |
142 | | |
143 | 595k | if ((dev = (*dev_proc(dev, get_page_device))(dev)) != 0) { |
144 | 595k | int code = (*dev->page_procs.install) (dev, igs); |
145 | | |
146 | 595k | if (code < 0) |
147 | 0 | return code; |
148 | 595k | } |
149 | 595k | return 0; |
150 | 595k | } |
151 | | |
152 | | /* <showpage_count> .callbeginpage - */ |
153 | | static int |
154 | | zcallbeginpage(i_ctx_t *i_ctx_p) |
155 | 783k | { |
156 | 783k | os_ptr op = osp; |
157 | 783k | gx_device *dev = gs_currentdevice(igs); |
158 | | |
159 | 783k | check_op(1); |
160 | 783k | check_type(*op, t_integer); |
161 | 783k | if ((dev = (*dev_proc(dev, get_page_device))(dev)) != 0) { |
162 | 783k | int code = (*dev->page_procs.begin_page)(dev, igs); |
163 | | |
164 | 783k | if (code < 0) |
165 | 0 | return code; |
166 | 783k | } |
167 | 783k | pop(1); |
168 | 783k | return 0; |
169 | 783k | } |
170 | | |
171 | | /* <showpage_count> <reason_int> .callendpage <flush_bool> */ |
172 | | static int |
173 | | zcallendpage(i_ctx_t *i_ctx_p) |
174 | 937k | { |
175 | 937k | os_ptr op = osp; |
176 | 937k | gx_device *dev = gs_currentdevice(igs); |
177 | 937k | int code; |
178 | | |
179 | 937k | check_op(2); |
180 | 937k | check_type(op[-1], t_integer); |
181 | 937k | check_type(*op, t_integer); |
182 | 937k | if ((dev = (*dev_proc(dev, get_page_device))(dev)) != 0) { |
183 | 937k | code = (*dev->page_procs.end_page)(dev, (int)op->value.intval, igs); |
184 | 937k | if (code < 0) |
185 | 0 | return code; |
186 | 937k | if (code > 1) |
187 | 0 | return_error(gs_error_rangecheck); |
188 | 937k | } else { |
189 | 0 | code = (op->value.intval == 2 ? 0 : 1); |
190 | 0 | } |
191 | 937k | make_bool(op - 1, code); |
192 | 937k | pop(1); |
193 | 937k | return 0; |
194 | 937k | } |
195 | | |
196 | | /* ------ Wrappers for operators that save the graphics state. ------ */ |
197 | | |
198 | | /* When saving the state with the current device a page device, */ |
199 | | /* we need to make sure that the page device dictionary exists */ |
200 | | /* so that grestore can use it to reset the device parameters. */ |
201 | | /* This may have significant performance consequences, but we don't see */ |
202 | | /* any way around it. */ |
203 | | |
204 | | /* Check whether we need to call out to create the page device dictionary. */ |
205 | | static bool |
206 | | save_page_device(gs_gstate *pgs) |
207 | 5.72M | { |
208 | 5.72M | return |
209 | 5.72M | (r_has_type(&gs_int_gstate(pgs)->pagedevice, t_null) && |
210 | 294k | (*dev_proc(gs_currentdevice(pgs), get_page_device))(gs_currentdevice(pgs)) != 0); |
211 | 5.72M | } |
212 | | |
213 | | /* - gsave - */ |
214 | | static int |
215 | | z2gsave(i_ctx_t *i_ctx_p) |
216 | 4.62M | { |
217 | 4.62M | if (!save_page_device(igs)) |
218 | 4.47M | return gs_gsave(igs); |
219 | 146k | return push_callout(i_ctx_p, "%gsavepagedevice"); |
220 | 4.62M | } |
221 | | |
222 | | /* - save - */ |
223 | | static int |
224 | | z2save(i_ctx_t *i_ctx_p) |
225 | 1.09M | { |
226 | 1.09M | if (!save_page_device(igs)) |
227 | 951k | return zsave(i_ctx_p); |
228 | 147k | return push_callout(i_ctx_p, "%savepagedevice"); |
229 | 1.09M | } |
230 | | |
231 | | /* - gstate <gstate> */ |
232 | | static int |
233 | | z2gstate(i_ctx_t *i_ctx_p) |
234 | 262 | { |
235 | 262 | if (!save_page_device(igs)) |
236 | 262 | return zgstate(i_ctx_p); |
237 | 0 | return push_callout(i_ctx_p, "%gstatepagedevice"); |
238 | 262 | } |
239 | | |
240 | | /* <gstate1> <gstate2> copy <gstate2> */ |
241 | | static int |
242 | | z2copy_gstate(i_ctx_t *i_ctx_p) |
243 | 0 | { |
244 | 0 | os_ptr op = osp; |
245 | 0 | check_op(2); |
246 | 0 | if (!save_page_device(igs)) |
247 | 0 | return zcopy_gstate(i_ctx_p); |
248 | 0 | return push_callout(i_ctx_p, "%copygstatepagedevice"); |
249 | 0 | } |
250 | | |
251 | | /* <gstate> currentgstate <gstate> */ |
252 | | static int |
253 | | z2currentgstate(i_ctx_t *i_ctx_p) |
254 | 18 | { |
255 | 18 | os_ptr op = osp; |
256 | 18 | check_op(1); |
257 | 8 | if (!save_page_device(igs)) |
258 | 8 | return zcurrentgstate(i_ctx_p); |
259 | 0 | return push_callout(i_ctx_p, "%currentgstatepagedevice"); |
260 | 8 | } |
261 | | |
262 | | /* ------ Wrappers for operators that reset the graphics state. ------ */ |
263 | | |
264 | | /* Check whether we need to call out to restore the page device. */ |
265 | | static int |
266 | | restore_page_device(i_ctx_t *i_ctx_p, const gs_gstate * pgs_old, const gs_gstate * pgs_new) |
267 | 9.24M | { |
268 | 9.24M | gx_device *dev_old = gs_currentdevice(pgs_old); |
269 | 9.24M | gx_device *dev_new; |
270 | 9.24M | gx_device *dev_t1; |
271 | 9.24M | gx_device *dev_t2; |
272 | 9.24M | bool samepagedevice = obj_eq(dev_old->memory, &gs_int_gstate(pgs_old)->pagedevice, |
273 | 9.24M | &gs_int_gstate(pgs_new)->pagedevice); |
274 | 9.24M | bool LockSafetyParams = dev_old->LockSafetyParams; |
275 | | |
276 | 9.24M | if ((dev_t1 = (*dev_proc(dev_old, get_page_device)) (dev_old)) == 0) |
277 | 84.3k | return 0; |
278 | | /* If we are going to putdeviceparams in a callout, we need to */ |
279 | | /* unlock temporarily. The device will be re-locked as needed */ |
280 | | /* by putdeviceparams from the pgs_old->pagedevice dict state. */ |
281 | 9.16M | if (!samepagedevice) |
282 | 84.2k | dev_old->LockSafetyParams = false; |
283 | 9.16M | dev_new = gs_currentdevice(pgs_new); |
284 | 9.16M | if (dev_old != dev_new) { |
285 | 896 | if ((dev_t2 = (*dev_proc(dev_new, get_page_device)) (dev_new)) == 0) |
286 | 0 | samepagedevice = true; |
287 | 896 | else if (dev_t1 != dev_t2) |
288 | 896 | samepagedevice = false; |
289 | 896 | } |
290 | | |
291 | 9.16M | if (LockSafetyParams) { |
292 | 0 | const int required_ops = 512; |
293 | 0 | const int required_es = 32; |
294 | | |
295 | | /* The %grestorepagedevice must complete: the biggest danger |
296 | | is operand stack overflow. As we use get/putdeviceparams |
297 | | that means pushing all the device params onto the stack, |
298 | | pdfwrite having by far the largest number of parameters |
299 | | at (currently) 212 key/value pairs - thus needing (currently) |
300 | | 424 entries on the op stack. Allowing for working stack |
301 | | space, and safety margin..... |
302 | | */ |
303 | 0 | if (required_ops + ref_stack_count(&o_stack) >= ref_stack_max_count(&o_stack)) { |
304 | 0 | gs_currentdevice(pgs_old)->LockSafetyParams = LockSafetyParams; |
305 | 0 | return_error(gs_error_stackoverflow); |
306 | 0 | } |
307 | | /* We also want enough exec stack space - 32 is an overestimate of |
308 | | what we need to complete the Postscript call out. |
309 | | */ |
310 | 0 | if (required_es + ref_stack_count(&e_stack) >= ref_stack_max_count(&e_stack)) { |
311 | 0 | gs_currentdevice(pgs_old)->LockSafetyParams = LockSafetyParams; |
312 | 0 | return_error(gs_error_execstackoverflow); |
313 | 0 | } |
314 | 0 | } |
315 | | /* |
316 | | * The current implementation of setpagedevice just sets new |
317 | | * parameters in the same device object, so we have to check |
318 | | * whether the page device dictionaries are the same. |
319 | | */ |
320 | 9.16M | return samepagedevice ? 0 : 1; |
321 | 9.16M | } |
322 | | |
323 | | /* - grestore - */ |
324 | | static int |
325 | | z2grestore(i_ctx_t *i_ctx_p) |
326 | 9.03M | { |
327 | 9.03M | int code = restore_page_device(i_ctx_p, igs, gs_gstate_saved(igs)); |
328 | 9.03M | if (code < 0) return code; |
329 | | |
330 | 9.03M | if (code == 0) |
331 | 9.03M | return gs_grestore(igs); |
332 | 4.47k | return push_callout(i_ctx_p, "%grestorepagedevice"); |
333 | 9.03M | } |
334 | | |
335 | | /* - grestoreall - */ |
336 | | static int |
337 | | z2grestoreall(i_ctx_t *i_ctx_p) |
338 | 253 | { |
339 | 258 | for (;;) { |
340 | 258 | int code = restore_page_device(i_ctx_p, igs, gs_gstate_saved(igs)); |
341 | 258 | if (code < 0) return code; |
342 | 258 | if (code == 0) { |
343 | 128 | bool done = !gs_gstate_saved(gs_gstate_saved(igs)); |
344 | | |
345 | 128 | gs_grestore(igs); |
346 | 128 | if (done) |
347 | 123 | break; |
348 | 128 | } else |
349 | 130 | return push_callout(i_ctx_p, "%grestoreallpagedevice"); |
350 | 258 | } |
351 | 123 | return 0; |
352 | 253 | } |
353 | | /* This is the Level 2+ variant of restore - which adds restoring |
354 | | of the page device to the Level 1 variant in zvmem.c. |
355 | | Previous this restored the device state before calling zrestore.c |
356 | | which validated operands etc, meaning a restore could error out |
357 | | partially complete. |
358 | | The operand checking, and actual VM restore are now in two functions |
359 | | so they can called separately thus, here, we can do as much |
360 | | checking as possible, before embarking on actual changes |
361 | | */ |
362 | | /* <save> restore - */ |
363 | | static int |
364 | | z2restore(i_ctx_t *i_ctx_p) |
365 | 181k | { |
366 | 181k | alloc_save_t *asave; |
367 | 181k | bool saveLockSafety = gs_currentdevice_inline(igs)->LockSafetyParams; |
368 | 181k | int code = restore_check_save(i_ctx_p, &asave); |
369 | | |
370 | 181k | if (code < 0) return code; |
371 | | |
372 | 208k | while (gs_gstate_saved(gs_gstate_saved(igs))) { |
373 | 26.7k | code = restore_page_device(i_ctx_p, igs, gs_gstate_saved(igs)); |
374 | 26.7k | if (code < 0) return code; |
375 | 26.7k | if (code > 0) |
376 | 0 | return push_callout(i_ctx_p, "%restore1pagedevice"); |
377 | 26.7k | gs_grestore(igs); |
378 | 26.7k | } |
379 | 181k | code = restore_page_device(i_ctx_p, igs, gs_gstate_saved(igs)); |
380 | 181k | if (code < 0) return code; |
381 | 181k | if (code > 0) |
382 | 79.6k | return push_callout(i_ctx_p, "%restorepagedevice"); |
383 | | |
384 | 101k | code = dorestore(i_ctx_p, asave); |
385 | | |
386 | 101k | if (code < 0) { |
387 | | /* An error here is basically fatal, but.... |
388 | | restore_page_device() has to set LockSafetyParams false so it can |
389 | | configure the restored device correctly - in normal operation, that |
390 | | gets reset by that configuration. If we hit an error, though, that |
391 | | may not happen - at least ensure we keep the setting through the |
392 | | error. |
393 | | */ |
394 | 0 | gs_currentdevice_inline(igs)->LockSafetyParams = saveLockSafety; |
395 | 0 | } |
396 | 101k | return code; |
397 | 181k | } |
398 | | |
399 | | /* <gstate> setgstate - */ |
400 | | static int |
401 | | z2setgstate(i_ctx_t *i_ctx_p) |
402 | 18 | { |
403 | 18 | os_ptr op = osp; |
404 | 18 | int code; |
405 | | |
406 | 18 | check_op(1); |
407 | 10 | check_stype(*op, st_igstate_obj); |
408 | 6 | code = restore_page_device(i_ctx_p, igs, igstate_ptr(op)); |
409 | 6 | if (code < 0) return code; |
410 | 6 | if (code == 0) |
411 | 6 | return zsetgstate(i_ctx_p); |
412 | 0 | return push_callout(i_ctx_p, "%setgstatepagedevice"); |
413 | 6 | } |
414 | | |
415 | | /* ------ Initialization procedure ------ */ |
416 | | |
417 | | const op_def zdevice2_l2_op_defs[] = |
418 | | { |
419 | | op_def_begin_level2(), |
420 | | {"0.currentshowpagecount", zcurrentshowpagecount}, |
421 | | {"0.currentpagedevice", zcurrentpagedevice}, |
422 | | {"1.setpagedevice", zsetpagedevice}, |
423 | | /* Note that the following replace prior definitions */ |
424 | | /* in the indicated files: */ |
425 | | {"1copy", z2copy}, /* zdps1.c */ |
426 | | {"0gsave", z2gsave}, /* zgstate.c */ |
427 | | {"0save", z2save}, /* zvmem.c */ |
428 | | {"0gstate", z2gstate}, /* zdps1.c */ |
429 | | {"1currentgstate", z2currentgstate}, /* zdps1.c */ |
430 | | {"0grestore", z2grestore}, /* zgstate.c */ |
431 | | {"0grestoreall", z2grestoreall}, /* zgstate.c */ |
432 | | {"1restore", z2restore}, /* zvmem.c */ |
433 | | {"1setgstate", z2setgstate}, /* zdps1.c */ |
434 | | /* Default Install/BeginPage/EndPage procedures */ |
435 | | /* that just call the procedure in the device. */ |
436 | | {"0.callinstall", zcallinstall}, |
437 | | {"1.callbeginpage", zcallbeginpage}, |
438 | | {"2.callendpage", zcallendpage}, |
439 | | op_def_end(0) |
440 | | }; |
441 | | |
442 | | /* ------ Internal routines ------ */ |
443 | | |
444 | | /* Call out to a PostScript procedure. */ |
445 | | static int |
446 | | push_callout(i_ctx_t *i_ctx_p, const char *callout_name) |
447 | 378k | { |
448 | 378k | int code; |
449 | | |
450 | 378k | check_estack(1); |
451 | 378k | code = name_enter_string(imemory, callout_name, esp + 1); |
452 | 378k | if (code < 0) |
453 | 0 | return code; |
454 | 378k | ++esp; |
455 | 378k | r_set_attrs(esp, a_executable); |
456 | 378k | return o_push_estack; |
457 | 378k | } |