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