/src/ghostpdl/base/gsdevice.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2001-2024 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 | | /* Device operators for Ghostscript library */ |
18 | | #include "ctype_.h" |
19 | | #include "memory_.h" /* for memchr, memcpy */ |
20 | | #include "string_.h" |
21 | | #include "gx.h" |
22 | | #include "gp.h" |
23 | | #include "gscdefs.h" /* for gs_lib_device_list */ |
24 | | #include "gserrors.h" |
25 | | #include "gsfname.h" |
26 | | #include "gsstruct.h" |
27 | | #include "gspath.h" /* gs_initclip prototype */ |
28 | | #include "gspaint.h" /* gs_erasepage prototype */ |
29 | | #include "gsmatrix.h" /* for gscoord.h */ |
30 | | #include "gscoord.h" /* for gs_initmatrix */ |
31 | | #include "gzstate.h" |
32 | | #include "gxcmap.h" |
33 | | #include "gxdevice.h" |
34 | | #include "gxdevmem.h" |
35 | | #include "gxdevsop.h" |
36 | | #include "gxiodev.h" |
37 | | #include "gxcspace.h" |
38 | | #include "gsicc_manage.h" |
39 | | #include "gscms.h" |
40 | | #include "gxgetbit.h" |
41 | | |
42 | | /* Include the extern for the device list. */ |
43 | | extern_gs_lib_device_list(); |
44 | | |
45 | | /* |
46 | | * Finalization for devices: do any special finalization first, then |
47 | | * close the device if it is open, and finally free the structure |
48 | | * descriptor if it is dynamic. |
49 | | */ |
50 | | void |
51 | | gx_device_finalize(const gs_memory_t *cmem, void *vptr) |
52 | 169M | { |
53 | 169M | gx_device * const dev = (gx_device *)vptr; |
54 | 169M | (void)cmem; /* unused */ |
55 | | |
56 | 169M | discard(gs_closedevice(dev)); |
57 | | |
58 | 169M | if (dev->icc_struct != NULL) { |
59 | 2.33M | rc_decrement(dev->icc_struct, "gx_device_finalize(icc_profile)"); |
60 | 2.33M | } |
61 | | |
62 | | /* Deal with subclassed devices. Ordinarily these should not be a problem, we |
63 | | * will never see them, but if ths is a end of job restore we can end up |
64 | | * with the 'child' device(s) being freed before their parents. We need to make |
65 | | * sure we don't leave any dangling pointers in that case. |
66 | | */ |
67 | 169M | if (dev->child) |
68 | 0 | dev->child->parent = dev->parent; |
69 | 169M | if (dev->parent) |
70 | 31.6k | dev->parent->child = dev->child; |
71 | 169M | if (dev->PageList) { |
72 | 0 | rc_decrement(dev->PageList, "gx_device_finalize(PageList)"); |
73 | 0 | dev->PageList = 0; |
74 | 0 | } |
75 | 169M | if (dev->NupControl) { |
76 | 0 | rc_decrement(dev->NupControl, "gx_device_finalize(NupControl)"); |
77 | 0 | dev->NupControl = 0; |
78 | 0 | } |
79 | | |
80 | 169M | if (dev->finalize) |
81 | 169M | dev->finalize(dev); |
82 | | |
83 | 169M | if (dev->stype_is_dynamic) |
84 | 99.5k | gs_free_const_object(dev->memory->non_gc_memory, dev->stype, |
85 | 99.5k | "gx_device_finalize"); |
86 | | |
87 | | #ifdef DEBUG |
88 | | /* Slightly ugly hack: because the garbage collector makes no promises |
89 | | * about the order objects can be garbage collected, it is possible for |
90 | | * a forwarding device to remain in existence (awaiting garbage collection |
91 | | * itself) after it's target marked as free memory by the garbage collector. |
92 | | * In such a case, the normal reference counting is fine (since the garbage |
93 | | * collector leaves the object contents alone until is has completed its |
94 | | * sweep), but the reference counting debugging attempts to access the |
95 | | * memory header to output type information - and the header has been |
96 | | * overwritten by the garbage collector, causing a crash. |
97 | | * Setting the rc memory to NULL here should be safe, since the memory |
98 | | * is now in the hands of the garbage collector, and means we can check in |
99 | | * debugging code to ensure we don't try to use values that not longer exist |
100 | | * in the memmory header. |
101 | | * In the non-gc case, finalize is the very last thing to happen before the |
102 | | * memory is actually freed, so the rc.memory pointer is moot. |
103 | | * See rc_object_type_name() |
104 | | */ |
105 | | if (gs_debug_c('^')) |
106 | | dev->rc.memory = NULL; |
107 | | #endif |
108 | 169M | } |
109 | | |
110 | | /* "Free" a device locally allocated on the stack, by finalizing it. */ |
111 | | void |
112 | | gx_device_free_local(gx_device *dev) |
113 | 0 | { |
114 | 0 | gx_device_finalize(dev->memory, dev); |
115 | 0 | } |
116 | | |
117 | | /* GC procedures */ |
118 | | static |
119 | 1.37M | ENUM_PTRS_WITH(device_enum_ptrs, gx_device *dev) return 0; |
120 | 457k | case 0:ENUM_RETURN(gx_device_enum_ptr(dev->parent)); |
121 | 457k | case 1:ENUM_RETURN(gx_device_enum_ptr(dev->child)); |
122 | 1.37M | ENUM_PTRS_END |
123 | 457k | static RELOC_PTRS_WITH(device_reloc_ptrs, gx_device *dev) |
124 | 457k | { |
125 | 457k | dev->parent = gx_device_reloc_ptr(dev->parent, gcst); |
126 | 457k | dev->child = gx_device_reloc_ptr(dev->child, gcst); |
127 | 457k | } |
128 | 457k | RELOC_PTRS_END |
129 | | static |
130 | 618k | ENUM_PTRS_WITH(device_forward_enum_ptrs, gx_device_forward *fdev) return 0; |
131 | 208k | case 0: ENUM_RETURN(gx_device_enum_ptr(fdev->target)); |
132 | 618k | ENUM_PTRS_END |
133 | 208k | static RELOC_PTRS_WITH(device_forward_reloc_ptrs, gx_device_forward *fdev) |
134 | 208k | { |
135 | 208k | fdev->target = gx_device_reloc_ptr(fdev->target, gcst); |
136 | 208k | } |
137 | 208k | RELOC_PTRS_END |
138 | | |
139 | | /* |
140 | | * Structure descriptors. These must follow the procedures, because |
141 | | * we can't conveniently forward-declare the procedures. |
142 | | * (See gxdevice.h for details.) |
143 | | */ |
144 | | public_st_device(); |
145 | | public_st_device_forward(); |
146 | | public_st_device_null(); |
147 | | |
148 | | /* GC utilities */ |
149 | | /* Enumerate or relocate a device pointer for a client. */ |
150 | | gx_device * |
151 | | gx_device_enum_ptr(gx_device * dev) |
152 | 17.7M | { |
153 | 17.7M | if (dev == 0 || dev->memory == 0) |
154 | 1.52M | return 0; |
155 | 16.2M | return dev; |
156 | 17.7M | } |
157 | | gx_device * |
158 | | gx_device_reloc_ptr(gx_device * dev, gc_state_t * gcst) |
159 | 17.7M | { |
160 | 17.7M | if (dev == 0 || dev->memory == 0) |
161 | 1.52M | return dev; |
162 | 16.2M | return RELOC_OBJ(dev); /* gcst implicit */ |
163 | 17.7M | } |
164 | | |
165 | | /* Flush buffered output to the device */ |
166 | | int |
167 | | gs_flushpage(gs_gstate * pgs) |
168 | 162k | { |
169 | 162k | gx_device *dev = gs_currentdevice(pgs); |
170 | | |
171 | 162k | return (*dev_proc(dev, sync_output)) (dev); |
172 | 162k | } |
173 | | |
174 | | /* Make the device output the accumulated page description */ |
175 | | int |
176 | | gs_copypage(gs_gstate * pgs) |
177 | 0 | { |
178 | 0 | return gs_output_page(pgs, 1, 0); |
179 | 0 | } |
180 | | int |
181 | | gs_output_page(gs_gstate * pgs, int num_copies, int flush) |
182 | 133k | { |
183 | 133k | gx_device *dev = gs_currentdevice(pgs); |
184 | 133k | cmm_dev_profile_t *dev_profile; |
185 | 133k | int code; |
186 | | |
187 | | /* for devices that hook 'fill_path' in order to pick up gs_gstate */ |
188 | | /* values such as dev_ht (such as tiffsep1), make a dummy call here */ |
189 | | /* to make sure that it has been called at least once */ |
190 | 133k | code = gs_gsave(pgs); |
191 | 133k | if (code < 0) |
192 | 0 | return code; |
193 | 133k | if (((code = gs_newpath(pgs)) < 0) || |
194 | 133k | ((code = gs_moveto(pgs, 0.0, 0.0)) < 0) || |
195 | 133k | ((code = gs_setgray(pgs, 0.0)) < 0) || |
196 | 133k | ((code = gs_fill(pgs)) < 0)) |
197 | 0 | { |
198 | 0 | gs_grestore(pgs); |
199 | 0 | return code; |
200 | 0 | } |
201 | 133k | code = gs_grestore(pgs); |
202 | 133k | if (code < 0) |
203 | 0 | return code; |
204 | | |
205 | 133k | if (dev->IgnoreNumCopies) |
206 | 0 | num_copies = 1; |
207 | 133k | if ((code = (*dev_proc(dev, output_page)) (dev, num_copies, flush)) < 0) |
208 | 13.3k | return code; |
209 | | |
210 | 119k | code = dev_proc(dev, get_profile)(dev, &(dev_profile)); |
211 | 119k | if (code < 0) |
212 | 0 | return code; |
213 | 119k | if (dev_profile->graydetection && !dev_profile->pageneutralcolor) { |
214 | 0 | dev_profile->pageneutralcolor = true; /* start detecting again */ |
215 | 0 | code = gsicc_mcm_begin_monitor(pgs->icc_link_cache, dev); |
216 | 0 | } |
217 | 119k | return code; |
218 | 119k | } |
219 | | |
220 | | /* |
221 | | * Do generic work for output_page. All output_page procedures must call |
222 | | * this as the last thing they do, unless an error has occurred earlier. |
223 | | */ |
224 | | int |
225 | | gx_finish_output_page(gx_device *dev, int num_copies, int flush) |
226 | 119k | { |
227 | 119k | dev->PageCount += num_copies; |
228 | 119k | return 0; |
229 | 119k | } |
230 | | |
231 | | /* Copy scan lines from an image device */ |
232 | | int |
233 | | gs_copyscanlines(gx_device * dev, int start_y, byte * data, uint size, |
234 | | int *plines_copied, uint * pbytes_copied) |
235 | 0 | { |
236 | 0 | uint line_size = gx_device_raster(dev, 0); |
237 | 0 | uint count = size / line_size; |
238 | 0 | uint i; |
239 | 0 | byte *dest = data; |
240 | 0 | gs_int_rect rect; |
241 | 0 | gs_get_bits_params_t params; |
242 | |
|
243 | 0 | rect.p.x = 0; |
244 | 0 | rect.q.x = dev->width; |
245 | 0 | params.x_offset = 0; |
246 | 0 | params.raster = bitmap_raster(dev->width * dev->color_info.depth); |
247 | |
|
248 | 0 | for (i = 0; i < count; i++, dest += line_size) { |
249 | 0 | int code; |
250 | |
|
251 | 0 | rect.p.y = start_y+i; |
252 | 0 | rect.q.y = start_y+i+1; |
253 | |
|
254 | 0 | params.options = (GB_ALIGN_ANY | |
255 | 0 | GB_RETURN_COPY | |
256 | 0 | GB_OFFSET_0 | |
257 | 0 | GB_RASTER_STANDARD | GB_PACKING_CHUNKY | |
258 | 0 | GB_COLORS_NATIVE | GB_ALPHA_NONE); |
259 | 0 | params.data[0] = dest; |
260 | 0 | code = (*dev_proc(dev, get_bits_rectangle))(dev, &rect, ¶ms); |
261 | 0 | if (code < 0) { |
262 | | /* Might just be an overrun. */ |
263 | 0 | if (start_y + i == dev->height) |
264 | 0 | break; |
265 | 0 | return_error(code); |
266 | 0 | } |
267 | 0 | } |
268 | 0 | if (plines_copied != NULL) |
269 | 0 | *plines_copied = i; |
270 | 0 | if (pbytes_copied != NULL) |
271 | 0 | *pbytes_copied = i * line_size; |
272 | 0 | return 0; |
273 | 0 | } |
274 | | |
275 | | /* Get the current device from the graphics state. */ |
276 | | gx_device * |
277 | | gs_currentdevice(const gs_gstate * pgs) |
278 | 73.9M | { |
279 | 73.9M | return pgs->device; |
280 | 73.9M | } |
281 | | |
282 | | /* Get the name of a device. */ |
283 | | const char * |
284 | | gs_devicename(const gx_device * dev) |
285 | 0 | { |
286 | 0 | return dev->dname; |
287 | 0 | } |
288 | | |
289 | | /* Get the initial matrix of a device. */ |
290 | | void |
291 | | gs_deviceinitialmatrix(gx_device * dev, gs_matrix * pmat) |
292 | 169M | { |
293 | 169M | fill_dev_proc(dev, get_initial_matrix, gx_default_get_initial_matrix); |
294 | 169M | (*dev_proc(dev, get_initial_matrix)) (dev, pmat); |
295 | 169M | } |
296 | | |
297 | | /* Get the N'th device from the known device list */ |
298 | | const gx_device * |
299 | | gs_getdevice(int index) |
300 | 7.14M | { |
301 | 7.14M | const gx_device *const *list; |
302 | 7.14M | int count = gs_lib_device_list(&list, NULL); |
303 | | |
304 | 7.14M | if (index < 0 || index >= count) |
305 | 324k | return 0; /* index out of range */ |
306 | 6.82M | return list[index]; |
307 | 7.14M | } |
308 | | |
309 | | /* Get the default device from the known device list */ |
310 | | const gx_device * |
311 | | gs_getdefaultlibdevice(gs_memory_t *mem) |
312 | 0 | { |
313 | 0 | const gx_device *const *list; |
314 | 0 | int count = gs_lib_device_list(&list, NULL); |
315 | 0 | const char *name, *end, *fin; |
316 | 0 | int i; |
317 | | |
318 | | /* Search the compiled in device list for a known device name */ |
319 | | /* In the case the lib ctx hasn't been initialised */ |
320 | 0 | if (mem && mem->gs_lib_ctx && mem->gs_lib_ctx->default_device_list) { |
321 | 0 | name = mem->gs_lib_ctx->default_device_list; |
322 | 0 | fin = name + strlen(name); |
323 | 0 | } |
324 | 0 | else { |
325 | 0 | name = gs_dev_defaults; |
326 | 0 | fin = name + strlen(name); |
327 | 0 | } |
328 | | |
329 | | /* iterate through each name in the string */ |
330 | 0 | while (name < fin) { |
331 | | |
332 | | /* split a name from any whitespace */ |
333 | 0 | while ((name < fin) && (*name == ' ' || *name == '\t')) |
334 | 0 | name++; |
335 | 0 | end = name; |
336 | 0 | while ((end < fin) && (*end != ' ') && (*end != '\t')) |
337 | 0 | end++; |
338 | | |
339 | | /* return any matches */ |
340 | 0 | for (i = 0; i < count; i++) |
341 | 0 | if ((end - name) == strlen(list[i]->dname)) |
342 | 0 | if (!memcmp(name, list[i]->dname, end - name)) |
343 | 0 | return gs_getdevice(i); |
344 | | |
345 | | /* otherwise, try the next device name */ |
346 | 0 | name = end; |
347 | 0 | } |
348 | | |
349 | | /* Fall back to the first device in the list. */ |
350 | 0 | return gs_getdevice(0); |
351 | 0 | } |
352 | | |
353 | | const gx_device * |
354 | | gs_getdefaultdevice(void) |
355 | 0 | { |
356 | 0 | return gs_getdefaultlibdevice(NULL); |
357 | 0 | } |
358 | | |
359 | | /* Fill in the GC structure descriptor for a device. */ |
360 | | static void |
361 | | gx_device_make_struct_type(gs_memory_struct_type_t *st, |
362 | | const gx_device *dev) |
363 | 67.9k | { |
364 | 67.9k | if (dev->stype) |
365 | 67.9k | *st = *dev->stype; |
366 | 0 | else if (dev_proc(dev, get_page_device) == gx_forward_get_page_device) |
367 | 0 | *st = st_device_forward; |
368 | 0 | else |
369 | 0 | *st = st_device; |
370 | 67.9k | st->ssize = dev->params_size; |
371 | 67.9k | } |
372 | | |
373 | | /* Clone an existing device. */ |
374 | | int |
375 | | gs_copydevice2(gx_device ** pnew_dev, const gx_device * dev, bool keep_open, |
376 | | gs_memory_t * mem) |
377 | 2.52M | { |
378 | 2.52M | gx_device *new_dev; |
379 | 2.52M | const gs_memory_struct_type_t *std = dev->stype; |
380 | 2.52M | const gs_memory_struct_type_t *new_std; |
381 | 2.52M | gs_memory_struct_type_t *a_std = 0; |
382 | 2.52M | int code; |
383 | | |
384 | 2.52M | if (dev->stype_is_dynamic) { |
385 | | /* |
386 | | * We allocated the stype for this device previously. |
387 | | * Just allocate a new stype and copy the old one into it. |
388 | | */ |
389 | 0 | a_std = (gs_memory_struct_type_t *) |
390 | 0 | gs_alloc_bytes_immovable(mem->non_gc_memory, sizeof(*std), |
391 | 0 | "gs_copydevice(stype)"); |
392 | 0 | if (!a_std) |
393 | 0 | return_error(gs_error_VMerror); |
394 | 0 | *a_std = *std; |
395 | 0 | new_std = a_std; |
396 | 2.52M | } else if (std != 0 && std->ssize == dev->params_size) { |
397 | | /* Use the static stype. */ |
398 | 2.45M | new_std = std; |
399 | 2.45M | } else { |
400 | | /* We need to figure out or adjust the stype. */ |
401 | 67.9k | a_std = (gs_memory_struct_type_t *) |
402 | 67.9k | gs_alloc_bytes_immovable(mem->non_gc_memory, sizeof(*std), |
403 | 67.9k | "gs_copydevice(stype)"); |
404 | 67.9k | if (!a_std) |
405 | 0 | return_error(gs_error_VMerror); |
406 | 67.9k | gx_device_make_struct_type(a_std, dev); |
407 | 67.9k | new_std = a_std; |
408 | 67.9k | } |
409 | | /* |
410 | | * Because command list devices have complicated internal pointer |
411 | | * structures, we allocate all device instances as immovable. |
412 | | */ |
413 | 2.52M | new_dev = gs_alloc_struct_immovable(mem, gx_device, new_std, |
414 | 2.52M | "gs_copydevice(device)"); |
415 | 2.52M | if (new_dev == 0) { |
416 | 0 | gs_free_object(mem->non_gc_memory, a_std, "gs_copydevice(stype)"); |
417 | 0 | return_error(gs_error_VMerror); |
418 | 0 | } |
419 | 2.52M | code = gx_device_init(new_dev, dev, mem, false); |
420 | 2.52M | new_dev->stype = new_std; |
421 | 2.52M | new_dev->stype_is_dynamic = new_std != std; |
422 | | /* |
423 | | * keep_open is very dangerous. On the other hand, so is copydevice in |
424 | | * general, since it just copies the bits without any regard to pointers |
425 | | * (including self-pointers) that they may contain. |
426 | | */ |
427 | 2.52M | new_dev->is_open = dev->is_open && keep_open; |
428 | 2.52M | if (code < 0) { |
429 | 0 | gs_free_object(mem, new_dev, "gs_copydevice(device)"); |
430 | | #if 0 /* gs_free_object above calls gx_device_finalize, |
431 | | which closes the device and releases its stype, i.e. a_std. */ |
432 | | if (a_std) |
433 | | gs_free_object(dev->memory->non_gc_memory, a_std, "gs_copydevice(stype)"); |
434 | | #endif |
435 | 0 | return code; |
436 | 0 | } |
437 | | /* We really want to be able to interrogate the device for capabilities |
438 | | * and/or preferences right from when it is created, so set dev_spec_op |
439 | | * now (if not already set). |
440 | | */ |
441 | 2.52M | fill_dev_proc(new_dev, dev_spec_op, gx_default_dev_spec_op); |
442 | 2.52M | *pnew_dev = new_dev; |
443 | 2.52M | return 0; |
444 | 2.52M | } |
445 | | int |
446 | | gs_copydevice(gx_device ** pnew_dev, const gx_device * dev, gs_memory_t * mem) |
447 | 2.20M | { |
448 | 2.20M | return gs_copydevice2(pnew_dev, dev, false, mem); |
449 | 2.20M | } |
450 | | |
451 | | /* Open a device if not open already. Return 0 if the device was open, */ |
452 | | /* 1 if it was closed. */ |
453 | | int |
454 | | gs_opendevice(gx_device *dev) |
455 | 718k | { |
456 | 718k | if (dev->is_open) |
457 | 0 | return 0; |
458 | 718k | check_device_separable(dev); |
459 | 718k | gx_device_fill_in_procs(dev); |
460 | 718k | { |
461 | 718k | int code = (*dev_proc(dev, open_device))(dev); |
462 | | |
463 | 718k | if (code < 0) |
464 | 0 | return_error(code); |
465 | 718k | dev->is_open = true; |
466 | 718k | return 1; |
467 | 718k | } |
468 | 718k | } |
469 | | |
470 | | static void |
471 | | gs_gstate_update_device(gs_gstate *pgs, gx_device *dev) |
472 | 1.91M | { |
473 | 1.91M | gx_set_cmap_procs(pgs, dev); |
474 | 1.91M | gx_unset_both_dev_colors(pgs); |
475 | 1.91M | } |
476 | | |
477 | | int |
478 | | gs_gstate_putdeviceparams(gs_gstate *pgs, gx_device *dev, gs_param_list *plist) |
479 | 0 | { |
480 | 0 | int code; |
481 | 0 | gx_device *dev2; |
482 | |
|
483 | 0 | if (dev) |
484 | 0 | dev2 = dev; |
485 | 0 | else |
486 | 0 | dev2 = pgs->device; |
487 | |
|
488 | 0 | code = gs_putdeviceparams(dev2, plist); |
489 | 0 | if (code >= 0) |
490 | 0 | gs_gstate_update_device(pgs, dev2); |
491 | 0 | return code; |
492 | 0 | } |
493 | | |
494 | | /* Set the device in the graphics state */ |
495 | | int |
496 | | gs_setdevice(gs_gstate * pgs, gx_device * dev) |
497 | 0 | { |
498 | 0 | int code = gs_setdevice_no_erase(pgs, dev); |
499 | |
|
500 | 0 | if (code == 1) |
501 | 0 | code = gs_erasepage(pgs); |
502 | 0 | return code; |
503 | 0 | } |
504 | | int |
505 | | gs_setdevice_no_erase(gs_gstate * pgs, gx_device * dev) |
506 | 1.69M | { |
507 | 1.69M | int open_code = 0, code; |
508 | 1.69M | gs_lib_ctx_t *libctx = gs_lib_ctx_get_interp_instance(pgs->memory); |
509 | | |
510 | | /* If the ICC manager is not yet initialized, set it up now. But only |
511 | | if we have file io capability now */ |
512 | 1.69M | if (libctx->io_device_table != NULL) { |
513 | 1.53M | cmm_dev_profile_t *dev_profile; |
514 | 1.53M | if (pgs->icc_manager->lab_profile == NULL) { /* pick one not set externally */ |
515 | 257k | code = gsicc_init_iccmanager(pgs); |
516 | 257k | if (code < 0) |
517 | 0 | return(code); |
518 | 257k | } |
519 | | /* Also, if the device profile is not yet set then take care of that |
520 | | before we start filling pages, if we can */ |
521 | | /* Although device methods should not be NULL, they are not completely filled in until |
522 | | * gx_device_fill_in_procs is called, and its possible for us to get here before this |
523 | | * happens, so we *must* make sure the method is not NULL before we use it. |
524 | | */ |
525 | 1.53M | if (dev->procs.get_profile != NULL) { |
526 | 1.27M | code = dev_proc(dev, get_profile)(dev, &dev_profile); |
527 | 1.27M | if (code < 0) { |
528 | 0 | return(code); |
529 | 0 | } |
530 | 1.27M | if (dev_profile == NULL || |
531 | 1.27M | dev_profile->device_profile[gsDEFAULTPROFILE] == NULL) { |
532 | 200 | if ((code = gsicc_init_device_profile_struct(dev, NULL, |
533 | 200 | gsDEFAULTPROFILE)) < 0) |
534 | 0 | return(code); |
535 | | /* set the intent too */ |
536 | 200 | if ((code = gsicc_set_device_profile_intent(dev, gsRINOTSPECIFIED, |
537 | 200 | gsDEFAULTPROFILE)) < 0) |
538 | 0 | return(code); |
539 | 200 | } |
540 | 1.27M | } |
541 | 1.53M | } |
542 | | |
543 | | /* Initialize the device */ |
544 | 1.69M | if (!dev->is_open) { |
545 | 717k | gx_device_fill_in_procs(dev); |
546 | | |
547 | | /* If we have not yet done so, and if we can, set the device profile |
548 | | * Doing so *before* the device is opened means that a device which |
549 | | * opens other devices can pass a profile on - for example, pswrite |
550 | | * also opens a bbox device |
551 | | */ |
552 | 717k | if (libctx->io_device_table != NULL) { |
553 | 554k | cmm_dev_profile_t *dev_profile; |
554 | | /* Although device methods should not be NULL, they are not completely filled in until |
555 | | * gx_device_fill_in_procs is called, and its possible for us to get here before this |
556 | | * happens, so we *must* make sure the method is not NULL before we use it. |
557 | | */ |
558 | 554k | if (dev->procs.get_profile != NULL) { |
559 | 554k | code = dev_proc(dev, get_profile)(dev, &dev_profile); |
560 | 554k | if (code < 0) { |
561 | 0 | return(code); |
562 | 0 | } |
563 | 554k | if (dev_profile == NULL || |
564 | 554k | dev_profile->device_profile[gsDEFAULTPROFILE] == NULL) { |
565 | 217k | if ((code = gsicc_init_device_profile_struct(dev, NULL, |
566 | 217k | gsDEFAULTPROFILE)) < 0) |
567 | 0 | return(code); |
568 | 217k | } |
569 | 554k | } |
570 | 554k | } |
571 | | |
572 | 717k | if (gs_device_is_memory(dev)) { |
573 | | /* Set the target to the current device. */ |
574 | 6 | gx_device *odev = gs_currentdevice_inline(pgs); |
575 | | |
576 | 12 | while (odev != 0 && gs_device_is_memory(odev)) |
577 | 6 | odev = ((gx_device_memory *)odev)->target; |
578 | 6 | gx_device_set_target(((gx_device_forward *)dev), odev); |
579 | 6 | } |
580 | 717k | code = open_code = gs_opendevice(dev); |
581 | 717k | if (code < 0) |
582 | 0 | return code; |
583 | 717k | } |
584 | 1.69M | gs_setdevice_no_init(pgs, dev); |
585 | 1.69M | pgs->ctm_default_set = false; |
586 | 1.69M | if ((code = gs_initmatrix(pgs)) < 0 || |
587 | 1.69M | (code = gs_initclip(pgs)) < 0 |
588 | 1.69M | ) |
589 | 0 | return code; |
590 | | /* If we were in a charpath or a setcachedevice, */ |
591 | | /* we aren't any longer. */ |
592 | 1.69M | pgs->in_cachedevice = 0; |
593 | 1.69M | pgs->in_charpath = (gs_char_path_mode) 0; |
594 | 1.69M | return open_code; |
595 | 1.69M | } |
596 | | int |
597 | | gs_setdevice_no_init(gs_gstate * pgs, gx_device * dev) |
598 | 1.91M | { |
599 | | /* |
600 | | * Just set the device, possibly changing color space but no other |
601 | | * device parameters. |
602 | | * |
603 | | * Make sure we don't close the device if dev == pgs->device |
604 | | * This could be done by allowing the rc_assign to close the |
605 | | * old 'dev' if the rc goes to 0 (via the device structure's |
606 | | * finalization procedure), but then the 'code' from the dev |
607 | | * closedevice would not be propagated up. We want to allow |
608 | | * the code to be handled, particularly for the pdfwrite |
609 | | * device. |
610 | | */ |
611 | 1.91M | if (pgs->device != NULL && pgs->device->rc.ref_count == 1 && |
612 | 1.91M | pgs->device != dev) { |
613 | 162k | int code = gs_closedevice(pgs->device); |
614 | | |
615 | 162k | if (code < 0) |
616 | 0 | return code; |
617 | 162k | } |
618 | 1.91M | rc_assign(pgs->device, dev, "gs_setdevice_no_init"); |
619 | 1.91M | gs_gstate_update_device(pgs, dev); |
620 | 1.91M | return 0; |
621 | 1.91M | } |
622 | | |
623 | | /* Initialize a just-allocated device. */ |
624 | | int |
625 | | gx_device_init(gx_device * dev, const gx_device * proto, gs_memory_t * mem, |
626 | | bool internal) |
627 | 178M | { |
628 | 178M | memcpy(dev, proto, proto->params_size); |
629 | 178M | dev->initialize_device_procs = proto->initialize_device_procs; |
630 | 178M | if (dev->initialize_device_procs != NULL) |
631 | 178M | dev->initialize_device_procs(dev); |
632 | 178M | dev->memory = mem; /* must precede initialize_device call so devices can use it */ |
633 | 178M | if (dev->procs.initialize_device) { |
634 | 1.86M | int code = dev->procs.initialize_device(dev); |
635 | 1.86M | if (code < 0) |
636 | 0 | return code; |
637 | 1.86M | } |
638 | 178M | dev->retained = !internal; |
639 | 178M | rc_init(dev, mem, (internal ? 0 : 1)); |
640 | 178M | rc_increment(dev->icc_struct); |
641 | | |
642 | 178M | return 0; |
643 | 178M | } |
644 | | |
645 | | void |
646 | | gx_device_init_on_stack(gx_device * dev, const gx_device * proto, |
647 | | gs_memory_t * mem) |
648 | 16.7M | { |
649 | 16.7M | memcpy(dev, proto, proto->params_size); |
650 | 16.7M | dev->initialize_device_procs = proto->initialize_device_procs; |
651 | 16.7M | dev->initialize_device_procs(dev); |
652 | 16.7M | if (dev->procs.initialize_device) { |
653 | | /* A condition of devices inited on the stack is that they can |
654 | | * never fail to initialize! */ |
655 | 0 | (void)dev->procs.initialize_device(dev); |
656 | 0 | } |
657 | 16.7M | gx_device_fill_in_procs(dev); |
658 | 16.7M | dev->memory = mem; |
659 | 16.7M | dev->retained = 0; |
660 | 16.7M | dev->pad = proto->pad; |
661 | 16.7M | dev->log2_align_mod = proto->log2_align_mod; |
662 | 16.7M | dev->num_planar_planes = proto->num_planar_planes; |
663 | 16.7M | rc_init(dev, NULL, 0); |
664 | 16.7M | } |
665 | | |
666 | | /* Make a null device. */ |
667 | | void |
668 | | gs_make_null_device(gx_device_null *dev_null, gx_device *dev, |
669 | | gs_memory_t * mem) |
670 | 198k | { |
671 | | /* Can never fail */ |
672 | 198k | (void)gx_device_init((gx_device *)dev_null, |
673 | 198k | (const gx_device *)&gs_null_device, |
674 | 198k | mem, true); |
675 | 198k | gx_device_fill_in_procs((gx_device *)dev_null); |
676 | 198k | gx_device_set_target((gx_device_forward *)dev_null, dev); |
677 | 198k | if (dev) { |
678 | | /* The gx_device_copy_color_params() call below should |
679 | | probably copy over these new-style color mapping procs, as |
680 | | well as the old-style (map_rgb_color and friends). However, |
681 | | the change was made here instead, to minimize the potential |
682 | | impact of the patch. |
683 | | */ |
684 | 198k | gx_device *dn = (gx_device *)dev_null; |
685 | 198k | set_dev_proc(dn, get_color_mapping_procs, gx_forward_get_color_mapping_procs); |
686 | 198k | set_dev_proc(dn, get_color_comp_index, gx_forward_get_color_comp_index); |
687 | 198k | set_dev_proc(dn, encode_color, gx_forward_encode_color); |
688 | 198k | set_dev_proc(dn, decode_color, gx_forward_decode_color); |
689 | 198k | set_dev_proc(dn, get_profile, gx_forward_get_profile); |
690 | 198k | set_dev_proc(dn, set_graphics_type_tag, gx_forward_set_graphics_type_tag); |
691 | 198k | set_dev_proc(dn, begin_transparency_group, gx_default_begin_transparency_group); |
692 | 198k | set_dev_proc(dn, end_transparency_group, gx_default_end_transparency_group); |
693 | 198k | set_dev_proc(dn, begin_transparency_mask, gx_default_begin_transparency_mask); |
694 | 198k | set_dev_proc(dn, end_transparency_mask, gx_default_end_transparency_mask); |
695 | 198k | set_dev_proc(dn, discard_transparency_layer, gx_default_discard_transparency_layer); |
696 | 198k | set_dev_proc(dn, push_transparency_state, gx_default_push_transparency_state); |
697 | 198k | set_dev_proc(dn, pop_transparency_state, gx_default_pop_transparency_state); |
698 | 198k | set_dev_proc(dn, put_image, gx_default_put_image); |
699 | 198k | set_dev_proc(dn, copy_planes, gx_default_copy_planes); |
700 | 198k | set_dev_proc(dn, copy_alpha_hl_color, gx_default_no_copy_alpha_hl_color); |
701 | 198k | dn->graphics_type_tag = dev->graphics_type_tag; /* initialize to same as target */ |
702 | 198k | gx_device_copy_color_params(dn, dev); |
703 | 198k | } |
704 | 198k | } |
705 | | |
706 | | /* Is a null device ? */ |
707 | | bool gs_is_null_device(gx_device *dev) |
708 | 16.8M | { |
709 | 16.8M | gx_device ldev; |
710 | | |
711 | 16.8M | ldev.initialize_device_procs = gs_null_device.initialize_device_procs; |
712 | 16.8M | ldev.initialize_device_procs(&ldev); |
713 | | |
714 | | /* Assuming null_fill_path isn't used elswhere. */ |
715 | 16.8M | return dev->procs.fill_path == ldev.procs.fill_path; |
716 | 16.8M | } |
717 | | |
718 | | /* Mark a device as retained or not retained. */ |
719 | | void |
720 | | gx_device_retain(gx_device *dev, bool retained) |
721 | 174M | { |
722 | 174M | int delta = (int)retained - (int)dev->retained; |
723 | | |
724 | 174M | if (delta) { |
725 | 174M | dev->retained = retained; /* do first in case dev is freed */ |
726 | 174M | rc_adjust_only(dev, delta, "gx_device_retain"); |
727 | 174M | } |
728 | 174M | } |
729 | | |
730 | | /* Select a null device. */ |
731 | | int |
732 | | gs_nulldevice(gs_gstate * pgs) |
733 | 514k | { |
734 | 514k | int code = 0; |
735 | 514k | gs_gstate *spgs; |
736 | 514k | bool saveLockSafety = false; |
737 | 514k | if (pgs->device == NULL || !gx_device_is_null(pgs->device)) { |
738 | 513k | gx_device *ndev; |
739 | 513k | code = gs_copydevice(&ndev, (const gx_device *)&gs_null_device, |
740 | 513k | pgs->memory); |
741 | | |
742 | 513k | if (code < 0) |
743 | 0 | return code; |
744 | 513k | if (gs_currentdevice_inline(pgs) != NULL) |
745 | 256k | saveLockSafety = gs_currentdevice_inline(pgs)->LockSafetyParams; |
746 | | /* |
747 | | * Internal devices have a reference count of 0, not 1, |
748 | | * aside from references from graphics states. |
749 | | */ |
750 | | /* There is some strange use of the null device in the code. I need |
751 | | to sort out how the icc profile is best handled with this device. |
752 | | It seems to inherit properties from the current device if there |
753 | | is one */ |
754 | 513k | rc_init(ndev, pgs->memory, 0); |
755 | 513k | if (pgs->device != NULL) { |
756 | 256k | if ((code = dev_proc(pgs->device, get_profile)(pgs->device, |
757 | 256k | &(ndev->icc_struct))) < 0) |
758 | 0 | return code; |
759 | 256k | rc_increment(ndev->icc_struct); |
760 | 256k | set_dev_proc(ndev, get_profile, gx_default_get_profile); |
761 | 256k | } |
762 | | |
763 | 513k | if (gs_setdevice_no_erase(pgs, ndev) < 0) { |
764 | 0 | gs_free_object(pgs->memory, ndev, "gs_copydevice(device)"); |
765 | | /* We are out of options: find the device we installed in |
766 | | the initial graphics state, and put that in place. |
767 | | We just need something so we can end this job cleanly. |
768 | | */ |
769 | 0 | spgs = pgs->saved; |
770 | 0 | if (spgs != NULL) { |
771 | 0 | while (spgs->saved) spgs = spgs->saved; |
772 | 0 | gs_currentdevice_inline(pgs) = gs_currentdevice_inline(spgs); |
773 | 0 | rc_increment(gs_currentdevice_inline(pgs)); |
774 | 0 | } |
775 | 0 | code = gs_note_error(gs_error_Fatal); |
776 | 0 | } |
777 | 513k | if (gs_currentdevice_inline(pgs) != NULL) |
778 | 513k | gs_currentdevice_inline(pgs)->LockSafetyParams = saveLockSafety; |
779 | 513k | } |
780 | 514k | return code; |
781 | 514k | } |
782 | | |
783 | | /* Close a device. The client is responsible for ensuring that */ |
784 | | /* this device is not current in any graphics state. */ |
785 | | int |
786 | | gs_closedevice(gx_device * dev) |
787 | 170M | { |
788 | 170M | int code = 0; |
789 | | |
790 | 170M | if (dev->is_open) { |
791 | 1.20M | code = (*dev_proc(dev, close_device))(dev); |
792 | 1.20M | dev->is_open = false; |
793 | 1.20M | if (code < 0) |
794 | 0 | return_error(code); |
795 | 1.20M | } |
796 | 170M | return code; |
797 | 170M | } |
798 | | |
799 | | /* |
800 | | * Just set the device without any reinitializing. |
801 | | * (For internal use only.) |
802 | | */ |
803 | | void |
804 | | gx_set_device_only(gs_gstate * pgs, gx_device * dev) |
805 | 6.18M | { |
806 | 6.18M | rc_assign(pgs->device, dev, "gx_set_device_only"); |
807 | 6.18M | } |
808 | | |
809 | | /* Compute the size of one scan line for a device. */ |
810 | | /* If pad = 0 return the line width in bytes. If pad = 1, |
811 | | * return the actual raster value (the number of bytes to offset from |
812 | | * a byte on one scanline to the same byte on the scanline below.) */ |
813 | | uint |
814 | | gx_device_raster(const gx_device * dev, bool pad) |
815 | 356M | { |
816 | 356M | int depth = dev->color_info.depth; |
817 | 356M | ulong bits = (ulong) dev->width * depth; |
818 | 356M | ulong raster; |
819 | 356M | int l2align; |
820 | | |
821 | 356M | if (dev->num_planar_planes) { |
822 | 7.40M | int num_components = dev->num_planar_planes; |
823 | | /* bpc accounts for unused bits, e.g. depth==4, num_comp==3, or depth==8, num_comps==5 */ |
824 | 7.40M | int bpc = depth / num_components; |
825 | | |
826 | | /* depth can be <= num_components if planar and MEM_SET_PARAMS has changed it */ |
827 | 7.40M | if (depth <= num_components || bpc >= 8) { |
828 | 6.56M | bits /= num_components; |
829 | 6.56M | } else { |
830 | | /* depth is original depth, not the plane_depth since it is > num_components */ |
831 | 838k | bits /= (depth / bpc); |
832 | 838k | } |
833 | 7.40M | } |
834 | 356M | raster = (uint)((bits + 7) >> 3); |
835 | 356M | if (!pad) |
836 | 164M | return raster; |
837 | 192M | l2align = dev->log2_align_mod; |
838 | 192M | if (l2align < log2_align_bitmap_mod) |
839 | 192M | l2align = log2_align_bitmap_mod; |
840 | 192M | return (uint)(((bits + (8 << l2align) - 1) >> (l2align + 3)) << l2align); |
841 | 356M | } |
842 | | |
843 | | uint |
844 | | gx_device_raster_chunky(const gx_device * dev, bool pad) |
845 | 8.47k | { |
846 | 8.47k | ulong bits = (ulong) dev->width * dev->color_info.depth; |
847 | 8.47k | ulong raster; |
848 | 8.47k | int l2align; |
849 | | |
850 | 8.47k | raster = (uint)((bits + 7) >> 3); |
851 | 8.47k | if (!pad) |
852 | 8.47k | return raster; |
853 | 0 | l2align = dev->log2_align_mod; |
854 | 0 | if (l2align < log2_align_bitmap_mod) |
855 | 0 | l2align = log2_align_bitmap_mod; |
856 | 0 | return (uint)(((bits + (8 << l2align) - 1) >> (l2align + 3)) << l2align); |
857 | 8.47k | } |
858 | | uint |
859 | | gx_device_raster_plane(const gx_device * dev, const gx_render_plane_t *render_plane) |
860 | 160M | { |
861 | 160M | ulong bpc = (render_plane && render_plane->index >= 0 ? |
862 | 160M | render_plane->depth : dev->color_info.depth/(dev->num_planar_planes ? dev->num_planar_planes : 1)); |
863 | 160M | ulong bits = (ulong) dev->width * bpc; |
864 | 160M | int l2align; |
865 | | |
866 | 160M | l2align = dev->log2_align_mod; |
867 | 160M | if (l2align < log2_align_bitmap_mod) |
868 | 160M | l2align = log2_align_bitmap_mod; |
869 | 160M | return (uint)(((bits + (8 << l2align) - 1) >> (l2align + 3)) << l2align); |
870 | 160M | } |
871 | | |
872 | | /* Adjust the resolution for devices that only have a fixed set of */ |
873 | | /* geometries, so that the apparent size in inches remains constant. */ |
874 | | /* If fit=1, the resolution is adjusted so that the entire image fits; */ |
875 | | /* if fit=0, one dimension fits, but the other one is clipped. */ |
876 | | int |
877 | | gx_device_adjust_resolution(gx_device * dev, |
878 | | int actual_width, int actual_height, int fit) |
879 | 0 | { |
880 | 0 | double width_ratio = (double)actual_width / dev->width; |
881 | 0 | double height_ratio = (double)actual_height / dev->height; |
882 | 0 | double ratio = |
883 | 0 | (fit ? min(width_ratio, height_ratio) : |
884 | 0 | max(width_ratio, height_ratio)); |
885 | |
|
886 | 0 | dev->HWResolution[0] *= ratio; |
887 | 0 | dev->HWResolution[1] *= ratio; |
888 | 0 | gx_device_set_width_height(dev, actual_width, actual_height); |
889 | 0 | return 0; |
890 | 0 | } |
891 | | |
892 | | /* Set the HWMargins to values defined in inches. */ |
893 | | /* If move_origin is true, also reset the Margins. */ |
894 | | /* Note that this assumes a printer-type device (Y axis inverted). */ |
895 | | void |
896 | | gx_device_set_margins(gx_device * dev, const float *margins /*[4] */ , |
897 | | bool move_origin) |
898 | 250k | { |
899 | 250k | int i; |
900 | | |
901 | 1.25M | for (i = 0; i < 4; ++i) |
902 | 1.00M | dev->HWMargins[i] = margins[i] * 72.0; |
903 | 250k | if (move_origin) { |
904 | 0 | dev->Margins[0] = -margins[0] * dev->HWResolution[0]; |
905 | 0 | dev->Margins[1] = -margins[3] * dev->HWResolution[1]; |
906 | 0 | } |
907 | 250k | } |
908 | | |
909 | | static void |
910 | | gx_device_set_hwsize_from_media(gx_device *dev) |
911 | 587k | { |
912 | 587k | int rot = (dev->LeadingEdge & 1); |
913 | 587k | double rot_media_x = rot ? dev->MediaSize[1] : dev->MediaSize[0]; |
914 | 587k | double rot_media_y = rot ? dev->MediaSize[0] : dev->MediaSize[1]; |
915 | 587k | gx_device *parent = dev; |
916 | 587k | int hwsize[2]; |
917 | | |
918 | | /* Try the spec_op to give the device to control it */ |
919 | 587k | hwsize[0] = (int)(rot_media_x * dev->HWResolution[0] / 72.0 + 0.5); |
920 | 587k | hwsize[1] = (int)(rot_media_y * dev->HWResolution[1] / 72.0 + 0.5); |
921 | | |
922 | 617k | while (parent->parent != NULL) { |
923 | 30.0k | parent = parent->parent; |
924 | 30.0k | } |
925 | 587k | if (dev_proc(parent, dev_spec_op)(parent, gxdso_set_HWSize, &hwsize, sizeof(hwsize)) <= 0) { |
926 | | /* just do the default setting */ |
927 | 587k | dev->width = hwsize[0]; |
928 | 587k | dev->height = hwsize[1]; |
929 | 587k | } |
930 | 587k | } |
931 | | |
932 | | static void |
933 | | gx_device_set_media_from_hwsize(gx_device *dev) |
934 | 73.4k | { |
935 | 73.4k | int rot = (dev->LeadingEdge & 1); |
936 | 73.4k | double x = dev->width * 72.0 / dev->HWResolution[0]; |
937 | 73.4k | double y = dev->height * 72.0 / dev->HWResolution[1]; |
938 | | |
939 | 73.4k | if (rot) { |
940 | 0 | dev->MediaSize[1] = x; |
941 | 0 | dev->MediaSize[0] = y; |
942 | 73.4k | } else { |
943 | 73.4k | dev->MediaSize[0] = x; |
944 | 73.4k | dev->MediaSize[1] = y; |
945 | 73.4k | } |
946 | 73.4k | } |
947 | | |
948 | | /* Set the width and height, updating MediaSize to remain consistent. */ |
949 | | void |
950 | | gx_device_set_width_height(gx_device * dev, int width, int height) |
951 | 73.4k | { |
952 | 73.4k | dev->width = width; |
953 | 73.4k | dev->height = height; |
954 | 73.4k | gx_device_set_media_from_hwsize(dev); |
955 | 73.4k | } |
956 | | |
957 | | /* Set the resolution, updating width and height to remain consistent. */ |
958 | | void |
959 | | gx_device_set_resolution(gx_device * dev, double x_dpi, double y_dpi) |
960 | 186k | { |
961 | 186k | dev->HWResolution[0] = x_dpi; |
962 | 186k | dev->HWResolution[1] = y_dpi; |
963 | 186k | gx_device_set_hwsize_from_media(dev); |
964 | 186k | } |
965 | | |
966 | | /* Set the MediaSize, updating width and height to remain consistent. */ |
967 | | void |
968 | | gx_device_set_media_size(gx_device * dev, double media_width, double media_height) |
969 | 401k | { |
970 | 401k | dev->MediaSize[0] = media_width; |
971 | 401k | dev->MediaSize[1] = media_height; |
972 | 401k | gx_device_set_hwsize_from_media(dev); |
973 | 401k | } |
974 | | |
975 | | /* |
976 | | * Copy the color mapping procedures from the target if they are |
977 | | * standard ones (saving a level of procedure call at mapping time). |
978 | | */ |
979 | | void |
980 | | gx_device_copy_color_procs(gx_device *dev, const gx_device *target) |
981 | 172M | { |
982 | 172M | dev_proc_map_cmyk_color((*from_cmyk)) = |
983 | 172M | dev_proc(dev, map_cmyk_color); |
984 | 172M | dev_proc_map_rgb_color((*from_rgb)) = |
985 | 172M | dev_proc(dev, map_rgb_color); |
986 | 172M | dev_proc_map_color_rgb((*to_rgb)) = |
987 | 172M | dev_proc(dev, map_color_rgb); |
988 | | |
989 | | /* The logic in this function seems a bit stale; it sets the |
990 | | old-style color procs, but not the new ones |
991 | | (get_color_mapping_procs, get_color_comp_index, encode_color, |
992 | | and decode_color). It should probably copy those as well. |
993 | | */ |
994 | 172M | if (from_cmyk == gx_forward_map_cmyk_color || |
995 | 172M | from_cmyk == cmyk_1bit_map_cmyk_color || |
996 | 172M | from_cmyk == cmyk_8bit_map_cmyk_color) { |
997 | 172M | from_cmyk = dev_proc(target, map_cmyk_color); |
998 | 172M | set_dev_proc(dev, map_cmyk_color, |
999 | 172M | (from_cmyk == cmyk_1bit_map_cmyk_color || |
1000 | 172M | from_cmyk == cmyk_8bit_map_cmyk_color ? |
1001 | 172M | from_cmyk : gx_forward_map_cmyk_color)); |
1002 | 172M | } |
1003 | 172M | if (from_rgb == gx_forward_map_rgb_color || |
1004 | 172M | from_rgb == gx_default_rgb_map_rgb_color) { |
1005 | 172M | from_rgb = dev_proc(target, map_rgb_color); |
1006 | 172M | set_dev_proc(dev, map_rgb_color, |
1007 | 172M | (from_rgb == gx_default_rgb_map_rgb_color ? |
1008 | 172M | from_rgb : gx_forward_map_rgb_color)); |
1009 | 172M | } |
1010 | 172M | if (to_rgb == gx_forward_map_color_rgb || |
1011 | 172M | to_rgb == cmyk_1bit_map_color_rgb || |
1012 | 172M | to_rgb == cmyk_8bit_map_color_rgb) { |
1013 | 172M | to_rgb = dev_proc(target, map_color_rgb); |
1014 | 172M | set_dev_proc(dev, map_color_rgb, |
1015 | 172M | (to_rgb == cmyk_1bit_map_color_rgb || |
1016 | 172M | to_rgb == cmyk_8bit_map_color_rgb ? |
1017 | 172M | to_rgb : gx_forward_map_color_rgb)); |
1018 | 172M | } |
1019 | 172M | } |
1020 | | |
1021 | 640k | #define COPY_PARAM(p) dev->p = target->p |
1022 | | |
1023 | | /* |
1024 | | * Copy the color-related device parameters back from the target: |
1025 | | * color_info and color mapping procedures. |
1026 | | */ |
1027 | | void |
1028 | | gx_device_copy_color_params(gx_device *dev, const gx_device *target) |
1029 | 319k | { |
1030 | 319k | COPY_PARAM(color_info); |
1031 | 319k | COPY_PARAM(cached_colors); |
1032 | 319k | gx_device_copy_color_procs(dev, target); |
1033 | 319k | } |
1034 | | |
1035 | | /* |
1036 | | * Copy device parameters back from a target. This copies all standard |
1037 | | * parameters related to page size and resolution, plus color_info |
1038 | | * and (if appropriate) color mapping procedures. |
1039 | | */ |
1040 | | void |
1041 | | gx_device_copy_params(gx_device *dev, const gx_device *target) |
1042 | 247 | { |
1043 | 1.23k | #define COPY_ARRAY_PARAM(p) memcpy(dev->p, target->p, sizeof(dev->p)) |
1044 | 247 | COPY_PARAM(width); |
1045 | 247 | COPY_PARAM(height); |
1046 | 247 | COPY_ARRAY_PARAM(MediaSize); |
1047 | 247 | COPY_ARRAY_PARAM(ImagingBBox); |
1048 | 247 | COPY_PARAM(ImagingBBox_set); |
1049 | 247 | COPY_ARRAY_PARAM(HWResolution); |
1050 | 247 | COPY_ARRAY_PARAM(Margins); |
1051 | 247 | COPY_ARRAY_PARAM(HWMargins); |
1052 | 247 | COPY_PARAM(PageCount); |
1053 | 247 | COPY_PARAM(MaxPatternBitmap); |
1054 | 247 | #undef COPY_ARRAY_PARAM |
1055 | 247 | gx_device_copy_color_params(dev, target); |
1056 | 247 | } |
1057 | | |
1058 | | #undef COPY_PARAM |
1059 | | |
1060 | | /* |
1061 | | * Parse the output file name detecting and validating any %nnd format |
1062 | | * for inserting the page count. If a format is present, store a pointer |
1063 | | * to its last character in *pfmt, otherwise store 0 there. |
1064 | | * Note that we assume devices have already been scanned, and any % must |
1065 | | * precede a valid format character. |
1066 | | * |
1067 | | * If there was a format, then return the max_width |
1068 | | */ |
1069 | | static int |
1070 | | gx_parse_output_format(gs_parsed_file_name_t *pfn, const char **pfmt) |
1071 | 581k | { |
1072 | 581k | bool have_format = false, field; |
1073 | 581k | uint width[2], int_width = sizeof(int) * 3, w = 0; |
1074 | 581k | uint i; |
1075 | | |
1076 | | /* Scan the file name for a format string, and validate it if present. */ |
1077 | 581k | width[0] = width[1] = 0; |
1078 | 7.23M | for (i = 0; i < pfn->len; ++i) |
1079 | 6.65M | if (pfn->fname[i] == '%') { |
1080 | 240 | if (i + 1 < pfn->len && pfn->fname[i + 1] == '%') { |
1081 | 240 | i++; |
1082 | 240 | continue; |
1083 | 240 | } |
1084 | 0 | if (have_format) /* more than one % */ |
1085 | 0 | return_error(gs_error_undefinedfilename); |
1086 | 0 | have_format = true; |
1087 | 0 | field = -1; /* -1..3 for the 5 components of "%[flags][width][.precision][l]type" */ |
1088 | 0 | for (;;) |
1089 | 0 | if (++i == pfn->len) |
1090 | 0 | return_error(gs_error_undefinedfilename); |
1091 | 0 | else { |
1092 | 0 | switch (field) { |
1093 | 0 | case -1: /* flags */ |
1094 | 0 | if (strchr(" #+-", pfn->fname[i])) |
1095 | 0 | continue; |
1096 | 0 | else |
1097 | 0 | field++; |
1098 | | /* falls through */ |
1099 | 0 | default: /* width (field = 0) and precision (field = 1) */ |
1100 | 0 | if (strchr("0123456789", pfn->fname[i])) { |
1101 | 0 | width[field] = width[field] * 10 + pfn->fname[i] - '0'; |
1102 | 0 | if (width[field] > max_int) |
1103 | 0 | return_error(gs_error_undefinedfilename); |
1104 | 0 | continue; |
1105 | 0 | } else if (0 == field && '.' == pfn->fname[i]) { |
1106 | 0 | field++; |
1107 | 0 | continue; |
1108 | 0 | } else |
1109 | 0 | field = 2; |
1110 | | /* falls through */ |
1111 | 0 | case 2: /* "long" indicator */ |
1112 | 0 | field++; |
1113 | 0 | if ('l' == pfn->fname[i]) { |
1114 | 0 | int_width = sizeof(long) * 3; |
1115 | 0 | continue; |
1116 | 0 | } |
1117 | | /* falls through */ |
1118 | 0 | case 3: /* type */ |
1119 | 0 | if (strchr("diuoxX", pfn->fname[i])) { |
1120 | 0 | *pfmt = &pfn->fname[i]; |
1121 | 0 | break; |
1122 | 0 | } else |
1123 | 0 | return_error(gs_error_undefinedfilename); |
1124 | 0 | } |
1125 | 0 | break; |
1126 | 0 | } |
1127 | 0 | } |
1128 | 581k | if (have_format) { |
1129 | | /* Calculate a conservative maximum width. */ |
1130 | 0 | w = max(width[0], width[1]); |
1131 | 0 | w = max(w, int_width) + 5; |
1132 | 0 | if (w > max_int) |
1133 | 0 | return_error(gs_error_undefinedfilename); |
1134 | 0 | } |
1135 | 581k | return (int)w; |
1136 | 581k | } |
1137 | | |
1138 | | /* |
1139 | | * Parse the output file name for a device, recognizing "-" and "|command", |
1140 | | * and also detecting and validating any %nnd format for inserting the |
1141 | | * page count. If a format is present, store a pointer to its last |
1142 | | * character in *pfmt, otherwise store 0 there. Note that an empty name |
1143 | | * is currently allowed. |
1144 | | */ |
1145 | | int |
1146 | | gx_parse_output_file_name(gs_parsed_file_name_t *pfn, const char **pfmt, |
1147 | | const char *fname, uint fnlen, gs_memory_t *memory) |
1148 | 581k | { |
1149 | 581k | int code; |
1150 | | |
1151 | 581k | *pfmt = 0; |
1152 | 581k | pfn->memory = 0; |
1153 | 581k | pfn->iodev = NULL; |
1154 | 581k | pfn->fname = NULL; /* irrelevant since length = 0 */ |
1155 | 581k | pfn->len = 0; |
1156 | 581k | if (fnlen == 0) /* allow null name */ |
1157 | 0 | return 0; |
1158 | | /* |
1159 | | * If the file name begins with a %, it might be either an IODevice |
1160 | | * or a %nnd format. Check (carefully) for this case. |
1161 | | */ |
1162 | 581k | code = gs_parse_file_name(pfn, fname, fnlen, memory); |
1163 | 581k | if (code < 0) { |
1164 | 0 | if (fname[0] == '%') { |
1165 | | /* not a recognized iodev -- may be a leading format descriptor */ |
1166 | 0 | pfn->len = fnlen; |
1167 | 0 | pfn->fname = fname; |
1168 | 0 | code = gx_parse_output_format(pfn, pfmt); |
1169 | 0 | } |
1170 | 0 | if (code < 0) |
1171 | 0 | return code; |
1172 | 0 | } |
1173 | 581k | if (!pfn->iodev) { |
1174 | 581k | if ( (pfn->len == 1) && (pfn->fname[0] == '-') ) { |
1175 | 0 | pfn->iodev = gs_findiodevice(memory, (const byte *)"%stdout", 7); |
1176 | 0 | pfn->fname = NULL; |
1177 | 581k | } else if (pfn->fname[0] == '|') { |
1178 | 0 | pfn->iodev = gs_findiodevice(memory, (const byte *)"%pipe", 5); |
1179 | 0 | pfn->fname++, pfn->len--; |
1180 | 0 | } else |
1181 | 581k | pfn->iodev = iodev_default(memory); |
1182 | 581k | if (!pfn->iodev) |
1183 | 0 | return_error(gs_error_undefinedfilename); |
1184 | 581k | } |
1185 | 581k | if (!pfn->fname) |
1186 | 0 | return 0; |
1187 | 581k | code = gx_parse_output_format(pfn, pfmt); |
1188 | 581k | if (code < 0) { |
1189 | 0 | return code; |
1190 | 0 | } |
1191 | | |
1192 | 581k | if (pfn->len >= gp_file_name_sizeof - strlen(pfn->iodev->dname) || |
1193 | 581k | code >= gp_file_name_sizeof - strlen(pfn->iodev->dname) - pfn->len) { |
1194 | 0 | return_error(gs_error_undefinedfilename); |
1195 | 0 | } |
1196 | | |
1197 | 581k | return 0; |
1198 | 581k | } |
1199 | | |
1200 | | /* Check if we write each page into separate file. */ |
1201 | | bool |
1202 | | gx_outputfile_is_separate_pages(const char *fname, gs_memory_t *memory) |
1203 | 78.6k | { |
1204 | 78.6k | const char *fmt; |
1205 | 78.6k | gs_parsed_file_name_t parsed; |
1206 | 78.6k | int code = gx_parse_output_file_name(&parsed, &fmt, fname, |
1207 | 78.6k | strlen(fname), memory); |
1208 | | |
1209 | 78.6k | return (code >= 0 && fmt != 0); |
1210 | 78.6k | } |
1211 | | |
1212 | | /* Delete the current output file for a device (file must be closed first) */ |
1213 | | int gx_device_delete_output_file(const gx_device * dev, const char *fname) |
1214 | 0 | { |
1215 | 0 | gs_parsed_file_name_t parsed; |
1216 | 0 | const char *fmt; |
1217 | 0 | char *pfname = (char *)gs_alloc_bytes(dev->memory, gp_file_name_sizeof, "gx_device_delete_output_file(pfname)"); |
1218 | 0 | int code; |
1219 | 0 | size_t len; |
1220 | |
|
1221 | 0 | if (pfname == NULL) { |
1222 | 0 | code = gs_note_error(gs_error_VMerror); |
1223 | 0 | goto done; |
1224 | 0 | } |
1225 | | |
1226 | 0 | len = strlen(fname); |
1227 | 0 | code = gx_parse_output_file_name(&parsed, &fmt, fname, len, |
1228 | 0 | dev->memory); |
1229 | 0 | if (code < 0) { |
1230 | 0 | goto done; |
1231 | 0 | } |
1232 | | |
1233 | 0 | if (parsed.iodev && !strcmp(parsed.iodev->dname, "%stdout%")) |
1234 | 0 | goto done; |
1235 | | |
1236 | 0 | if (fmt) { /* filename includes "%nnd" */ |
1237 | 0 | long count1 = dev->PageCount + 1; |
1238 | |
|
1239 | 0 | while (*fmt != 'l' && *fmt != '%') |
1240 | 0 | --fmt; |
1241 | 0 | if (*fmt == 'l') |
1242 | 0 | gs_snprintf(pfname, gp_file_name_sizeof, parsed.fname, count1); |
1243 | 0 | else |
1244 | 0 | gs_snprintf(pfname, gp_file_name_sizeof, parsed.fname, (int)count1); |
1245 | 0 | } else if (parsed.len && strchr(parsed.fname, '%')) /* filename with "%%" but no "%nnd" */ |
1246 | 0 | gs_snprintf(pfname, gp_file_name_sizeof, parsed.fname); |
1247 | 0 | else |
1248 | 0 | pfname[0] = 0; /* 0 to use "fname", not "pfname" */ |
1249 | 0 | if (pfname[0]) { |
1250 | 0 | parsed.fname = pfname; |
1251 | 0 | parsed.len = strlen(parsed.fname); |
1252 | 0 | } |
1253 | 0 | if (parsed.iodev) |
1254 | 0 | code = parsed.iodev->procs.delete_file((gx_io_device *)(parsed.iodev), (const char *)parsed.fname); |
1255 | 0 | else |
1256 | 0 | code = gs_note_error(gs_error_invalidfileaccess); |
1257 | |
|
1258 | 0 | done: |
1259 | 0 | if (pfname != NULL) |
1260 | 0 | gs_free_object(dev->memory, pfname, "gx_device_delete_output_file(pfname)"); |
1261 | |
|
1262 | 0 | return(code); |
1263 | 0 | } |
1264 | | |
1265 | | static int |
1266 | | noclose(FILE *f) |
1267 | 0 | { |
1268 | 0 | return 0; |
1269 | 0 | } |
1270 | | |
1271 | | /* Open the output file for a device. */ |
1272 | | int |
1273 | | gx_device_open_output_file(const gx_device * dev, char *fname, |
1274 | | bool binary, bool positionable, gp_file ** pfile) |
1275 | 127k | { |
1276 | 127k | gs_parsed_file_name_t parsed; |
1277 | 127k | const char *fmt; |
1278 | 127k | char *pfname = (char *)gs_alloc_bytes(dev->memory, gp_file_name_sizeof, "gx_device_open_output_file(pfname)"); |
1279 | 127k | int code; |
1280 | | |
1281 | 127k | if (pfname == NULL) { |
1282 | 0 | code = gs_note_error(gs_error_VMerror); |
1283 | 0 | goto done; |
1284 | 0 | } |
1285 | | |
1286 | 127k | if (strlen(fname) == 0) { |
1287 | 0 | code = gs_note_error(gs_error_undefinedfilename); |
1288 | 0 | emprintf1(dev->memory, "Device '%s' requires an output file but no file was specified.\n", dev->dname); |
1289 | 0 | goto done; |
1290 | 0 | } |
1291 | 127k | code = gx_parse_output_file_name(&parsed, &fmt, fname, strlen(fname), dev->memory); |
1292 | 127k | if (code < 0) { |
1293 | 0 | goto done; |
1294 | 0 | } |
1295 | | |
1296 | 127k | if (parsed.iodev && !strcmp(parsed.iodev->dname, "%stdout%")) { |
1297 | 0 | if (parsed.fname) { |
1298 | 0 | code = gs_note_error(gs_error_undefinedfilename); |
1299 | 0 | goto done; |
1300 | 0 | } |
1301 | 0 | *pfile = gp_file_FILE_alloc(dev->memory); |
1302 | 0 | if (*pfile == NULL) { |
1303 | 0 | code = gs_note_error(gs_error_VMerror); |
1304 | 0 | goto done; |
1305 | 0 | } |
1306 | 0 | gp_file_FILE_set(*pfile, dev->memory->gs_lib_ctx->core->fstdout, noclose); |
1307 | | /* Force stdout to binary. */ |
1308 | 0 | code = gp_setmode_binary_impl(dev->memory->gs_lib_ctx->core->fstdout, true); |
1309 | 0 | goto done; |
1310 | 127k | } else if (parsed.iodev && !strcmp(parsed.iodev->dname, "%pipe%")) { |
1311 | 0 | positionable = false; |
1312 | 0 | } |
1313 | 127k | if (fmt) { /* filename includes "%nnd" */ |
1314 | 0 | long count1 = dev->PageCount + 1; |
1315 | |
|
1316 | 0 | while (*fmt != 'l' && *fmt != '%') |
1317 | 0 | --fmt; |
1318 | 0 | if (*fmt == 'l') |
1319 | 0 | gs_snprintf(pfname, gp_file_name_sizeof, parsed.fname, count1); |
1320 | 0 | else |
1321 | 0 | gs_snprintf(pfname, gp_file_name_sizeof, parsed.fname, (int)count1); |
1322 | 127k | } else if (parsed.len && strchr(parsed.fname, '%')) /* filename with "%%" but no "%nnd" */ |
1323 | 3 | gs_snprintf(pfname, gp_file_name_sizeof, parsed.fname); |
1324 | 127k | else |
1325 | 127k | pfname[0] = 0; /* 0 to use "fname", not "pfname" */ |
1326 | 127k | if (pfname[0]) { |
1327 | 3 | parsed.fname = pfname; |
1328 | 3 | parsed.len = strlen(parsed.fname); |
1329 | 3 | } |
1330 | 127k | if (parsed.iodev && |
1331 | 127k | (positionable || parsed.iodev != iodev_default(dev->memory))) { |
1332 | 54.9k | char fmode[4]; |
1333 | | |
1334 | 54.9k | if (!parsed.fname) { |
1335 | 0 | code = gs_note_error(gs_error_undefinedfilename); |
1336 | 0 | goto done; |
1337 | 0 | } |
1338 | 54.9k | strcpy(fmode, gp_fmode_wb); |
1339 | 54.9k | if (positionable) |
1340 | 54.9k | strcat(fmode, "+"); |
1341 | 54.9k | code = parsed.iodev->procs.gp_fopen(parsed.iodev, parsed.fname, fmode, |
1342 | 54.9k | pfile, NULL, 0, dev->memory); |
1343 | 54.9k | if (code) |
1344 | 2 | emprintf1(dev->memory, |
1345 | 54.9k | "**** Could not open the file %s .\n", |
1346 | 54.9k | parsed.fname); |
1347 | 72.2k | } else { |
1348 | 72.2k | *pfile = gp_open_printer(dev->memory, (pfname[0] ? pfname : fname), binary); |
1349 | 72.2k | if (!(*pfile)) { |
1350 | 0 | emprintf1(dev->memory, "**** Could not open the file '%s'.\n", (pfname[0] ? pfname : fname)); |
1351 | |
|
1352 | 0 | code = gs_note_error(gs_error_invalidfileaccess); |
1353 | 0 | } |
1354 | 72.2k | } |
1355 | | |
1356 | 127k | done: |
1357 | 127k | if (pfname != NULL) |
1358 | 127k | gs_free_object(dev->memory, pfname, "gx_device_open_output_file(pfname)"); |
1359 | | |
1360 | 127k | return(code); |
1361 | 127k | } |
1362 | | |
1363 | | /* Close the output file for a device. */ |
1364 | | int |
1365 | | gx_device_close_output_file(const gx_device * dev, const char *fname, |
1366 | | gp_file *file) |
1367 | 127k | { |
1368 | 127k | gs_parsed_file_name_t parsed; |
1369 | 127k | const char *fmt; |
1370 | 127k | int code = gx_parse_output_file_name(&parsed, &fmt, fname, strlen(fname), |
1371 | 127k | dev->memory); |
1372 | | |
1373 | 127k | if (code < 0) |
1374 | 0 | return code; |
1375 | 127k | if (parsed.iodev) { |
1376 | 127k | if (!strcmp(parsed.iodev->dname, "%stdout%")) |
1377 | 0 | return 0; |
1378 | | /* NOTE: fname is unsubstituted if the name has any %nnd formats. */ |
1379 | 127k | if (parsed.iodev != iodev_default(dev->memory)) |
1380 | 0 | return parsed.iodev->procs.fclose(parsed.iodev, file); |
1381 | 127k | } |
1382 | 127k | gp_close_printer(file, (parsed.fname ? parsed.fname : fname)); |
1383 | 127k | return 0; |
1384 | 127k | } |
1385 | | |
1386 | | bool gx_color_info_equal(const gx_device_color_info * p1, const gx_device_color_info * p2) |
1387 | 136k | { |
1388 | 136k | if (p1->anti_alias.graphics_bits != p2->anti_alias.graphics_bits) |
1389 | 0 | return false; |
1390 | 136k | if (p1->anti_alias.text_bits != p2->anti_alias.text_bits) |
1391 | 0 | return false; |
1392 | 136k | if (p1->black_component != p2->black_component) |
1393 | 0 | return false; |
1394 | 136k | if (strcmp(p1->cm_name, p2->cm_name) != 0) |
1395 | 0 | return false; |
1396 | 136k | if (p1->depth != p2->depth) |
1397 | 23.7k | return false; |
1398 | 113k | if (p1->dither_colors != p2->dither_colors) |
1399 | 0 | return false; |
1400 | 113k | if (p1->dither_grays != p2->dither_grays) |
1401 | 0 | return false; |
1402 | 113k | if (p1->gray_index != p2->gray_index) |
1403 | 0 | return false; |
1404 | 113k | if (p1->max_color != p2->max_color) |
1405 | 0 | return false; |
1406 | 113k | if (p1->max_components != p2->max_components) |
1407 | 0 | return false; |
1408 | 113k | if (p1->opmsupported != p2->opmsupported) |
1409 | 0 | return false; |
1410 | 113k | if (p1->polarity != p2->polarity) |
1411 | 0 | return false; |
1412 | 113k | if (p1->process_comps != p2->process_comps) |
1413 | 0 | return false; |
1414 | 113k | if (p1->separable_and_linear != p2->separable_and_linear) |
1415 | 0 | return false; |
1416 | 113k | if (p1->use_antidropout_downscaler != p2->use_antidropout_downscaler) |
1417 | 0 | return false; |
1418 | 113k | return true; |
1419 | 113k | } |
1420 | | |
1421 | | int gx_callout(gx_device *dev, int id, int size, void *data) |
1422 | 0 | { |
1423 | 0 | return gs_lib_ctx_callout(dev->memory, dev->dname, |
1424 | 0 | id, size, data); |
1425 | 0 | } |
1426 | | |
1427 | | /* compare two space_params, we can't do this with memcmp since there is padding in the structure */ |
1428 | | int |
1429 | | gdev_space_params_cmp(const gdev_space_params sp1, |
1430 | 815k | const gdev_space_params sp2) { |
1431 | 815k | if (sp1.MaxBitmap != sp2.MaxBitmap) |
1432 | 0 | return(1); |
1433 | 815k | if (sp1.BufferSpace != sp2.BufferSpace) |
1434 | 0 | return(1); |
1435 | 815k | if (sp1.band.BandWidth != sp2.band.BandWidth) |
1436 | 0 | return(1); |
1437 | 815k | if (sp1.band.BandHeight != sp2.band.BandHeight) |
1438 | 0 | return(1); |
1439 | 815k | if (sp1.band.BandBufferSpace != sp2.band.BandBufferSpace) |
1440 | 0 | return(1); |
1441 | 815k | if (sp1.band.tile_cache_size != sp2.band.tile_cache_size) |
1442 | 0 | return(1); |
1443 | 815k | if (sp1.params_are_read_only != sp2.params_are_read_only) |
1444 | 0 | return(1); |
1445 | 815k | if (sp1.banding_type != sp2.banding_type) |
1446 | 0 | return(1); |
1447 | | |
1448 | 815k | return(0); |
1449 | 815k | } |
1450 | | |
1451 | | static void |
1452 | | release_nts_lock(gx_device *dev) |
1453 | 0 | { |
1454 | 0 | (void)gs_lib_ctx_nts_adjust(dev->memory, -1); |
1455 | 0 | } |
1456 | | |
1457 | | int gx_init_non_threadsafe_device(gx_device *dev) |
1458 | 0 | { |
1459 | 0 | int code; |
1460 | |
|
1461 | 0 | if (dev == NULL || dev->finalize != NULL) |
1462 | 0 | return gs_error_unknownerror; |
1463 | | |
1464 | 0 | code = gs_lib_ctx_nts_adjust(dev->memory, 1); |
1465 | 0 | if (code < 0) |
1466 | 0 | return code; |
1467 | | |
1468 | 0 | dev->finalize = release_nts_lock; |
1469 | |
|
1470 | 0 | return 0; |
1471 | 0 | } |