1 | //======================================================================== |
2 | // |
3 | // PSOutputDev.cc |
4 | // |
5 | // Copyright 1996-2013 Glyph & Cog, LLC |
6 | // |
7 | //======================================================================== |
8 | |
9 | //======================================================================== |
10 | // |
11 | // Modified under the Poppler project - http://poppler.freedesktop.org |
12 | // |
13 | // All changes made under the Poppler project to this file are licensed |
14 | // under GPL version 2 or later |
15 | // |
16 | // Copyright (C) 2005 Martin Kretzschmar <martink@gnome.org> |
17 | // Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com> |
18 | // Copyright (C) 2006-2009, 2011-2013, 2015-2022, 2024 Albert Astals Cid <aacid@kde.org> |
19 | // Copyright (C) 2006 Jeff Muizelaar <jeff@infidigm.net> |
20 | // Copyright (C) 2007, 2008 Brad Hards <bradh@kde.org> |
21 | // Copyright (C) 2008, 2009 Koji Otani <sho@bbr.jp> |
22 | // Copyright (C) 2008, 2010 Hib Eris <hib@hiberis.nl> |
23 | // Copyright (C) 2009-2013 Thomas Freitag <Thomas.Freitag@alfa.de> |
24 | // Copyright (C) 2009 Till Kamppeter <till.kamppeter@gmail.com> |
25 | // Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org> |
26 | // Copyright (C) 2009, 2011, 2012, 2014-2017, 2019, 2020 William Bader <williambader@hotmail.com> |
27 | // Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net> |
28 | // Copyright (C) 2009-2011, 2013-2015, 2017, 2020 Adrian Johnson <ajohnson@redneon.com> |
29 | // Copyright (C) 2012, 2014 Fabio D'Urso <fabiodurso@hotmail.it> |
30 | // Copyright (C) 2012 Lu Wang <coolwanglu@gmail.com> |
31 | // Copyright (C) 2014 Till Kamppeter <till.kamppeter@gmail.com> |
32 | // Copyright (C) 2015 Marek Kasik <mkasik@redhat.com> |
33 | // Copyright (C) 2016 Caolán McNamara <caolanm@redhat.com> |
34 | // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich |
35 | // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de> |
36 | // Copyright (C) 2018 Philipp Knechtges <philipp-dev@knechtges.com> |
37 | // Copyright (C) 2019, 2021 Christian Persch <chpe@src.gnome.org> |
38 | // Copyright (C) 2019, 2021-2023 Oliver Sander <oliver.sander@tu-dresden.de> |
39 | // Copyright (C) 2020, 2021 Philipp Knechtges <philipp-dev@knechtges.com> |
40 | // Copyright (C) 2021 Hubert Figuiere <hub@figuiere.net> |
41 | // Copyright (C) 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
42 | // |
43 | // To see a description of the changes please see the Changelog file that |
44 | // came with your tarball or type make ChangeLog if you are building from git |
45 | // |
46 | //======================================================================== |
47 | |
48 | #include <config.h> |
49 | |
50 | #include <cstdio> |
51 | #include <cstddef> |
52 | #include <cstdarg> |
53 | #include <csignal> |
54 | #include <cmath> |
55 | #include <climits> |
56 | #include <algorithm> |
57 | #include <array> |
58 | #include "goo/GooString.h" |
59 | #include "poppler-config.h" |
60 | #include "GlobalParams.h" |
61 | #include "Object.h" |
62 | #include "Error.h" |
63 | #include "Function.h" |
64 | #include "Gfx.h" |
65 | #include "GfxState.h" |
66 | #include "GfxFont.h" |
67 | #include "UnicodeMap.h" |
68 | #include <fofi/FoFiType1C.h> |
69 | #include <fofi/FoFiTrueType.h> |
70 | #include "Catalog.h" |
71 | #include "Page.h" |
72 | #include "Stream.h" |
73 | #include "FlateEncoder.h" |
74 | #ifdef ENABLE_ZLIB_UNCOMPRESS |
75 | # include "FlateStream.h" |
76 | #endif |
77 | #include "Annot.h" |
78 | #include "XRef.h" |
79 | #include "PreScanOutputDev.h" |
80 | #include "FileSpec.h" |
81 | #include "CharCodeToUnicode.h" |
82 | #include "splash/Splash.h" |
83 | #include "splash/SplashBitmap.h" |
84 | #include "SplashOutputDev.h" |
85 | #include "PSOutputDev.h" |
86 | #include "PDFDoc.h" |
87 | #include "UTF.h" |
88 | |
89 | #ifdef USE_CMS |
90 | # include <lcms2.h> |
91 | #endif |
92 | |
93 | // the MSVC math.h doesn't define this |
94 | #ifndef M_PI |
95 | # define M_PI 3.14159265358979323846 |
96 | #endif |
97 | |
98 | //------------------------------------------------------------------------ |
99 | |
100 | // Max size of a slice when rasterizing pages, in pixels. |
101 | #define rasterizationSliceSize 20000000 |
102 | |
103 | //------------------------------------------------------------------------ |
104 | // PostScript prolog and setup |
105 | //------------------------------------------------------------------------ |
106 | |
107 | // The '~' escapes mark prolog code that is emitted only in certain |
108 | // levels: |
109 | // |
110 | // ~[123][sn] |
111 | // ^ ^----- s=psLevel*Sep, n=psLevel* |
112 | // +----- 1=psLevel1*, 2=psLevel2*, 3=psLevel3* |
113 | |
114 | static const char *prolog[] = { "/xpdf 75 dict def xpdf begin" , |
115 | "% PDF special state" , |
116 | "/pdfDictSize 15 def" , |
117 | "~1sn" , |
118 | "/pdfStates 64 array def" , |
119 | " 0 1 63 {" , |
120 | " pdfStates exch pdfDictSize dict" , |
121 | " dup /pdfStateIdx 3 index put" , |
122 | " put" , |
123 | " } for" , |
124 | "~123sn" , |
125 | "/pdfSetup {" , |
126 | " /setpagedevice where {" , |
127 | " pop 2 dict begin" , |
128 | " /Policies 1 dict dup begin /PageSize 6 def end def" , |
129 | " { /Duplex true def } if" , |
130 | " currentdict end setpagedevice" , |
131 | " } {" , |
132 | " pop" , |
133 | " } ifelse" , |
134 | "} def" , |
135 | "/pdfSetupPaper {" , |
136 | " % Change paper size, but only if different from previous paper size otherwise" , |
137 | " % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size" , |
138 | " % so we use the same when checking if the size changes." , |
139 | " /setpagedevice where {" , |
140 | " pop currentpagedevice" , |
141 | " /PageSize known {" , |
142 | " 2 copy" , |
143 | " currentpagedevice /PageSize get aload pop" , |
144 | " exch 4 1 roll" , |
145 | " sub abs 5 gt" , |
146 | " 3 1 roll" , |
147 | " sub abs 5 gt" , |
148 | " or" , |
149 | " } {" , |
150 | " true" , |
151 | " } ifelse" , |
152 | " {" , |
153 | " 2 array astore" , |
154 | " 2 dict begin" , |
155 | " /PageSize exch def" , |
156 | " /ImagingBBox null def" , |
157 | " currentdict end" , |
158 | " setpagedevice" , |
159 | " } {" , |
160 | " pop pop" , |
161 | " } ifelse" , |
162 | " } {" , |
163 | " pop" , |
164 | " } ifelse" , |
165 | "} def" , |
166 | "~1sn" , |
167 | "/pdfOpNames [" , |
168 | " /pdfFill /pdfStroke /pdfLastFill /pdfLastStroke" , |
169 | " /pdfTextMat /pdfFontSize /pdfCharSpacing /pdfTextRender /pdfPatternCS" , |
170 | " /pdfTextRise /pdfWordSpacing /pdfHorizScaling /pdfTextClipPath" , |
171 | "] def" , |
172 | "~123sn" , |
173 | "/pdfStartPage {" , |
174 | "~1sn" , |
175 | " pdfStates 0 get begin" , |
176 | "~23sn" , |
177 | " pdfDictSize dict begin" , |
178 | "~23n" , |
179 | " /pdfFillCS [] def" , |
180 | " /pdfFillXform {} def" , |
181 | " /pdfStrokeCS [] def" , |
182 | " /pdfStrokeXform {} def" , |
183 | "~1n" , |
184 | " /pdfFill 0 def" , |
185 | " /pdfStroke 0 def" , |
186 | "~1s" , |
187 | " /pdfFill [0 0 0 1] def" , |
188 | " /pdfStroke [0 0 0 1] def" , |
189 | "~23sn" , |
190 | " /pdfFill [0] def" , |
191 | " /pdfStroke [0] def" , |
192 | " /pdfFillOP false def" , |
193 | " /pdfStrokeOP false def" , |
194 | "~3sn" , |
195 | " /pdfOPM false def" , |
196 | "~123sn" , |
197 | " /pdfLastFill false def" , |
198 | " /pdfLastStroke false def" , |
199 | " /pdfTextMat [1 0 0 1 0 0] def" , |
200 | " /pdfFontSize 0 def" , |
201 | " /pdfCharSpacing 0 def" , |
202 | " /pdfTextRender 0 def" , |
203 | " /pdfPatternCS false def" , |
204 | " /pdfTextRise 0 def" , |
205 | " /pdfWordSpacing 0 def" , |
206 | " /pdfHorizScaling 1 def" , |
207 | " /pdfTextClipPath [] def" , |
208 | "} def" , |
209 | "/pdfEndPage { end } def" , |
210 | "~23s" , |
211 | "% separation convention operators" , |
212 | "/findcmykcustomcolor where {" , |
213 | " pop" , |
214 | "}{" , |
215 | " /findcmykcustomcolor { 5 array astore } def" , |
216 | "} ifelse" , |
217 | "/setcustomcolor where {" , |
218 | " pop" , |
219 | "}{" , |
220 | " /setcustomcolor {" , |
221 | " exch" , |
222 | " [ exch /Separation exch dup 4 get exch /DeviceCMYK exch" , |
223 | " 0 4 getinterval cvx" , |
224 | " [ exch /dup load exch { mul exch dup } /forall load" , |
225 | " /pop load dup ] cvx" , |
226 | " ] setcolorspace setcolor" , |
227 | " } def" , |
228 | "} ifelse" , |
229 | "/customcolorimage where {" , |
230 | " pop" , |
231 | "}{" , |
232 | " /customcolorimage {" , |
233 | " gsave" , |
234 | " [ exch /Separation exch dup 4 get exch /DeviceCMYK exch" , |
235 | " 0 4 getinterval" , |
236 | " [ exch /dup load exch { mul exch dup } /forall load" , |
237 | " /pop load dup ] cvx" , |
238 | " ] setcolorspace" , |
239 | " 10 dict begin" , |
240 | " /ImageType 1 def" , |
241 | " /DataSource exch def" , |
242 | " /ImageMatrix exch def" , |
243 | " /BitsPerComponent exch def" , |
244 | " /Height exch def" , |
245 | " /Width exch def" , |
246 | " /Decode [1 0] def" , |
247 | " currentdict end" , |
248 | " image" , |
249 | " grestore" , |
250 | " } def" , |
251 | "} ifelse" , |
252 | "~123sn" , |
253 | "% PDF color state" , |
254 | "~1n" , |
255 | "/g { dup /pdfFill exch def setgray" , |
256 | " /pdfLastFill true def /pdfLastStroke false def } def" , |
257 | "/G { dup /pdfStroke exch def setgray" , |
258 | " /pdfLastStroke true def /pdfLastFill false def } def" , |
259 | "/fCol {" , |
260 | " pdfLastFill not {" , |
261 | " pdfFill setgray" , |
262 | " /pdfLastFill true def /pdfLastStroke false def" , |
263 | " } if" , |
264 | "} def" , |
265 | "/sCol {" , |
266 | " pdfLastStroke not {" , |
267 | " pdfStroke setgray" , |
268 | " /pdfLastStroke true def /pdfLastFill false def" , |
269 | " } if" , |
270 | "} def" , |
271 | "~1s" , |
272 | "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor" , |
273 | " /pdfLastFill true def /pdfLastStroke false def } def" , |
274 | "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor" , |
275 | " /pdfLastStroke true def /pdfLastFill false def } def" , |
276 | "/fCol {" , |
277 | " pdfLastFill not {" , |
278 | " pdfFill aload pop setcmykcolor" , |
279 | " /pdfLastFill true def /pdfLastStroke false def" , |
280 | " } if" , |
281 | "} def" , |
282 | "/sCol {" , |
283 | " pdfLastStroke not {" , |
284 | " pdfStroke aload pop setcmykcolor" , |
285 | " /pdfLastStroke true def /pdfLastFill false def" , |
286 | " } if" , |
287 | "} def" , |
288 | "~3n" , |
289 | "/opm { dup /pdfOPM exch def" , |
290 | " /setoverprintmode where{pop setoverprintmode}{pop}ifelse } def" , |
291 | "~23n" , |
292 | "/cs { /pdfFillXform exch def dup /pdfFillCS exch def" , |
293 | " setcolorspace } def" , |
294 | "/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def" , |
295 | " setcolorspace } def" , |
296 | "/sc { pdfLastFill not { pdfFillCS setcolorspace } if" , |
297 | " dup /pdfFill exch def aload pop pdfFillXform setcolor" , |
298 | " /pdfLastFill true def /pdfLastStroke false def } def" , |
299 | "/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if" , |
300 | " dup /pdfStroke exch def aload pop pdfStrokeXform setcolor" , |
301 | " /pdfLastStroke true def /pdfLastFill false def } def" , |
302 | "/op { /pdfFillOP exch def" , |
303 | " pdfLastFill { pdfFillOP setoverprint } if } def" , |
304 | "/OP { /pdfStrokeOP exch def" , |
305 | " pdfLastStroke { pdfStrokeOP setoverprint } if } def" , |
306 | "/fCol {" , |
307 | " pdfLastFill not {" , |
308 | " pdfFillCS setcolorspace" , |
309 | " pdfFill aload pop pdfFillXform setcolor" , |
310 | " pdfFillOP setoverprint" , |
311 | " /pdfLastFill true def /pdfLastStroke false def" , |
312 | " } if" , |
313 | "} def" , |
314 | "/sCol {" , |
315 | " pdfLastStroke not {" , |
316 | " pdfStrokeCS setcolorspace" , |
317 | " pdfStroke aload pop pdfStrokeXform setcolor" , |
318 | " pdfStrokeOP setoverprint" , |
319 | " /pdfLastStroke true def /pdfLastFill false def" , |
320 | " } if" , |
321 | "} def" , |
322 | "~3s" , |
323 | "/opm { dup /pdfOPM exch def" , |
324 | " /setoverprintmode where{pop setoverprintmode}{pop}ifelse } def" , |
325 | "~23s" , |
326 | "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor" , |
327 | " /pdfLastFill true def /pdfLastStroke false def } def" , |
328 | "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor" , |
329 | " /pdfLastStroke true def /pdfLastFill false def } def" , |
330 | "/ck { 6 copy 6 array astore /pdfFill exch def" , |
331 | " findcmykcustomcolor exch setcustomcolor" , |
332 | " /pdfLastFill true def /pdfLastStroke false def } def" , |
333 | "/CK { 6 copy 6 array astore /pdfStroke exch def" , |
334 | " findcmykcustomcolor exch setcustomcolor" , |
335 | " /pdfLastStroke true def /pdfLastFill false def } def" , |
336 | "/op { /pdfFillOP exch def" , |
337 | " pdfLastFill { pdfFillOP setoverprint } if } def" , |
338 | "/OP { /pdfStrokeOP exch def" , |
339 | " pdfLastStroke { pdfStrokeOP setoverprint } if } def" , |
340 | "/fCol {" , |
341 | " pdfLastFill not {" , |
342 | " pdfFill aload length 4 eq {" , |
343 | " setcmykcolor" , |
344 | " }{" , |
345 | " findcmykcustomcolor exch setcustomcolor" , |
346 | " } ifelse" , |
347 | " pdfFillOP setoverprint" , |
348 | " /pdfLastFill true def /pdfLastStroke false def" , |
349 | " } if" , |
350 | "} def" , |
351 | "/sCol {" , |
352 | " pdfLastStroke not {" , |
353 | " pdfStroke aload length 4 eq {" , |
354 | " setcmykcolor" , |
355 | " }{" , |
356 | " findcmykcustomcolor exch setcustomcolor" , |
357 | " } ifelse" , |
358 | " pdfStrokeOP setoverprint" , |
359 | " /pdfLastStroke true def /pdfLastFill false def" , |
360 | " } if" , |
361 | "} def" , |
362 | "~123sn" , |
363 | "% build a font" , |
364 | "/pdfMakeFont {" , |
365 | " 4 3 roll findfont" , |
366 | " 4 2 roll matrix scale makefont" , |
367 | " dup length dict begin" , |
368 | " { 1 index /FID ne { def } { pop pop } ifelse } forall" , |
369 | " /Encoding exch def" , |
370 | " currentdict" , |
371 | " end" , |
372 | " definefont pop" , |
373 | "} def" , |
374 | "/pdfMakeFont16 {" , |
375 | " exch findfont" , |
376 | " dup length dict begin" , |
377 | " { 1 index /FID ne { def } { pop pop } ifelse } forall" , |
378 | " /WMode exch def" , |
379 | " currentdict" , |
380 | " end" , |
381 | " definefont pop" , |
382 | "} def" , |
383 | "~3sn" , |
384 | "/pdfMakeFont16L3 {" , |
385 | " 1 index /CIDFont resourcestatus {" , |
386 | " pop pop 1 index /CIDFont findresource /CIDFontType known" , |
387 | " } {" , |
388 | " false" , |
389 | " } ifelse" , |
390 | " {" , |
391 | " 0 eq { /Identity-H } { /Identity-V } ifelse" , |
392 | " exch 1 array astore composefont pop" , |
393 | " } {" , |
394 | " pdfMakeFont16" , |
395 | " } ifelse" , |
396 | "} def" , |
397 | "~123sn" , |
398 | "% graphics state operators" , |
399 | "~1sn" , |
400 | "/q {" , |
401 | " gsave" , |
402 | " pdfOpNames length 1 sub -1 0 { pdfOpNames exch get load } for" , |
403 | " pdfStates pdfStateIdx 1 add get begin" , |
404 | " pdfOpNames { exch def } forall" , |
405 | "} def" , |
406 | "/Q { end grestore } def" , |
407 | "~23sn" , |
408 | "/q { gsave pdfDictSize dict begin } def" , |
409 | "/Q {" , |
410 | " end grestore" , |
411 | " /pdfLastFill where {" , |
412 | " pop" , |
413 | " pdfLastFill {" , |
414 | " pdfFillOP setoverprint" , |
415 | " } {" , |
416 | " pdfStrokeOP setoverprint" , |
417 | " } ifelse" , |
418 | " } if" , |
419 | "~3sn" , |
420 | " /pdfOPM where {" , |
421 | " pop" , |
422 | " pdfOPM /setoverprintmode where{pop setoverprintmode}{pop}ifelse " , |
423 | " } if" , |
424 | "~23sn" , |
425 | "} def" , |
426 | "~123sn" , |
427 | "/cm { concat } def" , |
428 | "/d { setdash } def" , |
429 | "/i { setflat } def" , |
430 | "/j { setlinejoin } def" , |
431 | "/J { setlinecap } def" , |
432 | "/M { setmiterlimit } def" , |
433 | "/w { setlinewidth } def" , |
434 | "% path segment operators" , |
435 | "/m { moveto } def" , |
436 | "/l { lineto } def" , |
437 | "/c { curveto } def" , |
438 | "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto" , |
439 | " neg 0 rlineto closepath } def" , |
440 | "/h { closepath } def" , |
441 | "% path painting operators" , |
442 | "/S { sCol stroke } def" , |
443 | "/Sf { fCol stroke } def" , |
444 | "/f { fCol fill } def" , |
445 | "/f* { fCol eofill } def" , |
446 | "% clipping operators" , |
447 | "/W { clip newpath } def" , |
448 | "/W* { eoclip newpath } def" , |
449 | "/Ws { strokepath clip newpath } def" , |
450 | "% text state operators" , |
451 | "/Tc { /pdfCharSpacing exch def } def" , |
452 | "/Tf { dup /pdfFontSize exch def" , |
453 | " dup pdfHorizScaling mul exch matrix scale" , |
454 | " pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put" , |
455 | " exch findfont exch makefont setfont } def" , |
456 | "/Tr { /pdfTextRender exch def } def" , |
457 | "/Tp { /pdfPatternCS exch def } def" , |
458 | "/Ts { /pdfTextRise exch def } def" , |
459 | "/Tw { /pdfWordSpacing exch def } def" , |
460 | "/Tz { /pdfHorizScaling exch def } def" , |
461 | "% text positioning operators" , |
462 | "/Td { pdfTextMat transform moveto } def" , |
463 | "/Tm { /pdfTextMat exch def } def" , |
464 | "% text string operators" , |
465 | "/xyshow where {" , |
466 | " pop" , |
467 | " /xyshow2 {" , |
468 | " dup length array" , |
469 | " 0 2 2 index length 1 sub {" , |
470 | " 2 index 1 index 2 copy get 3 1 roll 1 add get" , |
471 | " pdfTextMat dtransform" , |
472 | " 4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put" , |
473 | " } for" , |
474 | " exch pop" , |
475 | " xyshow" , |
476 | " } def" , |
477 | "}{" , |
478 | " /xyshow2 {" , |
479 | " currentfont /FontType get 0 eq {" , |
480 | " 0 2 3 index length 1 sub {" , |
481 | " currentpoint 4 index 3 index 2 getinterval show moveto" , |
482 | " 2 copy get 2 index 3 2 roll 1 add get" , |
483 | " pdfTextMat dtransform rmoveto" , |
484 | " } for" , |
485 | " } {" , |
486 | " 0 1 3 index length 1 sub {" , |
487 | " currentpoint 4 index 3 index 1 getinterval show moveto" , |
488 | " 2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get" , |
489 | " pdfTextMat dtransform rmoveto" , |
490 | " } for" , |
491 | " } ifelse" , |
492 | " pop pop" , |
493 | " } def" , |
494 | "} ifelse" , |
495 | "/cshow where {" , |
496 | " pop" , |
497 | " /xycp {" , // xycharpath |
498 | " 0 3 2 roll" , |
499 | " {" , |
500 | " pop pop currentpoint 3 2 roll" , |
501 | " 1 string dup 0 4 3 roll put false charpath moveto" , |
502 | " 2 copy get 2 index 2 index 1 add get" , |
503 | " pdfTextMat dtransform rmoveto" , |
504 | " 2 add" , |
505 | " } exch cshow" , |
506 | " pop pop" , |
507 | " } def" , |
508 | "}{" , |
509 | " /xycp {" , // xycharpath |
510 | " currentfont /FontType get 0 eq {" , |
511 | " 0 2 3 index length 1 sub {" , |
512 | " currentpoint 4 index 3 index 2 getinterval false charpath moveto" , |
513 | " 2 copy get 2 index 3 2 roll 1 add get" , |
514 | " pdfTextMat dtransform rmoveto" , |
515 | " } for" , |
516 | " } {" , |
517 | " 0 1 3 index length 1 sub {" , |
518 | " currentpoint 4 index 3 index 1 getinterval false charpath moveto" , |
519 | " 2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get" , |
520 | " pdfTextMat dtransform rmoveto" , |
521 | " } for" , |
522 | " } ifelse" , |
523 | " pop pop" , |
524 | " } def" , |
525 | "} ifelse" , |
526 | "/Tj {" , |
527 | " fCol" , // because stringwidth has to draw Type 3 chars |
528 | " 0 pdfTextRise pdfTextMat dtransform rmoveto" , |
529 | " currentpoint 4 2 roll" , |
530 | " pdfTextRender 1 and 0 eq {" , |
531 | " 2 copy xyshow2" , |
532 | " } if" , |
533 | " pdfTextRender 3 and dup 1 eq exch 2 eq or {" , |
534 | " 3 index 3 index moveto" , |
535 | " 2 copy" , |
536 | " currentfont /FontType get 3 eq { fCol } { sCol } ifelse" , |
537 | " xycp currentpoint stroke moveto" , |
538 | " } if" , |
539 | " pdfTextRender 4 and 0 ne {" , |
540 | " 4 2 roll moveto xycp" , |
541 | " /pdfTextClipPath [ pdfTextClipPath aload pop" , |
542 | " {/moveto cvx}" , |
543 | " {/lineto cvx}" , |
544 | " {/curveto cvx}" , |
545 | " {/closepath cvx}" , |
546 | " pathforall ] def" , |
547 | " currentpoint newpath moveto" , |
548 | " } {" , |
549 | " pop pop pop pop" , |
550 | " } ifelse" , |
551 | " 0 pdfTextRise neg pdfTextMat dtransform rmoveto" , |
552 | "} def" , |
553 | "/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0" , |
554 | " pdfTextMat dtransform rmoveto } def" , |
555 | "/TJmV { 0.001 mul pdfFontSize mul neg 0 exch" , |
556 | " pdfTextMat dtransform rmoveto } def" , |
557 | "/Tclip { pdfTextClipPath cvx exec clip newpath" , |
558 | " /pdfTextClipPath [] def } def" , |
559 | "/Tclip* { pdfTextClipPath cvx exec eoclip newpath" , |
560 | " /pdfTextClipPath [] def } def" , |
561 | "~1ns" , |
562 | "% Level 1 image operators" , |
563 | "/pdfIm1 {" , |
564 | " /pdfImBuf1 4 index string def" , |
565 | " { currentfile pdfImBuf1 readhexstring pop } image" , |
566 | "} def" , |
567 | "/pdfIm1Bin {" , |
568 | " /pdfImBuf1 4 index string def" , |
569 | " { currentfile pdfImBuf1 readstring pop } image" , |
570 | "} def" , |
571 | "~1s" , |
572 | "/pdfIm1Sep {" , |
573 | " /pdfImBuf1 4 index string def" , |
574 | " /pdfImBuf2 4 index string def" , |
575 | " /pdfImBuf3 4 index string def" , |
576 | " /pdfImBuf4 4 index string def" , |
577 | " { currentfile pdfImBuf1 readhexstring pop }" , |
578 | " { currentfile pdfImBuf2 readhexstring pop }" , |
579 | " { currentfile pdfImBuf3 readhexstring pop }" , |
580 | " { currentfile pdfImBuf4 readhexstring pop }" , |
581 | " true 4 colorimage" , |
582 | "} def" , |
583 | "/pdfIm1SepBin {" , |
584 | " /pdfImBuf1 4 index string def" , |
585 | " /pdfImBuf2 4 index string def" , |
586 | " /pdfImBuf3 4 index string def" , |
587 | " /pdfImBuf4 4 index string def" , |
588 | " { currentfile pdfImBuf1 readstring pop }" , |
589 | " { currentfile pdfImBuf2 readstring pop }" , |
590 | " { currentfile pdfImBuf3 readstring pop }" , |
591 | " { currentfile pdfImBuf4 readstring pop }" , |
592 | " true 4 colorimage" , |
593 | "} def" , |
594 | "~1ns" , |
595 | "/pdfImM1 {" , |
596 | " fCol /pdfImBuf1 4 index 7 add 8 idiv string def" , |
597 | " { currentfile pdfImBuf1 readhexstring pop } imagemask" , |
598 | "} def" , |
599 | "/pdfImM1Bin {" , |
600 | " fCol /pdfImBuf1 4 index 7 add 8 idiv string def" , |
601 | " { currentfile pdfImBuf1 readstring pop } imagemask" , |
602 | "} def" , |
603 | "/pdfImStr {" , |
604 | " 2 copy exch length lt {" , |
605 | " 2 copy get exch 1 add exch" , |
606 | " } {" , |
607 | " ()" , |
608 | " } ifelse" , |
609 | "} def" , |
610 | "/pdfImM1a {" , |
611 | " { pdfImStr } imagemask" , |
612 | " pop pop" , |
613 | "} def" , |
614 | "~23sn" , |
615 | "% Level 2/3 image operators" , |
616 | "/pdfImBuf 100 string def" , |
617 | "/pdfImStr {" , |
618 | " 2 copy exch length lt {" , |
619 | " 2 copy get exch 1 add exch" , |
620 | " } {" , |
621 | " ()" , |
622 | " } ifelse" , |
623 | "} def" , |
624 | "/skipEOD {" , |
625 | " { currentfile pdfImBuf readline" , |
626 | " not { pop exit } if" , |
627 | " (%-EOD-) eq { exit } if } loop" , |
628 | "} def" , |
629 | "/pdfIm { image skipEOD } def" , |
630 | "~3sn" , |
631 | "/pdfMask {" , |
632 | " /ReusableStreamDecode filter" , |
633 | " skipEOD" , |
634 | " /maskStream exch def" , |
635 | "} def" , |
636 | "/pdfMaskEnd { maskStream closefile } def" , |
637 | "/pdfMaskInit {" , |
638 | " /maskArray exch def" , |
639 | " /maskIdx 0 def" , |
640 | "} def" , |
641 | "/pdfMaskSrc {" , |
642 | " maskIdx maskArray length lt {" , |
643 | " maskArray maskIdx get" , |
644 | " /maskIdx maskIdx 1 add def" , |
645 | " } {" , |
646 | " ()" , |
647 | " } ifelse" , |
648 | "} def" , |
649 | "~23s" , |
650 | "/pdfImSep {" , |
651 | " findcmykcustomcolor exch" , |
652 | " dup /Width get /pdfImBuf1 exch string def" , |
653 | " dup /Decode get aload pop 1 index sub /pdfImDecodeRange exch def" , |
654 | " /pdfImDecodeLow exch def" , |
655 | " begin Width Height BitsPerComponent ImageMatrix DataSource end" , |
656 | " /pdfImData exch def" , |
657 | " { pdfImData pdfImBuf1 readstring pop" , |
658 | " 0 1 2 index length 1 sub {" , |
659 | " 1 index exch 2 copy get" , |
660 | " pdfImDecodeRange mul 255 div pdfImDecodeLow add round cvi" , |
661 | " 255 exch sub put" , |
662 | " } for }" , |
663 | " 6 5 roll customcolorimage" , |
664 | " skipEOD" , |
665 | "} def" , |
666 | "~23sn" , |
667 | "/pdfImM { fCol imagemask skipEOD } def" , |
668 | "~123sn" , |
669 | "/pr { 2 index 2 index 3 2 roll putinterval 4 add } def" , |
670 | "/pdfImClip {" , |
671 | " gsave" , |
672 | " 0 2 4 index length 1 sub {" , |
673 | " dup 4 index exch 2 copy" , |
674 | " get 5 index div put" , |
675 | " 1 add 3 index exch 2 copy" , |
676 | " get 3 index div put" , |
677 | " } for" , |
678 | " pop pop rectclip" , |
679 | "} def" , |
680 | "/pdfImClipEnd { grestore } def" , |
681 | "~23sn" , |
682 | "% shading operators" , |
683 | "/colordelta {" , |
684 | " false 0 1 3 index length 1 sub {" , |
685 | " dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {" , |
686 | " pop true" , |
687 | " } if" , |
688 | " } for" , |
689 | " exch pop exch pop" , |
690 | "} def" , |
691 | "/funcCol { func n array astore } def" , |
692 | "/funcSH {" , |
693 | " dup 0 eq {" , |
694 | " true" , |
695 | " } {" , |
696 | " dup 6 eq {" , |
697 | " false" , |
698 | " } {" , |
699 | " 4 index 4 index funcCol dup" , |
700 | " 6 index 4 index funcCol dup" , |
701 | " 3 1 roll colordelta 3 1 roll" , |
702 | " 5 index 5 index funcCol dup" , |
703 | " 3 1 roll colordelta 3 1 roll" , |
704 | " 6 index 8 index funcCol dup" , |
705 | " 3 1 roll colordelta 3 1 roll" , |
706 | " colordelta or or or" , |
707 | " } ifelse" , |
708 | " } ifelse" , |
709 | " {" , |
710 | " 1 add" , |
711 | " 4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch" , |
712 | " 6 index 6 index 4 index 4 index 4 index funcSH" , |
713 | " 2 index 6 index 6 index 4 index 4 index funcSH" , |
714 | " 6 index 2 index 4 index 6 index 4 index funcSH" , |
715 | " 5 3 roll 3 2 roll funcSH pop pop" , |
716 | " } {" , |
717 | " pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul" , |
718 | "~23n" , |
719 | " funcCol sc" , |
720 | "~23s" , |
721 | " funcCol aload pop k" , |
722 | "~23sn" , |
723 | " dup 4 index exch mat transform m" , |
724 | " 3 index 3 index mat transform l" , |
725 | " 1 index 3 index mat transform l" , |
726 | " mat transform l pop pop h f*" , |
727 | " } ifelse" , |
728 | "} def" , |
729 | "/axialCol {" , |
730 | " dup 0 lt {" , |
731 | " pop t0" , |
732 | " } {" , |
733 | " dup 1 gt {" , |
734 | " pop t1" , |
735 | " } {" , |
736 | " dt mul t0 add" , |
737 | " } ifelse" , |
738 | " } ifelse" , |
739 | " func n array astore" , |
740 | "} def" , |
741 | "/axialSH {" , |
742 | " dup 0 eq {" , |
743 | " true" , |
744 | " } {" , |
745 | " dup 8 eq {" , |
746 | " false" , |
747 | " } {" , |
748 | " 2 index axialCol 2 index axialCol colordelta" , |
749 | " } ifelse" , |
750 | " } ifelse" , |
751 | " {" , |
752 | " 1 add 3 1 roll 2 copy add 0.5 mul" , |
753 | " dup 4 3 roll exch 4 index axialSH" , |
754 | " exch 3 2 roll axialSH" , |
755 | " } {" , |
756 | " pop 2 copy add 0.5 mul" , |
757 | "~23n" , |
758 | " axialCol sc" , |
759 | "~23s" , |
760 | " axialCol aload pop k" , |
761 | "~23sn" , |
762 | " exch dup dx mul x0 add exch dy mul y0 add" , |
763 | " 3 2 roll dup dx mul x0 add exch dy mul y0 add" , |
764 | " dx abs dy abs ge {" , |
765 | " 2 copy yMin sub dy mul dx div add yMin m" , |
766 | " yMax sub dy mul dx div add yMax l" , |
767 | " 2 copy yMax sub dy mul dx div add yMax l" , |
768 | " yMin sub dy mul dx div add yMin l" , |
769 | " h f*" , |
770 | " } {" , |
771 | " exch 2 copy xMin sub dx mul dy div add xMin exch m" , |
772 | " xMax sub dx mul dy div add xMax exch l" , |
773 | " exch 2 copy xMax sub dx mul dy div add xMax exch l" , |
774 | " xMin sub dx mul dy div add xMin exch l" , |
775 | " h f*" , |
776 | " } ifelse" , |
777 | " } ifelse" , |
778 | "} def" , |
779 | "/radialCol {" , |
780 | " dup t0 lt {" , |
781 | " pop t0" , |
782 | " } {" , |
783 | " dup t1 gt {" , |
784 | " pop t1" , |
785 | " } if" , |
786 | " } ifelse" , |
787 | " func n array astore" , |
788 | "} def" , |
789 | "/radialSH {" , |
790 | " dup 0 eq {" , |
791 | " true" , |
792 | " } {" , |
793 | " dup 8 eq {" , |
794 | " false" , |
795 | " } {" , |
796 | " 2 index dt mul t0 add radialCol" , |
797 | " 2 index dt mul t0 add radialCol colordelta" , |
798 | " } ifelse" , |
799 | " } ifelse" , |
800 | " {" , |
801 | " 1 add 3 1 roll 2 copy add 0.5 mul" , |
802 | " dup 4 3 roll exch 4 index radialSH" , |
803 | " exch 3 2 roll radialSH" , |
804 | " } {" , |
805 | " pop 2 copy add 0.5 mul dt mul t0 add" , |
806 | "~23n" , |
807 | " radialCol sc" , |
808 | "~23s" , |
809 | " radialCol aload pop k" , |
810 | "~23sn" , |
811 | " encl {" , |
812 | " exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add" , |
813 | " 0 360 arc h" , |
814 | " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add" , |
815 | " 360 0 arcn h f" , |
816 | " } {" , |
817 | " 2 copy" , |
818 | " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add" , |
819 | " a1 a2 arcn" , |
820 | " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add" , |
821 | " a2 a1 arcn h" , |
822 | " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add" , |
823 | " a1 a2 arc" , |
824 | " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add" , |
825 | " a2 a1 arc h f" , |
826 | " } ifelse" , |
827 | " } ifelse" , |
828 | "} def" , |
829 | "~123sn" , |
830 | "end" , |
831 | nullptr }; |
832 | |
833 | static const char *cmapProlog[] = { "/CIDInit /ProcSet findresource begin" , |
834 | "10 dict begin" , |
835 | " begincmap" , |
836 | " /CMapType 1 def" , |
837 | " /CMapName /Identity-H def" , |
838 | " /CIDSystemInfo 3 dict dup begin" , |
839 | " /Registry (Adobe) def" , |
840 | " /Ordering (Identity) def" , |
841 | " /Supplement 0 def" , |
842 | " end def" , |
843 | " 1 begincodespacerange" , |
844 | " <0000> <ffff>" , |
845 | " endcodespacerange" , |
846 | " 0 usefont" , |
847 | " 1 begincidrange" , |
848 | " <0000> <ffff> 0" , |
849 | " endcidrange" , |
850 | " endcmap" , |
851 | " currentdict CMapName exch /CMap defineresource pop" , |
852 | "end" , |
853 | "10 dict begin" , |
854 | " begincmap" , |
855 | " /CMapType 1 def" , |
856 | " /CMapName /Identity-V def" , |
857 | " /CIDSystemInfo 3 dict dup begin" , |
858 | " /Registry (Adobe) def" , |
859 | " /Ordering (Identity) def" , |
860 | " /Supplement 0 def" , |
861 | " end def" , |
862 | " /WMode 1 def" , |
863 | " 1 begincodespacerange" , |
864 | " <0000> <ffff>" , |
865 | " endcodespacerange" , |
866 | " 0 usefont" , |
867 | " 1 begincidrange" , |
868 | " <0000> <ffff> 0" , |
869 | " endcidrange" , |
870 | " endcmap" , |
871 | " currentdict CMapName exch /CMap defineresource pop" , |
872 | "end" , |
873 | "end" , |
874 | nullptr }; |
875 | |
876 | //------------------------------------------------------------------------ |
877 | // Fonts |
878 | //------------------------------------------------------------------------ |
879 | |
880 | struct PSSubstFont |
881 | { |
882 | const char *psName; // PostScript name |
883 | double mWidth; // width of 'm' character |
884 | }; |
885 | |
886 | // NB: must be in same order as base14SubstFonts in GfxFont.cc |
887 | static const PSSubstFont psBase14SubstFonts[14] = { { .psName: "Courier" , .mWidth: 0.600 }, |
888 | { .psName: "Courier-Oblique" , .mWidth: 0.600 }, |
889 | { .psName: "Courier-Bold" , .mWidth: 0.600 }, |
890 | { .psName: "Courier-BoldOblique" , .mWidth: 0.600 }, |
891 | { .psName: "Helvetica" , .mWidth: 0.833 }, |
892 | { .psName: "Helvetica-Oblique" , .mWidth: 0.833 }, |
893 | { .psName: "Helvetica-Bold" , .mWidth: 0.889 }, |
894 | { .psName: "Helvetica-BoldOblique" , .mWidth: 0.889 }, |
895 | { .psName: "Times-Roman" , .mWidth: 0.788 }, |
896 | { .psName: "Times-Italic" , .mWidth: 0.722 }, |
897 | { .psName: "Times-Bold" , .mWidth: 0.833 }, |
898 | { .psName: "Times-BoldItalic" , .mWidth: 0.778 }, |
899 | // the last two are never used for substitution |
900 | { .psName: "Symbol" , .mWidth: 0 }, |
901 | { .psName: "ZapfDingbats" , .mWidth: 0 } }; |
902 | |
903 | // Mapping from Type 1/1C font file to PS font name. |
904 | struct PST1FontName |
905 | { |
906 | Ref fontFileID; |
907 | GooString *psName; // PostScript font name used for this |
908 | // embedded font file |
909 | }; |
910 | |
911 | // Info for 8-bit fonts |
912 | struct PSFont8Info |
913 | { |
914 | Ref fontID; |
915 | int *codeToGID; // code-to-GID mapping for TrueType fonts |
916 | }; |
917 | |
918 | // Encoding info for substitute 16-bit font |
919 | struct PSFont16Enc |
920 | { |
921 | Ref fontID; |
922 | GooString *enc; |
923 | }; |
924 | |
925 | //------------------------------------------------------------------------ |
926 | // process colors |
927 | //------------------------------------------------------------------------ |
928 | |
929 | #define psProcessCyan 1 |
930 | #define psProcessMagenta 2 |
931 | #define psProcessYellow 4 |
932 | #define psProcessBlack 8 |
933 | #define psProcessCMYK 15 |
934 | |
935 | //------------------------------------------------------------------------ |
936 | // PSOutCustomColor |
937 | //------------------------------------------------------------------------ |
938 | |
939 | class PSOutCustomColor |
940 | { |
941 | public: |
942 | PSOutCustomColor(double cA, double mA, double yA, double kA, GooString *nameA); |
943 | ~PSOutCustomColor(); |
944 | |
945 | PSOutCustomColor(const PSOutCustomColor &) = delete; |
946 | PSOutCustomColor &operator=(const PSOutCustomColor &) = delete; |
947 | |
948 | double c, m, y, k; |
949 | GooString *name; |
950 | PSOutCustomColor *next; |
951 | }; |
952 | |
953 | PSOutCustomColor::PSOutCustomColor(double cA, double mA, double yA, double kA, GooString *nameA) |
954 | { |
955 | c = cA; |
956 | m = mA; |
957 | y = yA; |
958 | k = kA; |
959 | name = nameA; |
960 | next = nullptr; |
961 | } |
962 | |
963 | PSOutCustomColor::~PSOutCustomColor() |
964 | { |
965 | delete name; |
966 | } |
967 | |
968 | //------------------------------------------------------------------------ |
969 | |
970 | struct PSOutImgClipRect |
971 | { |
972 | int x0, x1, y0, y1; |
973 | }; |
974 | |
975 | //------------------------------------------------------------------------ |
976 | // DeviceNRecoder |
977 | //------------------------------------------------------------------------ |
978 | |
979 | class DeviceNRecoder : public FilterStream |
980 | { |
981 | public: |
982 | DeviceNRecoder(Stream *strA, int widthA, int heightA, GfxImageColorMap *colorMapA); |
983 | ~DeviceNRecoder() override; |
984 | StreamKind getKind() const override { return strWeird; } |
985 | void reset() override; |
986 | int getChar() override { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx++]; } |
987 | int lookChar() override { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx]; } |
988 | GooString *getPSFilter(int psLevel, const char *indent) override { return nullptr; } |
989 | bool isBinary(bool last = true) const override { return true; } |
990 | bool isEncoder() const override { return true; } |
991 | |
992 | private: |
993 | bool fillBuf(); |
994 | |
995 | int width, height; |
996 | GfxImageColorMap *colorMap; |
997 | const Function *func; |
998 | ImageStream *imgStr; |
999 | int buf[gfxColorMaxComps]; |
1000 | int pixelIdx; |
1001 | int bufIdx; |
1002 | int bufSize; |
1003 | }; |
1004 | |
1005 | DeviceNRecoder::DeviceNRecoder(Stream *strA, int widthA, int heightA, GfxImageColorMap *colorMapA) : FilterStream(strA) |
1006 | { |
1007 | width = widthA; |
1008 | height = heightA; |
1009 | colorMap = colorMapA; |
1010 | imgStr = nullptr; |
1011 | pixelIdx = 0; |
1012 | bufIdx = gfxColorMaxComps; |
1013 | bufSize = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getAlt()->getNComps(); |
1014 | func = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getTintTransformFunc(); |
1015 | } |
1016 | |
1017 | DeviceNRecoder::~DeviceNRecoder() |
1018 | { |
1019 | if (imgStr) { |
1020 | delete imgStr; |
1021 | } |
1022 | if (str->isEncoder()) { |
1023 | delete str; |
1024 | } |
1025 | } |
1026 | |
1027 | void DeviceNRecoder::reset() |
1028 | { |
1029 | imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); |
1030 | imgStr->reset(); |
1031 | } |
1032 | |
1033 | bool DeviceNRecoder::fillBuf() |
1034 | { |
1035 | unsigned char pixBuf[gfxColorMaxComps]; |
1036 | GfxColor color; |
1037 | double x[gfxColorMaxComps], y[gfxColorMaxComps]; |
1038 | int i; |
1039 | |
1040 | if (pixelIdx >= width * height) { |
1041 | return false; |
1042 | } |
1043 | imgStr->getPixel(pix: pixBuf); |
1044 | colorMap->getColor(x: pixBuf, color: &color); |
1045 | for (i = 0; i < ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getNComps(); ++i) { |
1046 | x[i] = colToDbl(x: color.c[i]); |
1047 | } |
1048 | func->transform(in: x, out: y); |
1049 | for (i = 0; i < bufSize; ++i) { |
1050 | buf[i] = (int)(y[i] * 255 + 0.5); |
1051 | } |
1052 | bufIdx = 0; |
1053 | ++pixelIdx; |
1054 | return true; |
1055 | } |
1056 | |
1057 | //------------------------------------------------------------------------ |
1058 | // PSOutputDev |
1059 | //------------------------------------------------------------------------ |
1060 | |
1061 | extern "C" { |
1062 | typedef void (*SignalFunc)(int); |
1063 | } |
1064 | |
1065 | static void outputToFile(void *stream, const char *data, size_t len) |
1066 | { |
1067 | fwrite(ptr: data, size: 1, n: len, s: (FILE *)stream); |
1068 | } |
1069 | |
1070 | PSOutputDev::PSOutputDev(const char *fileName, PDFDoc *docA, char *psTitleA, const std::vector<int> &pagesA, PSOutMode modeA, int paperWidthA, int paperHeightA, bool noCropA, bool duplexA, int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, |
1071 | PSForceRasterize forceRasterizeA, bool manualCtrlA, PSOutCustomCodeCbk customCodeCbkA, void *customCodeCbkDataA, PSLevel levelA) |
1072 | { |
1073 | FILE *f; |
1074 | PSFileType fileTypeA; |
1075 | |
1076 | underlayCbk = nullptr; |
1077 | underlayCbkData = nullptr; |
1078 | overlayCbk = nullptr; |
1079 | overlayCbkData = nullptr; |
1080 | customCodeCbk = customCodeCbkA; |
1081 | customCodeCbkData = customCodeCbkDataA; |
1082 | |
1083 | t1FontNames = nullptr; |
1084 | font8Info = nullptr; |
1085 | font16Enc = nullptr; |
1086 | imgIDs = nullptr; |
1087 | formIDs = nullptr; |
1088 | embFontList = nullptr; |
1089 | customColors = nullptr; |
1090 | haveTextClip = false; |
1091 | t3String = nullptr; |
1092 | forceRasterize = forceRasterizeA; |
1093 | psTitle = nullptr; |
1094 | |
1095 | // open file or pipe |
1096 | if (!strcmp(s1: fileName, s2: "-" )) { |
1097 | fileTypeA = psStdout; |
1098 | f = stdout; |
1099 | } else if (fileName[0] == '|') { |
1100 | fileTypeA = psPipe; |
1101 | #ifdef HAVE_POPEN |
1102 | # ifndef _WIN32 |
1103 | signal(SIGPIPE, handler: (SignalFunc)SIG_IGN); |
1104 | # endif |
1105 | if (!(f = popen(command: fileName + 1, modes: "w" ))) { |
1106 | error(category: errIO, pos: -1, msg: "Couldn't run print command '{0:s}'" , fileName); |
1107 | ok = false; |
1108 | return; |
1109 | } |
1110 | #else |
1111 | error(errIO, -1, "Print commands are not supported ('{0:s}')" , fileName); |
1112 | ok = false; |
1113 | return; |
1114 | #endif |
1115 | } else { |
1116 | fileTypeA = psFile; |
1117 | if (!(f = openFile(path: fileName, mode: "w" ))) { |
1118 | error(category: errIO, pos: -1, msg: "Couldn't open PostScript file '{0:s}'" , fileName); |
1119 | ok = false; |
1120 | return; |
1121 | } |
1122 | } |
1123 | |
1124 | init(outputFuncA: outputToFile, outputStreamA: f, fileTypeA, psTitleA, doc: docA, pages: pagesA, modeA, imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA, paperWidthA, paperHeightA, noCropA, duplexA, levelA); |
1125 | } |
1126 | |
1127 | PSOutputDev::PSOutputDev(int fdA, PDFDoc *docA, char *psTitleA, const std::vector<int> &pagesA, PSOutMode modeA, int paperWidthA, int paperHeightA, bool noCropA, bool duplexA, int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, |
1128 | PSForceRasterize forceRasterizeA, bool manualCtrlA, PSOutCustomCodeCbk customCodeCbkA, void *customCodeCbkDataA, PSLevel levelA) |
1129 | { |
1130 | FILE *f; |
1131 | PSFileType fileTypeA; |
1132 | |
1133 | underlayCbk = nullptr; |
1134 | underlayCbkData = nullptr; |
1135 | overlayCbk = nullptr; |
1136 | overlayCbkData = nullptr; |
1137 | customCodeCbk = customCodeCbkA; |
1138 | customCodeCbkData = customCodeCbkDataA; |
1139 | |
1140 | t1FontNames = nullptr; |
1141 | font8Info = nullptr; |
1142 | font16Enc = nullptr; |
1143 | imgIDs = nullptr; |
1144 | formIDs = nullptr; |
1145 | embFontList = nullptr; |
1146 | customColors = nullptr; |
1147 | haveTextClip = false; |
1148 | t3String = nullptr; |
1149 | forceRasterize = forceRasterizeA; |
1150 | psTitle = nullptr; |
1151 | |
1152 | // open file or pipe |
1153 | if (fdA == fileno(stdout)) { |
1154 | fileTypeA = psStdout; |
1155 | f = stdout; |
1156 | } else { |
1157 | fileTypeA = psFile; |
1158 | if (!(f = fdopen(fd: fdA, modes: "w" ))) { |
1159 | error(category: errIO, pos: -1, msg: "Couldn't open PostScript file descriptor '{0:d}'" , fdA); |
1160 | ok = false; |
1161 | return; |
1162 | } |
1163 | } |
1164 | |
1165 | init(outputFuncA: outputToFile, outputStreamA: f, fileTypeA, psTitleA, doc: docA, pages: pagesA, modeA, imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA, paperWidthA, paperHeightA, noCropA, duplexA, levelA); |
1166 | } |
1167 | |
1168 | PSOutputDev::PSOutputDev(FoFiOutputFunc outputFuncA, void *outputStreamA, char *psTitleA, PDFDoc *docA, const std::vector<int> &pagesA, PSOutMode modeA, int paperWidthA, int paperHeightA, bool noCropA, bool duplexA, int imgLLXA, |
1169 | int imgLLYA, int imgURXA, int imgURYA, PSForceRasterize forceRasterizeA, bool manualCtrlA, PSOutCustomCodeCbk customCodeCbkA, void *customCodeCbkDataA, PSLevel levelA) |
1170 | { |
1171 | underlayCbk = nullptr; |
1172 | underlayCbkData = nullptr; |
1173 | overlayCbk = nullptr; |
1174 | overlayCbkData = nullptr; |
1175 | customCodeCbk = customCodeCbkA; |
1176 | customCodeCbkData = customCodeCbkDataA; |
1177 | |
1178 | t1FontNames = nullptr; |
1179 | font8Info = nullptr; |
1180 | font16Enc = nullptr; |
1181 | imgIDs = nullptr; |
1182 | formIDs = nullptr; |
1183 | embFontList = nullptr; |
1184 | customColors = nullptr; |
1185 | haveTextClip = false; |
1186 | t3String = nullptr; |
1187 | forceRasterize = forceRasterizeA; |
1188 | psTitle = nullptr; |
1189 | |
1190 | init(outputFuncA, outputStreamA, fileTypeA: psGeneric, psTitleA, doc: docA, pages: pagesA, modeA, imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA, paperWidthA, paperHeightA, noCropA, duplexA, levelA); |
1191 | } |
1192 | |
1193 | struct StandardMedia |
1194 | { |
1195 | const char *name; |
1196 | int width; |
1197 | int height; |
1198 | }; |
1199 | |
1200 | static const StandardMedia standardMedia[] = { { .name: "A0" , .width: 2384, .height: 3371 }, { .name: "A1" , .width: 1685, .height: 2384 }, { .name: "A2" , .width: 1190, .height: 1684 }, { .name: "A3" , .width: 842, .height: 1190 }, { .name: "A4" , .width: 595, .height: 842 }, { .name: "A5" , .width: 420, .height: 595 }, |
1201 | { .name: "B4" , .width: 729, .height: 1032 }, { .name: "B5" , .width: 516, .height: 729 }, { .name: "Letter" , .width: 612, .height: 792 }, { .name: "Tabloid" , .width: 792, .height: 1224 }, { .name: "Ledger" , .width: 1224, .height: 792 }, { .name: "Legal" , .width: 612, .height: 1008 }, |
1202 | { .name: "Statement" , .width: 396, .height: 612 }, { .name: "Executive" , .width: 540, .height: 720 }, { .name: "Folio" , .width: 612, .height: 936 }, { .name: "Quarto" , .width: 610, .height: 780 }, { .name: "10x14" , .width: 720, .height: 1008 }, { .name: nullptr, .width: 0, .height: 0 } }; |
1203 | |
1204 | /* PLRM specifies a tolerance of 5 points when matching page sizes */ |
1205 | static bool pageDimensionEqual(int a, int b) |
1206 | { |
1207 | int aux; |
1208 | if (unlikely(checkedSubtraction(a, b, &aux))) { |
1209 | return false; |
1210 | } |
1211 | return (abs(x: aux) < 5); |
1212 | } |
1213 | |
1214 | // Shared initialization of PSOutputDev members. |
1215 | // Store the values but do not process them so the function that |
1216 | // created the PSOutputDev can use the various setters to change defaults. |
1217 | |
1218 | void PSOutputDev::init(FoFiOutputFunc outputFuncA, void *outputStreamA, PSFileType fileTypeA, char *psTitleA, PDFDoc *docA, const std::vector<int> &pagesA, PSOutMode modeA, int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, |
1219 | bool manualCtrlA, int paperWidthA, int paperHeightA, bool noCropA, bool duplexA, PSLevel levelA) |
1220 | { |
1221 | |
1222 | if (pagesA.empty()) { |
1223 | ok = false; |
1224 | return; |
1225 | } |
1226 | |
1227 | // initialize |
1228 | postInitDone = false; |
1229 | embedType1 = true; |
1230 | embedTrueType = true; |
1231 | embedCIDPostScript = true; |
1232 | embedCIDTrueType = true; |
1233 | fontPassthrough = false; |
1234 | optimizeColorSpace = false; |
1235 | passLevel1CustomColor = false; |
1236 | preloadImagesForms = false; |
1237 | generateOPI = false; |
1238 | useASCIIHex = false; |
1239 | useBinary = false; |
1240 | enableLZW = true; |
1241 | enableFlate = true; |
1242 | rasterResolution = 300; |
1243 | uncompressPreloadedImages = false; |
1244 | psCenter = true; |
1245 | rasterAntialias = false; |
1246 | displayText = true; |
1247 | ok = true; |
1248 | outputFunc = outputFuncA; |
1249 | outputStream = outputStreamA; |
1250 | fileType = fileTypeA; |
1251 | psTitle = (psTitleA ? strdup(s: psTitleA) : nullptr); |
1252 | doc = docA; |
1253 | level = levelA; |
1254 | pages = pagesA; |
1255 | mode = modeA; |
1256 | paperWidth = paperWidthA; |
1257 | paperHeight = paperHeightA; |
1258 | noCrop = noCropA; |
1259 | duplex = duplexA; |
1260 | imgLLX = imgLLXA; |
1261 | imgLLY = imgLLYA; |
1262 | imgURX = imgURXA; |
1263 | imgURY = imgURYA; |
1264 | manualCtrl = manualCtrlA; |
1265 | |
1266 | xref = nullptr; |
1267 | |
1268 | processColors = 0; |
1269 | inType3Char = false; |
1270 | inUncoloredPattern = false; |
1271 | t3FillColorOnly = false; |
1272 | |
1273 | #ifdef OPI_SUPPORT |
1274 | // initialize OPI nesting levels |
1275 | opi13Nest = 0; |
1276 | opi20Nest = 0; |
1277 | #endif |
1278 | |
1279 | tx0 = ty0 = -1; |
1280 | xScale0 = yScale0 = 0; |
1281 | rotate0 = -1; |
1282 | clipLLX0 = clipLLY0 = 0; |
1283 | clipURX0 = clipURY0 = -1; |
1284 | |
1285 | processColorFormatSpecified = false; |
1286 | |
1287 | // initialize sequential page number |
1288 | seqPage = 1; |
1289 | } |
1290 | |
1291 | // Complete the initialization after the function that created the PSOutputDev |
1292 | // has had a chance to modify default values with the various setters. |
1293 | |
1294 | void PSOutputDev::postInit() |
1295 | { |
1296 | Catalog *catalog; |
1297 | PDFRectangle *box; |
1298 | int w, h, i; |
1299 | |
1300 | if (postInitDone || !ok) { |
1301 | return; |
1302 | } |
1303 | |
1304 | postInitDone = true; |
1305 | |
1306 | xref = doc->getXRef(); |
1307 | catalog = doc->getCatalog(); |
1308 | |
1309 | if (paperWidth < 0 || paperHeight < 0) { |
1310 | paperMatch = true; |
1311 | } else { |
1312 | paperMatch = false; |
1313 | } |
1314 | |
1315 | paperSizes.clear(); |
1316 | for (const int pg : pages) { |
1317 | Page *page = catalog->getPage(i: pg); |
1318 | if (page == nullptr) { |
1319 | paperMatch = false; |
1320 | } |
1321 | if (!paperMatch) { |
1322 | w = paperWidth; |
1323 | h = paperHeight; |
1324 | if (w < 0 || h < 0) { |
1325 | // Unable to obtain a paper size from the document and no page size |
1326 | // specified. In this case use A4 as the page size to ensure the PS output is |
1327 | // valid. This will only occur if the PDF is very broken. |
1328 | w = 595; |
1329 | h = 842; |
1330 | } |
1331 | } else if (noCrop) { |
1332 | w = (int)ceil(x: page->getMediaWidth()); |
1333 | h = (int)ceil(x: page->getMediaHeight()); |
1334 | } else { |
1335 | w = (int)ceil(x: page->getCropWidth()); |
1336 | h = (int)ceil(x: page->getCropHeight()); |
1337 | } |
1338 | if (paperMatch) { |
1339 | const int = page->getRotate(); |
1340 | if (pageRotate == 90 || pageRotate == 270) { |
1341 | std::swap(a&: w, b&: h); |
1342 | } |
1343 | } |
1344 | if (w > paperWidth) { |
1345 | paperWidth = w; |
1346 | } |
1347 | if (h > paperHeight) { |
1348 | paperHeight = h; |
1349 | } |
1350 | for (i = 0; i < (int)paperSizes.size(); ++i) { |
1351 | const PSOutPaperSize &size = paperSizes[i]; |
1352 | if (pageDimensionEqual(a: w, b: size.w) && pageDimensionEqual(a: h, b: size.h)) { |
1353 | break; |
1354 | } |
1355 | } |
1356 | if (i == (int)paperSizes.size()) { |
1357 | const StandardMedia *media = standardMedia; |
1358 | std::string name; |
1359 | while (media->name) { |
1360 | if (pageDimensionEqual(a: w, b: media->width) && pageDimensionEqual(a: h, b: media->height)) { |
1361 | name = std::string(media->name); |
1362 | w = media->width; |
1363 | h = media->height; |
1364 | break; |
1365 | } |
1366 | media++; |
1367 | } |
1368 | if (name.empty()) { |
1369 | name = GooString::format(fmt: "{0:d}x{1:d}mm" , int(w * 25.4 / 72), int(h * 25.4 / 72))->toStr(); |
1370 | } |
1371 | paperSizes.emplace_back(args: std::move(name), args&: w, args&: h); |
1372 | } |
1373 | pagePaperSize.insert(x: std::pair<int, int>(pg, i)); |
1374 | if (!paperMatch) { |
1375 | break; // we only need one entry when all pages are the same size |
1376 | } |
1377 | } |
1378 | if (imgLLX == 0 && imgURX == 0 && imgLLY == 0 && imgURY == 0) { |
1379 | imgLLX = imgLLY = 0; |
1380 | imgURX = paperWidth; |
1381 | imgURY = paperHeight; |
1382 | } |
1383 | std::vector<int> pageList; |
1384 | if (mode == psModeForm) { |
1385 | pageList.push_back(x: pages[0]); |
1386 | } else { |
1387 | pageList = pages; |
1388 | } |
1389 | |
1390 | // initialize fontIDs, fontFileIDs, and fontFileNames lists |
1391 | fontIDs.reserve(n: 64); |
1392 | fontIDs.resize(new_size: 0); |
1393 | for (i = 0; i < 14; ++i) { |
1394 | fontNames.emplace(args: psBase14SubstFonts[i].psName); |
1395 | } |
1396 | t1FontNameSize = 64; |
1397 | t1FontNameLen = 0; |
1398 | t1FontNames = (PST1FontName *)gmallocn(count: t1FontNameSize, size: sizeof(PST1FontName)); |
1399 | font8InfoLen = 0; |
1400 | font8InfoSize = 0; |
1401 | font16EncLen = 0; |
1402 | font16EncSize = 0; |
1403 | imgIDLen = 0; |
1404 | imgIDSize = 0; |
1405 | formIDLen = 0; |
1406 | formIDSize = 0; |
1407 | |
1408 | numSaves = 0; |
1409 | numTilingPatterns = 0; |
1410 | nextFunc = 0; |
1411 | |
1412 | // set some default process color format if none is set |
1413 | if (!processColorFormatSpecified) { |
1414 | if (level == psLevel1) { |
1415 | processColorFormat = splashModeMono8; |
1416 | } else if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep || overprintPreview) { |
1417 | processColorFormat = splashModeCMYK8; |
1418 | } |
1419 | #ifdef USE_CMS |
1420 | else if (getDisplayProfile()) { |
1421 | auto processcolorspace = cmsGetColorSpace(getDisplayProfile().get()); |
1422 | if (processcolorspace == cmsSigCmykData) { |
1423 | processColorFormat = splashModeCMYK8; |
1424 | } else if (processcolorspace == cmsSigGrayData) { |
1425 | processColorFormat = splashModeMono8; |
1426 | } else { |
1427 | processColorFormat = splashModeRGB8; |
1428 | } |
1429 | } |
1430 | #endif |
1431 | else { |
1432 | processColorFormat = splashModeRGB8; |
1433 | } |
1434 | } |
1435 | |
1436 | // check for consistency between the processColorFormat the LanguageLevel and other settings |
1437 | if (level == psLevel1 && processColorFormat != splashModeMono8) { |
1438 | error(category: errConfig, pos: -1, |
1439 | msg: "Conflicting settings between LanguageLevel=psLevel1 and processColorFormat." |
1440 | " Resetting processColorFormat to MONO8." ); |
1441 | processColorFormat = splashModeMono8; |
1442 | } else if ((level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep || overprintPreview) && processColorFormat != splashModeCMYK8) { |
1443 | error(category: errConfig, pos: -1, |
1444 | msg: "Conflicting settings between LanguageLevel and/or overprint simulation, and processColorFormat." |
1445 | " Resetting processColorFormat to CMYK8." ); |
1446 | processColorFormat = splashModeCMYK8; |
1447 | } |
1448 | #ifdef USE_CMS |
1449 | if (getDisplayProfile()) { |
1450 | auto processcolorspace = cmsGetColorSpace(getDisplayProfile().get()); |
1451 | if (processColorFormat == splashModeCMYK8) { |
1452 | if (processcolorspace != cmsSigCmykData) { |
1453 | error(errConfig, -1, "Mismatch between processColorFormat=CMYK8 and ICC profile color format." ); |
1454 | } |
1455 | } else if (processColorFormat == splashModeMono8) { |
1456 | if (processcolorspace != cmsSigGrayData) { |
1457 | error(errConfig, -1, "Mismatch between processColorFormat=MONO8 and ICC profile color format." ); |
1458 | } |
1459 | } else if (processColorFormat == splashModeRGB8) { |
1460 | if (processcolorspace != cmsSigRgbData) { |
1461 | error(errConfig, -1, "Mismatch between processColorFormat=RGB8 and ICC profile color format." ); |
1462 | } |
1463 | } |
1464 | } |
1465 | #endif |
1466 | |
1467 | // initialize embedded font resource comment list |
1468 | embFontList = new GooString(); |
1469 | |
1470 | if (!manualCtrl) { |
1471 | Page *page; |
1472 | // this check is needed in case the document has zero pages |
1473 | if ((page = doc->getPage(page: pageList[0]))) { |
1474 | writeHeader(nPages: pageList.size(), mediaBox: page->getMediaBox(), cropBox: page->getCropBox(), pageRotate: page->getRotate(), title: psTitle); |
1475 | } else { |
1476 | error(category: errSyntaxError, pos: -1, msg: "Invalid page {0:d}" , pageList[0]); |
1477 | box = new PDFRectangle(0, 0, 1, 1); |
1478 | writeHeader(nPages: pageList.size(), mediaBox: box, cropBox: box, pageRotate: 0, title: psTitle); |
1479 | delete box; |
1480 | } |
1481 | if (mode != psModeForm) { |
1482 | writePS(s: "%%BeginProlog\n" ); |
1483 | } |
1484 | writeXpdfProcset(); |
1485 | if (mode != psModeForm) { |
1486 | writePS(s: "%%EndProlog\n" ); |
1487 | writePS(s: "%%BeginSetup\n" ); |
1488 | } |
1489 | writeDocSetup(catalog, pageList, duplexA: duplex); |
1490 | if (mode != psModeForm) { |
1491 | writePS(s: "%%EndSetup\n" ); |
1492 | } |
1493 | } |
1494 | } |
1495 | |
1496 | PSOutputDev::~PSOutputDev() |
1497 | { |
1498 | PSOutCustomColor *cc; |
1499 | int i; |
1500 | |
1501 | if (ok) { |
1502 | if (!postInitDone) { |
1503 | postInit(); |
1504 | } |
1505 | if (!manualCtrl) { |
1506 | writePS(s: "%%Trailer\n" ); |
1507 | writeTrailer(); |
1508 | if (mode != psModeForm) { |
1509 | writePS(s: "%%EOF\n" ); |
1510 | } |
1511 | } |
1512 | if (fileType == psFile) { |
1513 | fclose(stream: (FILE *)outputStream); |
1514 | } |
1515 | #ifdef HAVE_POPEN |
1516 | else if (fileType == psPipe) { |
1517 | pclose(stream: (FILE *)outputStream); |
1518 | # ifndef _WIN32 |
1519 | signal(SIGPIPE, handler: (SignalFunc)SIG_DFL); |
1520 | # endif |
1521 | } |
1522 | #endif |
1523 | } |
1524 | if (embFontList) { |
1525 | delete embFontList; |
1526 | } |
1527 | if (t1FontNames) { |
1528 | for (i = 0; i < t1FontNameLen; ++i) { |
1529 | delete t1FontNames[i].psName; |
1530 | } |
1531 | gfree(p: t1FontNames); |
1532 | } |
1533 | if (font8Info) { |
1534 | for (i = 0; i < font8InfoLen; ++i) { |
1535 | gfree(p: font8Info[i].codeToGID); |
1536 | } |
1537 | gfree(p: font8Info); |
1538 | } |
1539 | if (font16Enc) { |
1540 | for (i = 0; i < font16EncLen; ++i) { |
1541 | if (font16Enc[i].enc) { |
1542 | delete font16Enc[i].enc; |
1543 | } |
1544 | } |
1545 | gfree(p: font16Enc); |
1546 | } |
1547 | gfree(p: imgIDs); |
1548 | gfree(p: formIDs); |
1549 | while (customColors) { |
1550 | cc = customColors; |
1551 | customColors = cc->next; |
1552 | delete cc; |
1553 | } |
1554 | gfree(p: psTitle); |
1555 | delete t3String; |
1556 | } |
1557 | |
1558 | void PSOutputDev::(int nPages, const PDFRectangle *mediaBox, const PDFRectangle *cropBox, int , const char *title) |
1559 | { |
1560 | double x1, y1, x2, y2; |
1561 | |
1562 | switch (mode) { |
1563 | case psModePS: |
1564 | writePS(s: "%!PS-Adobe-3.0\n" ); |
1565 | break; |
1566 | case psModeEPS: |
1567 | writePS(s: "%!PS-Adobe-3.0 EPSF-3.0\n" ); |
1568 | break; |
1569 | case psModeForm: |
1570 | writePS(s: "%!PS-Adobe-3.0 Resource-Form\n" ); |
1571 | break; |
1572 | } |
1573 | Object info = xref->getDocInfo(); |
1574 | std::unique_ptr<GooString> creator = GooString::format(fmt: "poppler pdftops version: {0:s} (http://poppler.freedesktop.org)" , PACKAGE_VERSION); |
1575 | if (info.isDict()) { |
1576 | Object obj1 = info.dictLookup(key: "Creator" ); |
1577 | if (obj1.isString()) { |
1578 | const GooString *pdfCreator = obj1.getString(); |
1579 | if (pdfCreator && !pdfCreator->toStr().empty()) { |
1580 | creator->append(str: ". PDF Creator: " ); |
1581 | if (hasUnicodeByteOrderMark(s: pdfCreator->toStr())) { |
1582 | creator->append(str: TextStringToUtf8(textStr: pdfCreator->toStr())); |
1583 | } else { |
1584 | creator->append(str: pdfCreator); |
1585 | } |
1586 | } |
1587 | } |
1588 | } |
1589 | writePS(s: "%%Creator: " ); |
1590 | writePSTextLine(s: creator.get()); |
1591 | if (title) { |
1592 | char *sanitizedTitle = strdup(s: title); |
1593 | for (size_t i = 0; i < strlen(s: sanitizedTitle); ++i) { |
1594 | if (sanitizedTitle[i] == '\n' || sanitizedTitle[i] == '\r') { |
1595 | sanitizedTitle[i] = ' '; |
1596 | } |
1597 | } |
1598 | writePSFmt(fmt: "%%Title: {0:s}\n" , sanitizedTitle); |
1599 | free(ptr: sanitizedTitle); |
1600 | } |
1601 | writePSFmt(fmt: "%%LanguageLevel: {0:d}\n" , (level == psLevel1 || level == psLevel1Sep) ? 1 : (level == psLevel2 || level == psLevel2Sep) ? 2 : 3); |
1602 | if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) { |
1603 | writePS(s: "%%DocumentProcessColors: (atend)\n" ); |
1604 | writePS(s: "%%DocumentCustomColors: (atend)\n" ); |
1605 | } |
1606 | writePS(s: "%%DocumentSuppliedResources: (atend)\n" ); |
1607 | if ((level == psLevel1 || level == psLevel1Sep) && useBinary) { |
1608 | writePS(s: "%%DocumentData: Binary\n" ); |
1609 | } |
1610 | |
1611 | switch (mode) { |
1612 | case psModePS: |
1613 | for (std::size_t i = 0; i < paperSizes.size(); ++i) { |
1614 | const PSOutPaperSize &size = paperSizes[i]; |
1615 | writePSFmt(fmt: "%%{0:s} {1:s} {2:d} {3:d} 0 () ()\n" , i == 0 ? "DocumentMedia:" : "+" , size.name.c_str(), size.w, size.h); |
1616 | } |
1617 | writePSFmt(fmt: "%%BoundingBox: 0 0 {0:d} {1:d}\n" , paperWidth, paperHeight); |
1618 | writePSFmt(fmt: "%%Pages: {0:d}\n" , nPages); |
1619 | writePS(s: "%%EndComments\n" ); |
1620 | if (!paperMatch) { |
1621 | writePS(s: "%%BeginDefaults\n" ); |
1622 | writePSFmt(fmt: "%%PageMedia: {0:s}\n" , paperSizes[0].name.c_str()); |
1623 | writePS(s: "%%EndDefaults\n" ); |
1624 | } |
1625 | break; |
1626 | case psModeEPS: |
1627 | epsX1 = cropBox->x1; |
1628 | epsY1 = cropBox->y1; |
1629 | epsX2 = cropBox->x2; |
1630 | epsY2 = cropBox->y2; |
1631 | if (pageRotate == 0 || pageRotate == 180) { |
1632 | x1 = epsX1; |
1633 | y1 = epsY1; |
1634 | x2 = epsX2; |
1635 | y2 = epsY2; |
1636 | } else { // pageRotate == 90 || pageRotate == 270 |
1637 | x1 = 0; |
1638 | y1 = 0; |
1639 | x2 = epsY2 - epsY1; |
1640 | y2 = epsX2 - epsX1; |
1641 | } |
1642 | writePSFmt(fmt: "%%BoundingBox: {0:d} {1:d} {2:d} {3:d}\n" , (int)floor(x: x1), (int)floor(x: y1), (int)ceil(x: x2), (int)ceil(x: y2)); |
1643 | writePSFmt(fmt: "%%HiResBoundingBox: {0:.6g} {1:.6g} {2:.6g} {3:.6g}\n" , x1, y1, x2, y2); |
1644 | writePS(s: "%%DocumentSuppliedResources: (atend)\n" ); |
1645 | writePS(s: "%%EndComments\n" ); |
1646 | break; |
1647 | case psModeForm: |
1648 | writePS(s: "%%EndComments\n" ); |
1649 | writePS(s: "32 dict dup begin\n" ); |
1650 | writePSFmt(fmt: "/BBox [{0:d} {1:d} {2:d} {3:d}] def\n" , (int)floor(x: mediaBox->x1), (int)floor(x: mediaBox->y1), (int)ceil(x: mediaBox->x2), (int)ceil(x: mediaBox->y2)); |
1651 | writePS(s: "/FormType 1 def\n" ); |
1652 | writePS(s: "/Matrix [1 0 0 1 0 0] def\n" ); |
1653 | break; |
1654 | } |
1655 | } |
1656 | |
1657 | void PSOutputDev::writeXpdfProcset() |
1658 | { |
1659 | bool lev1, lev2, lev3, sep, nonSep; |
1660 | const char **p; |
1661 | const char *q; |
1662 | |
1663 | writePSFmt(fmt: "%%BeginResource: procset xpdf {0:s} 0\n" , "3.00" ); |
1664 | writePSFmt(fmt: "%%Copyright: {0:s}\n" , xpdfCopyright); |
1665 | lev1 = lev2 = lev3 = sep = nonSep = true; |
1666 | for (p = prolog; *p; ++p) { |
1667 | if ((*p)[0] == '~') { |
1668 | lev1 = lev2 = lev3 = sep = nonSep = false; |
1669 | for (q = *p + 1; *q; ++q) { |
1670 | switch (*q) { |
1671 | case '1': |
1672 | lev1 = true; |
1673 | break; |
1674 | case '2': |
1675 | lev2 = true; |
1676 | break; |
1677 | case '3': |
1678 | lev3 = true; |
1679 | break; |
1680 | case 's': |
1681 | sep = true; |
1682 | break; |
1683 | case 'n': |
1684 | nonSep = true; |
1685 | break; |
1686 | } |
1687 | } |
1688 | } else if ((level == psLevel1 && lev1 && nonSep) || (level == psLevel1Sep && lev1 && sep) || (level == psLevel1Sep && lev2 && sep && getPassLevel1CustomColor()) || (level == psLevel2 && lev2 && nonSep) |
1689 | || (level == psLevel2Sep && lev2 && sep) || (level == psLevel3 && lev3 && nonSep) || (level == psLevel3Sep && lev3 && sep)) { |
1690 | writePSFmt(fmt: "{0:s}\n" , *p); |
1691 | } |
1692 | } |
1693 | writePS(s: "%%EndResource\n" ); |
1694 | |
1695 | if (level >= psLevel3) { |
1696 | for (p = cmapProlog; *p; ++p) { |
1697 | writePSFmt(fmt: "{0:s}\n" , *p); |
1698 | } |
1699 | } |
1700 | } |
1701 | |
1702 | void PSOutputDev::writeDocSetup(Catalog *catalog, const std::vector<int> &pageList, bool duplexA) |
1703 | { |
1704 | Page *page; |
1705 | Dict *resDict; |
1706 | Annots *annots; |
1707 | Object *acroForm; |
1708 | GooString *s; |
1709 | |
1710 | if (mode == psModeForm) { |
1711 | // swap the form and xpdf dicts |
1712 | writePS(s: "xpdf end begin dup begin\n" ); |
1713 | } else { |
1714 | writePS(s: "xpdf begin\n" ); |
1715 | } |
1716 | for (const int pg : pageList) { |
1717 | page = doc->getPage(page: pg); |
1718 | if (!page) { |
1719 | error(category: errSyntaxError, pos: -1, msg: "Failed writing resources for page {0:d}" , pg); |
1720 | continue; |
1721 | } |
1722 | if ((resDict = page->getResourceDict())) { |
1723 | setupResources(resDict); |
1724 | } |
1725 | annots = page->getAnnots(); |
1726 | for (Annot *annot : annots->getAnnots()) { |
1727 | Object obj1 = annot->getAppearanceResDict(); |
1728 | if (obj1.isDict()) { |
1729 | setupResources(obj1.getDict()); |
1730 | } |
1731 | } |
1732 | } |
1733 | if ((acroForm = catalog->getAcroForm()) && acroForm->isDict()) { |
1734 | Object obj1 = acroForm->dictLookup(key: "DR" ); |
1735 | if (obj1.isDict()) { |
1736 | setupResources(obj1.getDict()); |
1737 | } |
1738 | obj1 = acroForm->dictLookup(key: "Fields" ); |
1739 | if (obj1.isArray()) { |
1740 | for (int i = 0; i < obj1.arrayGetLength(); ++i) { |
1741 | Object obj2 = obj1.arrayGet(i); |
1742 | if (obj2.isDict()) { |
1743 | Object obj3 = obj2.dictLookup(key: "DR" ); |
1744 | if (obj3.isDict()) { |
1745 | setupResources(obj3.getDict()); |
1746 | } |
1747 | } |
1748 | } |
1749 | } |
1750 | } |
1751 | if (mode != psModeForm) { |
1752 | if (mode != psModeEPS && !manualCtrl) { |
1753 | writePSFmt(fmt: "{0:s} pdfSetup\n" , duplexA ? "true" : "false" ); |
1754 | if (!paperMatch) { |
1755 | writePSFmt(fmt: "{0:d} {1:d} pdfSetupPaper\n" , paperWidth, paperHeight); |
1756 | } |
1757 | } |
1758 | #ifdef OPI_SUPPORT |
1759 | if (generateOPI) { |
1760 | writePS(s: "/opiMatrix matrix currentmatrix def\n" ); |
1761 | } |
1762 | #endif |
1763 | } |
1764 | if (customCodeCbk) { |
1765 | if ((s = (*customCodeCbk)(this, psOutCustomDocSetup, 0, customCodeCbkData))) { |
1766 | writePS(s: s->c_str()); |
1767 | delete s; |
1768 | } |
1769 | } |
1770 | } |
1771 | |
1772 | void PSOutputDev::writePageTrailer() |
1773 | { |
1774 | if (mode != psModeForm) { |
1775 | writePS(s: "pdfEndPage\n" ); |
1776 | } |
1777 | } |
1778 | |
1779 | void PSOutputDev::writeTrailer() |
1780 | { |
1781 | PSOutCustomColor *cc; |
1782 | |
1783 | if (mode == psModeForm) { |
1784 | writePS(s: "/Foo exch /Form defineresource pop\n" ); |
1785 | } else { |
1786 | writePS(s: "end\n" ); |
1787 | writePS(s: "%%DocumentSuppliedResources:\n" ); |
1788 | writePS(s: embFontList->c_str()); |
1789 | if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) { |
1790 | writePS(s: "%%DocumentProcessColors:" ); |
1791 | if (processColors & psProcessCyan) { |
1792 | writePS(s: " Cyan" ); |
1793 | } |
1794 | if (processColors & psProcessMagenta) { |
1795 | writePS(s: " Magenta" ); |
1796 | } |
1797 | if (processColors & psProcessYellow) { |
1798 | writePS(s: " Yellow" ); |
1799 | } |
1800 | if (processColors & psProcessBlack) { |
1801 | writePS(s: " Black" ); |
1802 | } |
1803 | writePS(s: "\n" ); |
1804 | writePS(s: "%%DocumentCustomColors:" ); |
1805 | for (cc = customColors; cc; cc = cc->next) { |
1806 | writePS(s: " " ); |
1807 | writePSString(s: cc->name->toStr()); |
1808 | } |
1809 | writePS(s: "\n" ); |
1810 | writePS(s: "%%CMYKCustomColor:\n" ); |
1811 | for (cc = customColors; cc; cc = cc->next) { |
1812 | writePSFmt(fmt: "%%+ {0:.4g} {1:.4g} {2:.4g} {3:.4g} " , cc->c, cc->m, cc->y, cc->k); |
1813 | writePSString(s: cc->name->toStr()); |
1814 | writePS(s: "\n" ); |
1815 | } |
1816 | } |
1817 | } |
1818 | } |
1819 | |
1820 | void PSOutputDev::setupResources(Dict *resDict) |
1821 | { |
1822 | bool skip; |
1823 | |
1824 | setupFonts(resDict); |
1825 | setupImages(resDict); |
1826 | setupForms(resDict); |
1827 | |
1828 | //----- recursively scan XObjects |
1829 | Object xObjDict = resDict->lookup(key: "XObject" ); |
1830 | if (xObjDict.isDict()) { |
1831 | for (int i = 0; i < xObjDict.dictGetLength(); ++i) { |
1832 | |
1833 | // avoid infinite recursion on XObjects |
1834 | skip = false; |
1835 | const Object &xObjRef = xObjDict.dictGetValNF(i); |
1836 | if (xObjRef.isRef()) { |
1837 | Ref ref0 = xObjRef.getRef(); |
1838 | if (resourceIDs.find(x: ref0.num) != resourceIDs.end()) { |
1839 | skip = true; |
1840 | } else { |
1841 | resourceIDs.insert(x: ref0.num); |
1842 | } |
1843 | } |
1844 | if (!skip) { |
1845 | |
1846 | // process the XObject's resource dictionary |
1847 | Object xObj = xObjDict.dictGetVal(i); |
1848 | if (xObj.isStream()) { |
1849 | Ref resObjRef; |
1850 | Object resObj = xObj.streamGetDict()->lookup(key: "Resources" , returnRef: &resObjRef); |
1851 | if (resObj.isDict()) { |
1852 | if (resObjRef != Ref::INVALID()) { |
1853 | const int numObj = resObjRef.num; |
1854 | if (resourceIDs.find(x: numObj) != resourceIDs.end()) { |
1855 | error(category: errSyntaxError, pos: -1, msg: "loop in Resources (numObj: {0:d})" , numObj); |
1856 | continue; |
1857 | } |
1858 | resourceIDs.insert(x: numObj); |
1859 | } |
1860 | setupResources(resObj.getDict()); |
1861 | } |
1862 | } |
1863 | } |
1864 | } |
1865 | } |
1866 | |
1867 | //----- recursively scan Patterns |
1868 | Object patDict = resDict->lookup(key: "Pattern" ); |
1869 | if (patDict.isDict()) { |
1870 | inType3Char = true; |
1871 | for (int i = 0; i < patDict.dictGetLength(); ++i) { |
1872 | |
1873 | // avoid infinite recursion on Patterns |
1874 | skip = false; |
1875 | const Object &patRef = patDict.dictGetValNF(i); |
1876 | if (patRef.isRef()) { |
1877 | Ref ref0 = patRef.getRef(); |
1878 | if (resourceIDs.find(x: ref0.num) != resourceIDs.end()) { |
1879 | skip = true; |
1880 | } else { |
1881 | resourceIDs.insert(x: ref0.num); |
1882 | } |
1883 | } |
1884 | if (!skip) { |
1885 | |
1886 | // process the Pattern's resource dictionary |
1887 | Object pat = patDict.dictGetVal(i); |
1888 | if (pat.isStream()) { |
1889 | Ref resObjRef; |
1890 | Object resObj = pat.streamGetDict()->lookup(key: "Resources" , returnRef: &resObjRef); |
1891 | if (resObj.isDict()) { |
1892 | if (resObjRef != Ref::INVALID() && !resourceIDs.insert(x: resObjRef.num).second) { |
1893 | error(category: errSyntaxWarning, pos: -1, msg: "PSOutputDev::setupResources: Circular resources found." ); |
1894 | continue; |
1895 | } |
1896 | setupResources(resObj.getDict()); |
1897 | } |
1898 | } |
1899 | } |
1900 | } |
1901 | inType3Char = false; |
1902 | } |
1903 | } |
1904 | |
1905 | void PSOutputDev::setupFonts(Dict *resDict) |
1906 | { |
1907 | Ref r; |
1908 | GfxFontDict *gfxFontDict; |
1909 | int i; |
1910 | |
1911 | gfxFontDict = nullptr; |
1912 | const Object &obj1 = resDict->lookupNF(key: "Font" ); |
1913 | if (obj1.isRef()) { |
1914 | Object obj2 = obj1.fetch(xref); |
1915 | if (obj2.isDict()) { |
1916 | r = obj1.getRef(); |
1917 | gfxFontDict = new GfxFontDict(xref, &r, obj2.getDict()); |
1918 | } |
1919 | } else if (obj1.isDict()) { |
1920 | gfxFontDict = new GfxFontDict(xref, nullptr, obj1.getDict()); |
1921 | } |
1922 | if (gfxFontDict) { |
1923 | for (i = 0; i < gfxFontDict->getNumFonts(); ++i) { |
1924 | if (const std::shared_ptr<GfxFont> &font = gfxFontDict->getFont(i)) { |
1925 | setupFont(font: font.get(), parentResDict: resDict); |
1926 | } |
1927 | } |
1928 | delete gfxFontDict; |
1929 | } |
1930 | } |
1931 | |
1932 | void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) |
1933 | { |
1934 | GooString *psName; |
1935 | char buf[16]; |
1936 | bool subst; |
1937 | const char *charName; |
1938 | double xs, ys; |
1939 | int code; |
1940 | double w1, w2; |
1941 | int i, j; |
1942 | |
1943 | // check if font is already set up |
1944 | for (Ref fontID : fontIDs) { |
1945 | if (fontID == *font->getID()) { |
1946 | return; |
1947 | } |
1948 | } |
1949 | |
1950 | fontIDs.push_back(x: *font->getID()); |
1951 | |
1952 | psName = nullptr; |
1953 | xs = ys = 1; |
1954 | subst = false; |
1955 | |
1956 | if (font->getType() == fontType3) { |
1957 | psName = GooString::format(fmt: "T3_{0:d}_{1:d}" , font->getID()->num, font->getID()->gen).release(); |
1958 | setupType3Font(font, psName, parentResDict); |
1959 | } else { |
1960 | std::optional<GfxFontLoc> fontLoc = font->locateFont(xref, ps: this); |
1961 | if (fontLoc) { |
1962 | switch (fontLoc->locType) { |
1963 | case gfxFontLocEmbedded: |
1964 | switch (fontLoc->fontType) { |
1965 | case fontType1: |
1966 | // this assumes that the PS font name matches the PDF font name |
1967 | psName = font->getEmbeddedFontName() ? font->getEmbeddedFontName()->copy() : new GooString(); |
1968 | setupEmbeddedType1Font(id: &fontLoc->embFontID, psName); |
1969 | break; |
1970 | case fontType1C: |
1971 | psName = makePSFontName(font, id: &fontLoc->embFontID); |
1972 | setupEmbeddedType1CFont(font, id: &fontLoc->embFontID, psName); |
1973 | break; |
1974 | case fontType1COT: |
1975 | psName = makePSFontName(font, id: &fontLoc->embFontID); |
1976 | setupEmbeddedOpenTypeT1CFont(font, id: &fontLoc->embFontID, psName); |
1977 | break; |
1978 | case fontTrueType: |
1979 | case fontTrueTypeOT: |
1980 | psName = makePSFontName(font, id: font->getID()); |
1981 | setupEmbeddedTrueTypeFont(font, id: &fontLoc->embFontID, psName); |
1982 | break; |
1983 | case fontCIDType0C: |
1984 | psName = makePSFontName(font, id: &fontLoc->embFontID); |
1985 | setupEmbeddedCIDType0Font(font, id: &fontLoc->embFontID, psName); |
1986 | break; |
1987 | case fontCIDType2: |
1988 | case fontCIDType2OT: |
1989 | psName = makePSFontName(font, id: font->getID()); |
1990 | //~ should check to see if font actually uses vertical mode |
1991 | setupEmbeddedCIDTrueTypeFont(font, id: &fontLoc->embFontID, psName, needVerticalMetrics: true); |
1992 | break; |
1993 | case fontCIDType0COT: |
1994 | psName = makePSFontName(font, id: &fontLoc->embFontID); |
1995 | setupEmbeddedOpenTypeCFFFont(font, id: &fontLoc->embFontID, psName); |
1996 | break; |
1997 | default: |
1998 | break; |
1999 | } |
2000 | break; |
2001 | case gfxFontLocExternal: |
2002 | //~ add cases for external 16-bit fonts |
2003 | switch (fontLoc->fontType) { |
2004 | case fontType1: |
2005 | if (font->getEmbeddedFontName()) { |
2006 | // this assumes that the PS font name matches the PDF font name |
2007 | psName = font->getEmbeddedFontName()->copy(); |
2008 | } else { |
2009 | //~ this won't work -- the PS font name won't match |
2010 | psName = makePSFontName(font, id: font->getID()); |
2011 | } |
2012 | setupExternalType1Font(fileName: fontLoc->pathAsGooString(), psName); |
2013 | break; |
2014 | case fontTrueType: |
2015 | case fontTrueTypeOT: |
2016 | psName = makePSFontName(font, id: font->getID()); |
2017 | setupExternalTrueTypeFont(font, fileName: fontLoc->pathAsGooString(), psName); |
2018 | break; |
2019 | case fontCIDType2: |
2020 | case fontCIDType2OT: |
2021 | psName = makePSFontName(font, id: font->getID()); |
2022 | //~ should check to see if font actually uses vertical mode |
2023 | setupExternalCIDTrueTypeFont(font, fileName: fontLoc->pathAsGooString(), psName, needVerticalMetrics: true); |
2024 | break; |
2025 | default: |
2026 | break; |
2027 | } |
2028 | break; |
2029 | case gfxFontLocResident: |
2030 | psName = new GooString(fontLoc->path); |
2031 | break; |
2032 | } |
2033 | } |
2034 | |
2035 | if (!psName) { |
2036 | if (font->isCIDFont()) { |
2037 | error(category: errSyntaxError, pos: -1, msg: "Couldn't find a font to substitute for '{0:s}' ('{1:s}' character collection)" , font->getName() ? font->getName()->c_str() : "(unnamed)" , |
2038 | ((GfxCIDFont *)font)->getCollection() ? ((GfxCIDFont *)font)->getCollection()->c_str() : "(unknown)" ); |
2039 | if (font16EncLen >= font16EncSize) { |
2040 | font16EncSize += 16; |
2041 | font16Enc = (PSFont16Enc *)greallocn(p: font16Enc, count: font16EncSize, size: sizeof(PSFont16Enc)); |
2042 | } |
2043 | font16Enc[font16EncLen].fontID = *font->getID(); |
2044 | font16Enc[font16EncLen].enc = nullptr; |
2045 | ++font16EncLen; |
2046 | } else { |
2047 | error(category: errSyntaxError, pos: -1, msg: "Couldn't find a font to substitute for '{0:s}'" , font->getName() ? font->getName()->c_str() : "(unnamed)" ); |
2048 | } |
2049 | return; |
2050 | } |
2051 | |
2052 | // scale substituted 8-bit fonts |
2053 | if (fontLoc->locType == gfxFontLocResident && fontLoc->substIdx >= 0) { |
2054 | subst = true; |
2055 | for (code = 0; code < 256; ++code) { |
2056 | if ((charName = ((Gfx8BitFont *)font)->getCharName(code)) && charName[0] == 'm' && charName[1] == '\0') { |
2057 | break; |
2058 | } |
2059 | } |
2060 | if (code < 256) { |
2061 | w1 = ((Gfx8BitFont *)font)->getWidth(c: code); |
2062 | } else { |
2063 | w1 = 0; |
2064 | } |
2065 | w2 = psBase14SubstFonts[fontLoc->substIdx].mWidth; |
2066 | xs = w1 / w2; |
2067 | if (xs < 0.1) { |
2068 | xs = 1; |
2069 | } |
2070 | } |
2071 | } |
2072 | |
2073 | // generate PostScript code to set up the font |
2074 | if (font->isCIDFont()) { |
2075 | if (level == psLevel3 || level == psLevel3Sep) { |
2076 | writePSFmt(fmt: "/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n" , font->getID()->num, font->getID()->gen, psName, font->getWMode()); |
2077 | } else { |
2078 | writePSFmt(fmt: "/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16\n" , font->getID()->num, font->getID()->gen, psName, font->getWMode()); |
2079 | } |
2080 | } else { |
2081 | writePSFmt(fmt: "/F{0:d}_{1:d} /{2:t} {3:.6g} {4:.6g}\n" , font->getID()->num, font->getID()->gen, psName, xs, ys); |
2082 | for (i = 0; i < 256; i += 8) { |
2083 | writePS(s: (char *)((i == 0) ? "[ " : " " )); |
2084 | for (j = 0; j < 8; ++j) { |
2085 | if (font->getType() == fontTrueType && !subst && !((Gfx8BitFont *)font)->getHasEncoding()) { |
2086 | sprintf(s: buf, format: "c%02x" , i + j); |
2087 | charName = buf; |
2088 | } else { |
2089 | charName = ((Gfx8BitFont *)font)->getCharName(code: i + j); |
2090 | } |
2091 | writePS(s: "/" ); |
2092 | writePSName(s: charName ? charName : (char *)".notdef" ); |
2093 | // the empty name is legal in PDF and PostScript, but PostScript |
2094 | // uses a double-slash (//...) for "immediately evaluated names", |
2095 | // so we need to add a space character here |
2096 | if (charName && !charName[0]) { |
2097 | writePS(s: " " ); |
2098 | } |
2099 | } |
2100 | writePS(s: (i == 256 - 8) ? (char *)"]\n" : (char *)"\n" ); |
2101 | } |
2102 | writePS(s: "pdfMakeFont\n" ); |
2103 | } |
2104 | |
2105 | delete psName; |
2106 | } |
2107 | |
2108 | void PSOutputDev::setupEmbeddedType1Font(Ref *id, GooString *psName) |
2109 | { |
2110 | static const char hexChar[17] = "0123456789abcdef" ; |
2111 | Dict *dict; |
2112 | long length1, length2, length3, i; |
2113 | int c; |
2114 | int start[4]; |
2115 | bool binMode; |
2116 | bool writePadding = true; |
2117 | |
2118 | // check if font is already embedded |
2119 | if (!fontNames.emplace(args: psName->toStr()).second) { |
2120 | return; |
2121 | } |
2122 | |
2123 | // get the font stream and info |
2124 | Object obj1, obj2, obj3; |
2125 | Object refObj(*id); |
2126 | Object strObj = refObj.fetch(xref); |
2127 | if (!strObj.isStream()) { |
2128 | error(category: errSyntaxError, pos: -1, msg: "Embedded font file object is not a stream" ); |
2129 | goto err1; |
2130 | } |
2131 | if (!(dict = strObj.streamGetDict())) { |
2132 | error(category: errSyntaxError, pos: -1, msg: "Embedded font stream is missing its dictionary" ); |
2133 | goto err1; |
2134 | } |
2135 | obj1 = dict->lookup(key: "Length1" ); |
2136 | obj2 = dict->lookup(key: "Length2" ); |
2137 | obj3 = dict->lookup(key: "Length3" ); |
2138 | if (!obj1.isInt() || !obj2.isInt() || !obj3.isInt()) { |
2139 | error(category: errSyntaxError, pos: -1, msg: "Missing length fields in embedded font stream dictionary" ); |
2140 | goto err1; |
2141 | } |
2142 | length1 = obj1.getInt(); |
2143 | length2 = obj2.getInt(); |
2144 | length3 = obj3.getInt(); |
2145 | |
2146 | // beginning comment |
2147 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2148 | embFontList->append(str: "%%+ font " ); |
2149 | embFontList->append(str: psName->c_str()); |
2150 | embFontList->append(str: "\n" ); |
2151 | |
2152 | strObj.streamReset(); |
2153 | if (strObj.streamGetChar() == 0x80 && strObj.streamGetChar() == 1) { |
2154 | // PFB format |
2155 | length1 = strObj.streamGetChar() | (strObj.streamGetChar() << 8) | (strObj.streamGetChar() << 16) | (strObj.streamGetChar() << 24); |
2156 | } else { |
2157 | strObj.streamReset(); |
2158 | } |
2159 | // copy ASCII portion of font |
2160 | for (i = 0; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) { |
2161 | writePSChar(c); |
2162 | } |
2163 | |
2164 | // figure out if encrypted portion is binary or ASCII |
2165 | binMode = false; |
2166 | for (i = 0; i < 4; ++i) { |
2167 | start[i] = strObj.streamGetChar(); |
2168 | if (start[i] == EOF) { |
2169 | error(category: errSyntaxError, pos: -1, msg: "Unexpected end of file in embedded font stream" ); |
2170 | goto err1; |
2171 | } |
2172 | if (!((start[i] >= '0' && start[i] <= '9') || (start[i] >= 'A' && start[i] <= 'F') || (start[i] >= 'a' && start[i] <= 'f'))) { |
2173 | binMode = true; |
2174 | } |
2175 | } |
2176 | |
2177 | if (length2 == 0) { |
2178 | // length2 == 0 is an error |
2179 | // trying to solve it by just piping all |
2180 | // the stream data |
2181 | error(category: errSyntaxWarning, pos: -1, msg: "Font has length2 as 0, trying to overcome the problem reading the stream until the end" ); |
2182 | length2 = INT_MAX; |
2183 | writePadding = false; |
2184 | } |
2185 | |
2186 | // convert binary data to ASCII |
2187 | if (binMode) { |
2188 | if (start[0] == 0x80 && start[1] == 2) { |
2189 | length2 = start[2] | (start[3] << 8) | (strObj.streamGetChar() << 16) | (strObj.streamGetChar() << 24); |
2190 | i = 0; |
2191 | } else { |
2192 | for (i = 0; i < 4; ++i) { |
2193 | writePSChar(c: hexChar[(start[i] >> 4) & 0x0f]); |
2194 | writePSChar(c: hexChar[start[i] & 0x0f]); |
2195 | } |
2196 | } |
2197 | #if 0 // this causes trouble for various PostScript printers |
2198 | // if Length2 is incorrect (too small), font data gets chopped, so |
2199 | // we take a few extra characters from the trailer just in case |
2200 | length2 += length3 >= 8 ? 8 : length3; |
2201 | #endif |
2202 | while (i < length2) { |
2203 | if ((c = strObj.streamGetChar()) == EOF) { |
2204 | break; |
2205 | } |
2206 | writePSChar(c: hexChar[(c >> 4) & 0x0f]); |
2207 | writePSChar(c: hexChar[c & 0x0f]); |
2208 | if (++i % 32 == 0) { |
2209 | writePSChar(c: '\n'); |
2210 | } |
2211 | } |
2212 | if (i % 32 > 0) { |
2213 | writePSChar(c: '\n'); |
2214 | } |
2215 | |
2216 | // already in ASCII format -- just copy it |
2217 | } else { |
2218 | for (i = 0; i < 4; ++i) { |
2219 | writePSChar(c: start[i]); |
2220 | } |
2221 | for (i = 4; i < length2; ++i) { |
2222 | if ((c = strObj.streamGetChar()) == EOF) { |
2223 | break; |
2224 | } |
2225 | writePSChar(c); |
2226 | } |
2227 | } |
2228 | |
2229 | if (writePadding) { |
2230 | if (length3 > 0) { |
2231 | // write fixed-content portion |
2232 | c = strObj.streamGetChar(); |
2233 | if (c == 0x80) { |
2234 | c = strObj.streamGetChar(); |
2235 | if (c == 1) { |
2236 | length3 = strObj.streamGetChar() | (strObj.streamGetChar() << 8) | (strObj.streamGetChar() << 16) | (strObj.streamGetChar() << 24); |
2237 | |
2238 | i = 0; |
2239 | while (i < length3) { |
2240 | if ((c = strObj.streamGetChar()) == EOF) { |
2241 | break; |
2242 | } |
2243 | writePSChar(c); |
2244 | ++i; |
2245 | } |
2246 | } |
2247 | } else { |
2248 | if (c != EOF) { |
2249 | writePSChar(c); |
2250 | |
2251 | while ((c = strObj.streamGetChar()) != EOF) { |
2252 | writePSChar(c); |
2253 | } |
2254 | } |
2255 | } |
2256 | } else { |
2257 | // write padding and "cleartomark" |
2258 | for (i = 0; i < 8; ++i) { |
2259 | writePS(s: "00000000000000000000000000000000" |
2260 | "00000000000000000000000000000000\n" ); |
2261 | } |
2262 | writePS(s: "cleartomark\n" ); |
2263 | } |
2264 | } |
2265 | |
2266 | // ending comment |
2267 | writePS(s: "%%EndResource\n" ); |
2268 | |
2269 | err1: |
2270 | if (strObj.isStream()) { |
2271 | strObj.streamClose(); |
2272 | } |
2273 | } |
2274 | |
2275 | void PSOutputDev::setupExternalType1Font(const GooString *fileName, GooString *psName) |
2276 | { |
2277 | static const char hexChar[17] = "0123456789abcdef" ; |
2278 | FILE *fontFile; |
2279 | int c; |
2280 | |
2281 | if (!fontNames.emplace(args: psName->toStr()).second) { |
2282 | return; |
2283 | } |
2284 | |
2285 | // beginning comment |
2286 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2287 | embFontList->append(str: "%%+ font " ); |
2288 | embFontList->append(str: psName->c_str()); |
2289 | embFontList->append(str: "\n" ); |
2290 | |
2291 | // copy the font file |
2292 | if (!(fontFile = openFile(path: fileName->c_str(), mode: "rb" ))) { |
2293 | error(category: errIO, pos: -1, msg: "Couldn't open external font file" ); |
2294 | return; |
2295 | } |
2296 | |
2297 | c = fgetc(stream: fontFile); |
2298 | if (c == 0x80) { |
2299 | // PFB file |
2300 | ungetc(c: c, stream: fontFile); |
2301 | while (!feof(stream: fontFile)) { |
2302 | fgetc(stream: fontFile); // skip start of segment byte (0x80) |
2303 | int segType = fgetc(stream: fontFile); |
2304 | long segLen = fgetc(stream: fontFile) | (fgetc(stream: fontFile) << 8) | (fgetc(stream: fontFile) << 16) | (fgetc(stream: fontFile) << 24); |
2305 | if (feof(stream: fontFile)) { |
2306 | break; |
2307 | } |
2308 | |
2309 | if (segType == 1) { |
2310 | // ASCII segment |
2311 | for (long i = 0; i < segLen; i++) { |
2312 | c = fgetc(stream: fontFile); |
2313 | if (c == EOF) { |
2314 | break; |
2315 | } |
2316 | writePSChar(c); |
2317 | } |
2318 | } else if (segType == 2) { |
2319 | // binary segment |
2320 | for (long i = 0; i < segLen; i++) { |
2321 | c = fgetc(stream: fontFile); |
2322 | if (c == EOF) { |
2323 | break; |
2324 | } |
2325 | writePSChar(c: hexChar[(c >> 4) & 0x0f]); |
2326 | writePSChar(c: hexChar[c & 0x0f]); |
2327 | if (i % 36 == 35) { |
2328 | writePSChar(c: '\n'); |
2329 | } |
2330 | } |
2331 | } else { |
2332 | // end of file |
2333 | break; |
2334 | } |
2335 | } |
2336 | } else if (c != EOF) { |
2337 | writePSChar(c); |
2338 | while ((c = fgetc(stream: fontFile)) != EOF) { |
2339 | writePSChar(c); |
2340 | } |
2341 | } |
2342 | fclose(stream: fontFile); |
2343 | |
2344 | // ending comment |
2345 | writePS(s: "%%EndResource\n" ); |
2346 | } |
2347 | |
2348 | void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id, GooString *psName) |
2349 | { |
2350 | FoFiType1C *ffT1C; |
2351 | int i; |
2352 | |
2353 | // check if font is already embedded |
2354 | for (i = 0; i < t1FontNameLen; ++i) { |
2355 | if (t1FontNames[i].fontFileID == *id) { |
2356 | psName->clear(); |
2357 | psName->insert(i: 0, str: t1FontNames[i].psName); |
2358 | return; |
2359 | } |
2360 | } |
2361 | if (t1FontNameLen == t1FontNameSize) { |
2362 | t1FontNameSize *= 2; |
2363 | t1FontNames = (PST1FontName *)greallocn(p: t1FontNames, count: t1FontNameSize, size: sizeof(PST1FontName)); |
2364 | } |
2365 | t1FontNames[t1FontNameLen].fontFileID = *id; |
2366 | t1FontNames[t1FontNameLen].psName = psName->copy(); |
2367 | ++t1FontNameLen; |
2368 | |
2369 | // beginning comment |
2370 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2371 | embFontList->append(str: "%%+ font " ); |
2372 | embFontList->append(str: psName->c_str()); |
2373 | embFontList->append(str: "\n" ); |
2374 | |
2375 | // convert it to a Type 1 font |
2376 | const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
2377 | if (fontBuf) { |
2378 | if ((ffT1C = FoFiType1C::make(fileA: fontBuf->data(), lenA: fontBuf->size()))) { |
2379 | ffT1C->convertToType1(psName: psName->c_str(), newEncoding: nullptr, ascii: true, outputFunc, outputStream); |
2380 | delete ffT1C; |
2381 | } |
2382 | } |
2383 | |
2384 | // ending comment |
2385 | writePS(s: "%%EndResource\n" ); |
2386 | } |
2387 | |
2388 | void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, GooString *psName) |
2389 | { |
2390 | int i; |
2391 | |
2392 | // check if font is already embedded |
2393 | for (i = 0; i < t1FontNameLen; ++i) { |
2394 | if (t1FontNames[i].fontFileID == *id) { |
2395 | psName->clear(); |
2396 | psName->insert(i: 0, str: t1FontNames[i].psName); |
2397 | return; |
2398 | } |
2399 | } |
2400 | if (t1FontNameLen == t1FontNameSize) { |
2401 | t1FontNameSize *= 2; |
2402 | t1FontNames = (PST1FontName *)greallocn(p: t1FontNames, count: t1FontNameSize, size: sizeof(PST1FontName)); |
2403 | } |
2404 | t1FontNames[t1FontNameLen].fontFileID = *id; |
2405 | t1FontNames[t1FontNameLen].psName = psName->copy(); |
2406 | ++t1FontNameLen; |
2407 | |
2408 | // beginning comment |
2409 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2410 | embFontList->append(str: "%%+ font " ); |
2411 | embFontList->append(str: psName->c_str()); |
2412 | embFontList->append(str: "\n" ); |
2413 | |
2414 | // convert it to a Type 1 font |
2415 | const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
2416 | if (fontBuf) { |
2417 | if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::make(fileA: fontBuf->data(), lenA: fontBuf->size())) { |
2418 | if (ffTT->isOpenTypeCFF()) { |
2419 | ffTT->convertToType1(psName: psName->c_str(), newEncoding: nullptr, ascii: true, outputFunc, outputStream); |
2420 | } |
2421 | } |
2422 | } |
2423 | |
2424 | // ending comment |
2425 | writePS(s: "%%EndResource\n" ); |
2426 | } |
2427 | |
2428 | void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id, GooString *psName) |
2429 | { |
2430 | int *codeToGID; |
2431 | |
2432 | // beginning comment |
2433 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2434 | embFontList->append(str: "%%+ font " ); |
2435 | embFontList->append(str: psName->c_str()); |
2436 | embFontList->append(str: "\n" ); |
2437 | |
2438 | // convert it to a Type 42 font |
2439 | const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
2440 | if (fontBuf) { |
2441 | if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::make(fileA: fontBuf->data(), lenA: fontBuf->size())) { |
2442 | codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ff: ffTT.get()); |
2443 | ffTT->convertToType42(psName: psName->c_str(), encoding: ((Gfx8BitFont *)font)->getHasEncoding() ? ((Gfx8BitFont *)font)->getEncoding() : nullptr, codeToGID, outputFunc, outputStream); |
2444 | if (codeToGID) { |
2445 | if (font8InfoLen >= font8InfoSize) { |
2446 | font8InfoSize += 16; |
2447 | font8Info = (PSFont8Info *)greallocn(p: font8Info, count: font8InfoSize, size: sizeof(PSFont8Info)); |
2448 | } |
2449 | font8Info[font8InfoLen].fontID = *font->getID(); |
2450 | font8Info[font8InfoLen].codeToGID = codeToGID; |
2451 | ++font8InfoLen; |
2452 | } |
2453 | } |
2454 | } |
2455 | |
2456 | // ending comment |
2457 | writePS(s: "%%EndResource\n" ); |
2458 | } |
2459 | |
2460 | void PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, const GooString *fileName, GooString *psName) |
2461 | { |
2462 | int *codeToGID; |
2463 | |
2464 | // beginning comment |
2465 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2466 | embFontList->append(str: "%%+ font " ); |
2467 | embFontList->append(str: psName->c_str()); |
2468 | embFontList->append(str: "\n" ); |
2469 | |
2470 | // convert it to a Type 42 font |
2471 | if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::load(fileName: fileName->c_str())) { |
2472 | codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ff: ffTT.get()); |
2473 | ffTT->convertToType42(psName: psName->c_str(), encoding: ((Gfx8BitFont *)font)->getHasEncoding() ? ((Gfx8BitFont *)font)->getEncoding() : nullptr, codeToGID, outputFunc, outputStream); |
2474 | if (codeToGID) { |
2475 | if (font8InfoLen >= font8InfoSize) { |
2476 | font8InfoSize += 16; |
2477 | font8Info = (PSFont8Info *)greallocn(p: font8Info, count: font8InfoSize, size: sizeof(PSFont8Info)); |
2478 | } |
2479 | font8Info[font8InfoLen].fontID = *font->getID(); |
2480 | font8Info[font8InfoLen].codeToGID = codeToGID; |
2481 | ++font8InfoLen; |
2482 | } |
2483 | } |
2484 | |
2485 | // ending comment |
2486 | writePS(s: "%%EndResource\n" ); |
2487 | } |
2488 | |
2489 | void PSOutputDev::updateFontMaxValidGlyph(GfxFont *font, int maxValidGlyph) |
2490 | { |
2491 | if (maxValidGlyph >= 0 && font->getName()) { |
2492 | auto &fontMaxValidGlyph = perFontMaxValidGlyph[*font->getName()]; |
2493 | if (fontMaxValidGlyph < maxValidGlyph) { |
2494 | fontMaxValidGlyph = maxValidGlyph; |
2495 | } |
2496 | } |
2497 | } |
2498 | |
2499 | void PSOutputDev::setupExternalCIDTrueTypeFont(GfxFont *font, const GooString *fileName, GooString *psName, bool needVerticalMetrics) |
2500 | { |
2501 | int *codeToGID; |
2502 | int codeToGIDLen; |
2503 | |
2504 | // beginning comment |
2505 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2506 | embFontList->append(str: "%%+ font " ); |
2507 | embFontList->append(str: psName->c_str()); |
2508 | embFontList->append(str: "\n" ); |
2509 | |
2510 | // convert it to a Type 0 font |
2511 | //~ this should use fontNum to load the correct font |
2512 | if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::load(fileName: fileName->c_str())) { |
2513 | |
2514 | // check for embedding permission |
2515 | if (ffTT->getEmbeddingRights() >= 1) { |
2516 | codeToGID = nullptr; |
2517 | codeToGIDLen = 0; |
2518 | if (((GfxCIDFont *)font)->getCIDToGID()) { |
2519 | codeToGIDLen = ((GfxCIDFont *)font)->getCIDToGIDLen(); |
2520 | if (codeToGIDLen) { |
2521 | codeToGID = (int *)gmallocn(count: codeToGIDLen, size: sizeof(int)); |
2522 | memcpy(dest: codeToGID, src: ((GfxCIDFont *)font)->getCIDToGID(), n: codeToGIDLen * sizeof(int)); |
2523 | } |
2524 | } else { |
2525 | codeToGID = ((GfxCIDFont *)font)->getCodeToGIDMap(ff: ffTT.get(), codeToGIDLen: &codeToGIDLen); |
2526 | } |
2527 | if (ffTT->isOpenTypeCFF()) { |
2528 | ffTT->convertToCIDType0(psName: psName->c_str(), cidMap: codeToGID, nCIDs: codeToGIDLen, outputFunc, outputStream); |
2529 | } else if (level >= psLevel3) { |
2530 | // Level 3: use a CID font |
2531 | ffTT->convertToCIDType2(psName: psName->c_str(), cidMap: codeToGID, nCIDs: codeToGIDLen, needVerticalMetrics, outputFunc, outputStream); |
2532 | } else { |
2533 | // otherwise: use a non-CID composite font |
2534 | int maxValidGlyph = -1; |
2535 | ffTT->convertToType0(psName: psName->c_str(), cidMap: codeToGID, nCIDs: codeToGIDLen, needVerticalMetrics, maxValidGlyph: &maxValidGlyph, outputFunc, outputStream); |
2536 | updateFontMaxValidGlyph(font, maxValidGlyph); |
2537 | } |
2538 | gfree(p: codeToGID); |
2539 | } else { |
2540 | error(category: errSyntaxError, pos: -1, msg: "TrueType font '{0:s}' does not allow embedding" , font->getName() ? font->getName()->c_str() : "(unnamed)" ); |
2541 | } |
2542 | } |
2543 | |
2544 | // ending comment |
2545 | writePS(s: "%%EndResource\n" ); |
2546 | } |
2547 | |
2548 | void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id, GooString *psName) |
2549 | { |
2550 | FoFiType1C *ffT1C; |
2551 | int i; |
2552 | |
2553 | // check if font is already embedded |
2554 | for (i = 0; i < t1FontNameLen; ++i) { |
2555 | if (t1FontNames[i].fontFileID == *id) { |
2556 | psName->clear(); |
2557 | psName->insert(i: 0, str: t1FontNames[i].psName); |
2558 | return; |
2559 | } |
2560 | } |
2561 | if (t1FontNameLen == t1FontNameSize) { |
2562 | t1FontNameSize *= 2; |
2563 | t1FontNames = (PST1FontName *)greallocn(p: t1FontNames, count: t1FontNameSize, size: sizeof(PST1FontName)); |
2564 | } |
2565 | t1FontNames[t1FontNameLen].fontFileID = *id; |
2566 | t1FontNames[t1FontNameLen].psName = psName->copy(); |
2567 | ++t1FontNameLen; |
2568 | |
2569 | // beginning comment |
2570 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2571 | embFontList->append(str: "%%+ font " ); |
2572 | embFontList->append(str: psName->c_str()); |
2573 | embFontList->append(str: "\n" ); |
2574 | |
2575 | // convert it to a Type 0 font |
2576 | const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
2577 | if (fontBuf) { |
2578 | if ((ffT1C = FoFiType1C::make(fileA: fontBuf->data(), lenA: fontBuf->size()))) { |
2579 | if (level >= psLevel3) { |
2580 | // Level 3: use a CID font |
2581 | ffT1C->convertToCIDType0(psName: psName->c_str(), codeMap: nullptr, nCodes: 0, outputFunc, outputStream); |
2582 | } else { |
2583 | // otherwise: use a non-CID composite font |
2584 | ffT1C->convertToType0(psName: psName->c_str(), codeMap: nullptr, nCodes: 0, outputFunc, outputStream); |
2585 | } |
2586 | delete ffT1C; |
2587 | } |
2588 | } |
2589 | |
2590 | // ending comment |
2591 | writePS(s: "%%EndResource\n" ); |
2592 | } |
2593 | |
2594 | void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, GooString *psName, bool needVerticalMetrics) |
2595 | { |
2596 | // beginning comment |
2597 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2598 | embFontList->append(str: "%%+ font " ); |
2599 | embFontList->append(str: psName->c_str()); |
2600 | embFontList->append(str: "\n" ); |
2601 | |
2602 | // convert it to a Type 0 font |
2603 | const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
2604 | if (fontBuf) { |
2605 | if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::make(fileA: fontBuf->data(), lenA: fontBuf->size())) { |
2606 | if (level >= psLevel3) { |
2607 | // Level 3: use a CID font |
2608 | ffTT->convertToCIDType2(psName: psName->c_str(), cidMap: ((GfxCIDFont *)font)->getCIDToGID(), nCIDs: ((GfxCIDFont *)font)->getCIDToGIDLen(), needVerticalMetrics, outputFunc, outputStream); |
2609 | } else { |
2610 | // otherwise: use a non-CID composite font |
2611 | int maxValidGlyph = -1; |
2612 | ffTT->convertToType0(psName: psName->c_str(), cidMap: ((GfxCIDFont *)font)->getCIDToGID(), nCIDs: ((GfxCIDFont *)font)->getCIDToGIDLen(), needVerticalMetrics, maxValidGlyph: &maxValidGlyph, outputFunc, outputStream); |
2613 | updateFontMaxValidGlyph(font, maxValidGlyph); |
2614 | } |
2615 | } |
2616 | } |
2617 | |
2618 | // ending comment |
2619 | writePS(s: "%%EndResource\n" ); |
2620 | } |
2621 | |
2622 | void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, GooString *psName) |
2623 | { |
2624 | int i; |
2625 | |
2626 | // check if font is already embedded |
2627 | for (i = 0; i < t1FontNameLen; ++i) { |
2628 | if (t1FontNames[i].fontFileID == *id) { |
2629 | psName->clear(); |
2630 | psName->insert(i: 0, str: t1FontNames[i].psName); |
2631 | return; |
2632 | } |
2633 | } |
2634 | if (t1FontNameLen == t1FontNameSize) { |
2635 | t1FontNameSize *= 2; |
2636 | t1FontNames = (PST1FontName *)greallocn(p: t1FontNames, count: t1FontNameSize, size: sizeof(PST1FontName)); |
2637 | } |
2638 | t1FontNames[t1FontNameLen].fontFileID = *id; |
2639 | t1FontNames[t1FontNameLen].psName = psName->copy(); |
2640 | ++t1FontNameLen; |
2641 | |
2642 | // beginning comment |
2643 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2644 | embFontList->append(str: "%%+ font " ); |
2645 | embFontList->append(str: psName->c_str()); |
2646 | embFontList->append(str: "\n" ); |
2647 | |
2648 | // convert it to a Type 0 font |
2649 | const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
2650 | if (fontBuf) { |
2651 | if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::make(fileA: fontBuf->data(), lenA: fontBuf->size())) { |
2652 | if (ffTT->isOpenTypeCFF()) { |
2653 | if (level >= psLevel3) { |
2654 | // Level 3: use a CID font |
2655 | ffTT->convertToCIDType0(psName: psName->c_str(), cidMap: ((GfxCIDFont *)font)->getCIDToGID(), nCIDs: ((GfxCIDFont *)font)->getCIDToGIDLen(), outputFunc, outputStream); |
2656 | } else { |
2657 | // otherwise: use a non-CID composite font |
2658 | ffTT->convertToType0(psName: psName->c_str(), cidMap: ((GfxCIDFont *)font)->getCIDToGID(), nCIDs: ((GfxCIDFont *)font)->getCIDToGIDLen(), outputFunc, outputStream); |
2659 | } |
2660 | } |
2661 | } |
2662 | } |
2663 | |
2664 | // ending comment |
2665 | writePS(s: "%%EndResource\n" ); |
2666 | } |
2667 | |
2668 | void PSOutputDev::setupType3Font(GfxFont *font, GooString *psName, Dict *parentResDict) |
2669 | { |
2670 | Dict *resDict; |
2671 | Dict *charProcs; |
2672 | Gfx *gfx; |
2673 | PDFRectangle box; |
2674 | const double *m; |
2675 | int i; |
2676 | |
2677 | // set up resources used by font |
2678 | if ((resDict = ((Gfx8BitFont *)font)->getResources())) { |
2679 | inType3Char = true; |
2680 | setupResources(resDict); |
2681 | inType3Char = false; |
2682 | } else { |
2683 | resDict = parentResDict; |
2684 | } |
2685 | |
2686 | // beginning comment |
2687 | writePSFmt(fmt: "%%BeginResource: font {0:t}\n" , psName); |
2688 | embFontList->append(str: "%%+ font " ); |
2689 | embFontList->append(str: psName->c_str()); |
2690 | embFontList->append(str: "\n" ); |
2691 | |
2692 | // font dictionary |
2693 | writePS(s: "8 dict begin\n" ); |
2694 | writePS(s: "/FontType 3 def\n" ); |
2695 | m = font->getFontMatrix(); |
2696 | writePSFmt(fmt: "/FontMatrix [{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] def\n" , m[0], m[1], m[2], m[3], m[4], m[5]); |
2697 | m = font->getFontBBox(); |
2698 | writePSFmt(fmt: "/FontBBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}] def\n" , m[0], m[1], m[2], m[3]); |
2699 | writePS(s: "/Encoding 256 array def\n" ); |
2700 | writePS(s: " 0 1 255 { Encoding exch /.notdef put } for\n" ); |
2701 | writePS(s: "/BuildGlyph {\n" ); |
2702 | writePS(s: " exch /CharProcs get exch\n" ); |
2703 | writePS(s: " 2 copy known not { pop /.notdef } if\n" ); |
2704 | writePS(s: " get exec\n" ); |
2705 | writePS(s: "} bind def\n" ); |
2706 | writePS(s: "/BuildChar {\n" ); |
2707 | writePS(s: " 1 index /Encoding get exch get\n" ); |
2708 | writePS(s: " 1 index /BuildGlyph get exec\n" ); |
2709 | writePS(s: "} bind def\n" ); |
2710 | if ((charProcs = ((Gfx8BitFont *)font)->getCharProcs())) { |
2711 | writePSFmt(fmt: "/CharProcs {0:d} dict def\n" , charProcs->getLength()); |
2712 | writePS(s: "CharProcs begin\n" ); |
2713 | box.x1 = m[0]; |
2714 | box.y1 = m[1]; |
2715 | box.x2 = m[2]; |
2716 | box.y2 = m[3]; |
2717 | gfx = new Gfx(doc, this, resDict, &box, nullptr); |
2718 | inType3Char = true; |
2719 | for (i = 0; i < charProcs->getLength(); ++i) { |
2720 | t3FillColorOnly = false; |
2721 | t3Cacheable = false; |
2722 | t3NeedsRestore = false; |
2723 | writePS(s: "/" ); |
2724 | writePSName(s: charProcs->getKey(i)); |
2725 | writePS(s: " {\n" ); |
2726 | Object charProc = charProcs->getVal(i); |
2727 | gfx->display(obj: &charProc); |
2728 | if (t3String) { |
2729 | std::unique_ptr<GooString> buf; |
2730 | if (t3Cacheable) { |
2731 | buf = GooString::format(fmt: "{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g} setcachedevice\n" , t3WX, t3WY, t3LLX, t3LLY, t3URX, t3URY); |
2732 | } else { |
2733 | buf = GooString::format(fmt: "{0:.6g} {1:.6g} setcharwidth\n" , t3WX, t3WY); |
2734 | } |
2735 | (*outputFunc)(outputStream, buf->c_str(), buf->getLength()); |
2736 | (*outputFunc)(outputStream, t3String->c_str(), t3String->getLength()); |
2737 | delete t3String; |
2738 | t3String = nullptr; |
2739 | } |
2740 | if (t3NeedsRestore) { |
2741 | (*outputFunc)(outputStream, "Q\n" , 2); |
2742 | } |
2743 | writePS(s: "} def\n" ); |
2744 | } |
2745 | inType3Char = false; |
2746 | delete gfx; |
2747 | writePS(s: "end\n" ); |
2748 | } |
2749 | writePS(s: "currentdict end\n" ); |
2750 | writePSFmt(fmt: "/{0:t} exch definefont pop\n" , psName); |
2751 | |
2752 | // ending comment |
2753 | writePS(s: "%%EndResource\n" ); |
2754 | } |
2755 | |
2756 | // Make a unique PS font name, based on the names given in the PDF |
2757 | // font object, and an object ID (font file object for |
2758 | GooString *PSOutputDev::makePSFontName(GfxFont *font, const Ref *id) |
2759 | { |
2760 | const GooString *s; |
2761 | |
2762 | if ((s = font->getEmbeddedFontName())) { |
2763 | std::string psName = filterPSName(name: s->toStr()); |
2764 | if (fontNames.emplace(args&: psName).second) { |
2765 | return new GooString(std::move(psName)); |
2766 | } |
2767 | } |
2768 | if (font->getName()) { |
2769 | std::string psName = filterPSName(name: *font->getName()); |
2770 | if (fontNames.emplace(args&: psName).second) { |
2771 | return new GooString(std::move(psName)); |
2772 | } |
2773 | } |
2774 | GooString *psName = GooString::format(fmt: "FF{0:d}_{1:d}" , id->num, id->gen).release(); |
2775 | if ((s = font->getEmbeddedFontName())) { |
2776 | std::string filteredName = filterPSName(name: s->toStr()); |
2777 | psName->append(c: '_')->append(str: filteredName); |
2778 | } else if (font->getName()) { |
2779 | std::string filteredName = filterPSName(name: *font->getName()); |
2780 | psName->append(c: '_')->append(str: filteredName); |
2781 | } |
2782 | fontNames.emplace(args: psName->toStr()); |
2783 | return psName; |
2784 | } |
2785 | |
2786 | void PSOutputDev::setupImages(Dict *resDict) |
2787 | { |
2788 | Ref imgID; |
2789 | |
2790 | if (!(mode == psModeForm || inType3Char || preloadImagesForms)) { |
2791 | return; |
2792 | } |
2793 | |
2794 | //----- recursively scan XObjects |
2795 | Object xObjDict = resDict->lookup(key: "XObject" ); |
2796 | if (xObjDict.isDict()) { |
2797 | for (int i = 0; i < xObjDict.dictGetLength(); ++i) { |
2798 | const Object &xObjRef = xObjDict.dictGetValNF(i); |
2799 | Object xObj = xObjDict.dictGetVal(i); |
2800 | if (xObj.isStream()) { |
2801 | Object subtypeObj = xObj.streamGetDict()->lookup(key: "Subtype" ); |
2802 | if (subtypeObj.isName(nameA: "Image" )) { |
2803 | if (xObjRef.isRef()) { |
2804 | imgID = xObjRef.getRef(); |
2805 | int j; |
2806 | for (j = 0; j < imgIDLen; ++j) { |
2807 | if (imgIDs[j] == imgID) { |
2808 | break; |
2809 | } |
2810 | } |
2811 | if (j == imgIDLen) { |
2812 | if (imgIDLen >= imgIDSize) { |
2813 | if (imgIDSize == 0) { |
2814 | imgIDSize = 64; |
2815 | } else { |
2816 | imgIDSize *= 2; |
2817 | } |
2818 | imgIDs = (Ref *)greallocn(p: imgIDs, count: imgIDSize, size: sizeof(Ref)); |
2819 | } |
2820 | imgIDs[imgIDLen++] = imgID; |
2821 | setupImage(id: imgID, str: xObj.getStream(), mask: false); |
2822 | if (level >= psLevel3) { |
2823 | Object maskObj = xObj.streamGetDict()->lookup(key: "Mask" ); |
2824 | if (maskObj.isStream()) { |
2825 | setupImage(id: imgID, str: maskObj.getStream(), mask: true); |
2826 | } |
2827 | } |
2828 | } |
2829 | } else { |
2830 | error(category: errSyntaxError, pos: -1, msg: "Image in resource dict is not an indirect reference" ); |
2831 | } |
2832 | } |
2833 | } |
2834 | } |
2835 | } |
2836 | } |
2837 | |
2838 | void PSOutputDev::setupImage(Ref id, Stream *str, bool mask) |
2839 | { |
2840 | bool useFlate, useLZW, useRLE, useCompressed, doUseASCIIHex; |
2841 | GooString *s; |
2842 | int c; |
2843 | int size, line, col, i; |
2844 | int outerSize, outer; |
2845 | |
2846 | // filters |
2847 | //~ this does not correctly handle the DeviceN color space |
2848 | //~ -- need to use DeviceNRecoder |
2849 | |
2850 | useFlate = useLZW = useRLE = false; |
2851 | useCompressed = false; |
2852 | doUseASCIIHex = false; |
2853 | |
2854 | if (level < psLevel2) { |
2855 | doUseASCIIHex = true; |
2856 | } else { |
2857 | if (uncompressPreloadedImages) { |
2858 | /* nothing to do */; |
2859 | } else { |
2860 | s = str->getPSFilter(psLevel: level < psLevel3 ? 2 : 3, indent: "" ); |
2861 | if (s) { |
2862 | useCompressed = true; |
2863 | delete s; |
2864 | } else { |
2865 | if (level >= psLevel3 && getEnableFlate()) { |
2866 | useFlate = true; |
2867 | } else if (getEnableLZW()) { |
2868 | useLZW = true; |
2869 | } else { |
2870 | useRLE = true; |
2871 | } |
2872 | } |
2873 | } |
2874 | doUseASCIIHex = useASCIIHex; |
2875 | } |
2876 | if (useCompressed) { |
2877 | str = str->getUndecodedStream(); |
2878 | } |
2879 | if (useFlate) { |
2880 | str = new FlateEncoder(str); |
2881 | } else if (useLZW) { |
2882 | str = new LZWEncoder(str); |
2883 | } else if (useRLE) { |
2884 | str = new RunLengthEncoder(str); |
2885 | } |
2886 | if (doUseASCIIHex) { |
2887 | str = new ASCIIHexEncoder(str); |
2888 | } else { |
2889 | str = new ASCII85Encoder(str); |
2890 | } |
2891 | |
2892 | // compute image data size |
2893 | str->reset(); |
2894 | col = size = 0; |
2895 | do { |
2896 | do { |
2897 | c = str->getChar(); |
2898 | } while (c == '\n' || c == '\r'); |
2899 | if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) { |
2900 | break; |
2901 | } |
2902 | if (c == 'z') { |
2903 | ++col; |
2904 | } else { |
2905 | ++col; |
2906 | for (i = 1; i <= (doUseASCIIHex ? 1 : 4); ++i) { |
2907 | do { |
2908 | c = str->getChar(); |
2909 | } while (c == '\n' || c == '\r'); |
2910 | if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) { |
2911 | break; |
2912 | } |
2913 | ++col; |
2914 | } |
2915 | if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) { |
2916 | break; |
2917 | } |
2918 | } |
2919 | if (col > 225) { |
2920 | ++size; |
2921 | col = 0; |
2922 | } |
2923 | } while (c != (doUseASCIIHex ? '>' : '~') && c != EOF); |
2924 | // add one entry for the final line of data; add another entry |
2925 | // because the LZWDecode/RunLengthDecode filter may read past the end |
2926 | ++size; |
2927 | if (useLZW || useRLE) { |
2928 | ++size; |
2929 | } |
2930 | outerSize = size / 65535 + 1; |
2931 | |
2932 | writePSFmt(fmt: "{0:d} array dup /{1:s}Data_{2:d}_{3:d} exch def\n" , outerSize, mask ? "Mask" : "Im" , id.num, id.gen); |
2933 | str->close(); |
2934 | |
2935 | // write the data into the array |
2936 | str->reset(); |
2937 | for (outer = 0; outer < outerSize; outer++) { |
2938 | int innerSize = size > 65535 ? 65535 : size; |
2939 | |
2940 | // put the inner array into the outer array |
2941 | writePSFmt(fmt: "{0:d} array 1 index {1:d} 2 index put\n" , innerSize, outer); |
2942 | line = col = 0; |
2943 | writePS(s: (char *)(doUseASCIIHex ? "dup 0 <" : "dup 0 <~" )); |
2944 | for (;;) { |
2945 | do { |
2946 | c = str->getChar(); |
2947 | } while (c == '\n' || c == '\r'); |
2948 | if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) { |
2949 | break; |
2950 | } |
2951 | if (c == 'z') { |
2952 | writePSChar(c); |
2953 | ++col; |
2954 | } else { |
2955 | writePSChar(c); |
2956 | ++col; |
2957 | for (i = 1; i <= (doUseASCIIHex ? 1 : 4); ++i) { |
2958 | do { |
2959 | c = str->getChar(); |
2960 | } while (c == '\n' || c == '\r'); |
2961 | if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) { |
2962 | break; |
2963 | } |
2964 | writePSChar(c); |
2965 | ++col; |
2966 | } |
2967 | } |
2968 | if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) { |
2969 | break; |
2970 | } |
2971 | // each line is: "dup nnnnn <~...data...~> put<eol>" |
2972 | // so max data length = 255 - 20 = 235 |
2973 | // chunks are 1 or 4 bytes each, so we have to stop at 232 |
2974 | // but make it 225 just to be safe |
2975 | if (col > 225) { |
2976 | writePS(s: (char *)(doUseASCIIHex ? "> put\n" : "~> put\n" )); |
2977 | ++line; |
2978 | if (line >= innerSize) { |
2979 | break; |
2980 | } |
2981 | writePSFmt(fmt: (char *)(doUseASCIIHex ? "dup {0:d} <" : "dup {0:d} <~" ), line); |
2982 | col = 0; |
2983 | } |
2984 | } |
2985 | if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) { |
2986 | writePS(s: (char *)(doUseASCIIHex ? "> put\n" : "~> put\n" )); |
2987 | if (useLZW || useRLE) { |
2988 | ++line; |
2989 | writePSFmt(fmt: "{0:d} <> put\n" , line); |
2990 | } else { |
2991 | writePS(s: "pop\n" ); |
2992 | } |
2993 | break; |
2994 | } |
2995 | writePS(s: "pop\n" ); |
2996 | size -= innerSize; |
2997 | } |
2998 | writePS(s: "pop\n" ); |
2999 | str->close(); |
3000 | |
3001 | delete str; |
3002 | } |
3003 | |
3004 | void PSOutputDev::setupForms(Dict *resDict) |
3005 | { |
3006 | if (!preloadImagesForms) { |
3007 | return; |
3008 | } |
3009 | |
3010 | Object xObjDict = resDict->lookup(key: "XObject" ); |
3011 | if (xObjDict.isDict()) { |
3012 | for (int i = 0; i < xObjDict.dictGetLength(); ++i) { |
3013 | const Object &xObjRef = xObjDict.dictGetValNF(i); |
3014 | Object xObj = xObjDict.dictGetVal(i); |
3015 | if (xObj.isStream()) { |
3016 | Object subtypeObj = xObj.streamGetDict()->lookup(key: "Subtype" ); |
3017 | if (subtypeObj.isName(nameA: "Form" )) { |
3018 | if (xObjRef.isRef()) { |
3019 | setupForm(id: xObjRef.getRef(), strObj: &xObj); |
3020 | } else { |
3021 | error(category: errSyntaxError, pos: -1, msg: "Form in resource dict is not an indirect reference" ); |
3022 | } |
3023 | } |
3024 | } |
3025 | } |
3026 | } |
3027 | } |
3028 | |
3029 | void PSOutputDev::setupForm(Ref id, Object *strObj) |
3030 | { |
3031 | Dict *dict, *resDict; |
3032 | double m[6], bbox[4]; |
3033 | PDFRectangle box; |
3034 | Gfx *gfx; |
3035 | |
3036 | // check if form is already defined |
3037 | for (int i = 0; i < formIDLen; ++i) { |
3038 | if (formIDs[i] == id) { |
3039 | return; |
3040 | } |
3041 | } |
3042 | |
3043 | // add entry to formIDs list |
3044 | if (formIDLen >= formIDSize) { |
3045 | if (formIDSize == 0) { |
3046 | formIDSize = 64; |
3047 | } else { |
3048 | formIDSize *= 2; |
3049 | } |
3050 | formIDs = (Ref *)greallocn(p: formIDs, count: formIDSize, size: sizeof(Ref)); |
3051 | } |
3052 | formIDs[formIDLen++] = id; |
3053 | |
3054 | dict = strObj->streamGetDict(); |
3055 | |
3056 | // get bounding box |
3057 | Object bboxObj = dict->lookup(key: "BBox" ); |
3058 | if (!bboxObj.isArray()) { |
3059 | error(category: errSyntaxError, pos: -1, msg: "Bad form bounding box" ); |
3060 | return; |
3061 | } |
3062 | for (int i = 0; i < 4; ++i) { |
3063 | Object obj1 = bboxObj.arrayGet(i); |
3064 | bbox[i] = obj1.getNum(); |
3065 | } |
3066 | |
3067 | // get matrix |
3068 | Object matrixObj = dict->lookup(key: "Matrix" ); |
3069 | if (matrixObj.isArray()) { |
3070 | for (int i = 0; i < 6; ++i) { |
3071 | Object obj1 = matrixObj.arrayGet(i); |
3072 | m[i] = obj1.getNum(); |
3073 | } |
3074 | } else { |
3075 | m[0] = 1; |
3076 | m[1] = 0; |
3077 | m[2] = 0; |
3078 | m[3] = 1; |
3079 | m[4] = 0; |
3080 | m[5] = 0; |
3081 | } |
3082 | |
3083 | // get resources |
3084 | Object resObj = dict->lookup(key: "Resources" ); |
3085 | resDict = resObj.isDict() ? resObj.getDict() : nullptr; |
3086 | |
3087 | writePSFmt(fmt: "/f_{0:d}_{1:d} {{\n" , id.num, id.gen); |
3088 | writePS(s: "q\n" ); |
3089 | writePSFmt(fmt: "[{0:.6gs} {1:.6gs} {2:.6gs} {3:.6gs} {4:.6gs} {5:.6gs}] cm\n" , m[0], m[1], m[2], m[3], m[4], m[5]); |
3090 | |
3091 | box.x1 = bbox[0]; |
3092 | box.y1 = bbox[1]; |
3093 | box.x2 = bbox[2]; |
3094 | box.y2 = bbox[3]; |
3095 | gfx = new Gfx(doc, this, resDict, &box, &box); |
3096 | gfx->display(obj: strObj); |
3097 | delete gfx; |
3098 | |
3099 | writePS(s: "Q\n" ); |
3100 | writePS(s: "} def\n" ); |
3101 | } |
3102 | |
3103 | bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, int rotateA, bool useMediaBox, bool crop, int sliceX, int sliceY, int sliceW, int sliceH, bool printing, bool (*abortCheckCbk)(void *data), |
3104 | void *abortCheckCbkData, bool (*annotDisplayDecideCbk)(Annot *annot, void *user_data), void *annotDisplayDecideCbkData) |
3105 | { |
3106 | PreScanOutputDev *scan; |
3107 | bool rasterize; |
3108 | bool useFlate, useLZW; |
3109 | SplashOutputDev *splashOut; |
3110 | SplashColor paperColor; |
3111 | PDFRectangle box; |
3112 | GfxState *state; |
3113 | SplashBitmap *bitmap; |
3114 | Stream *str0, *str; |
3115 | unsigned char *p; |
3116 | unsigned char col[4]; |
3117 | double hDPI2, vDPI2; |
3118 | double m0, m1, m2, m3, m4, m5; |
3119 | int nStripes, stripeH, stripeY; |
3120 | int c, w, h, x, y, comp, i; |
3121 | int numComps, initialNumComps; |
3122 | char hexBuf[32 * 2 + 2]; // 32 values X 2 chars/value + line ending + null |
3123 | unsigned char digit; |
3124 | bool isOptimizedGray; |
3125 | bool overprint; |
3126 | SplashColorMode internalColorFormat; |
3127 | |
3128 | if (!postInitDone) { |
3129 | postInit(); |
3130 | } |
3131 | if (forceRasterize == psAlwaysRasterize) { |
3132 | rasterize = true; |
3133 | } else if (forceRasterize == psNeverRasterize) { |
3134 | rasterize = false; |
3135 | } else { |
3136 | scan = new PreScanOutputDev(level); |
3137 | page->displaySlice(out: scan, hDPI: 72, vDPI: 72, rotate: rotateA, useMediaBox, crop, sliceX, sliceY, sliceW, sliceH, printing, abortCheckCbk, abortCheckCbkData, annotDisplayDecideCbk, annotDisplayDecideCbkData); |
3138 | rasterize = scan->usesTransparency() || scan->usesPatternImageMask(); |
3139 | delete scan; |
3140 | } |
3141 | if (!rasterize) { |
3142 | return true; |
3143 | } |
3144 | |
3145 | // get the rasterization parameters |
3146 | useFlate = getEnableFlate() && level >= psLevel3; |
3147 | useLZW = getEnableLZW(); |
3148 | // start the PS page |
3149 | page->makeBox(hDPI: rasterResolution, vDPI: rasterResolution, rotate: rotateA, useMediaBox, upsideDown: false, sliceX, sliceY, sliceW, sliceH, box: &box, crop: &crop); |
3150 | rotateA += page->getRotate(); |
3151 | if (rotateA >= 360) { |
3152 | rotateA -= 360; |
3153 | } else if (rotateA < 0) { |
3154 | rotateA += 360; |
3155 | } |
3156 | state = new GfxState(rasterResolution, rasterResolution, &box, rotateA, false); |
3157 | startPage(pageNum: page->getNum(), state, xref); |
3158 | delete state; |
3159 | |
3160 | // If we would not rasterize this page, we would emit the overprint code anyway for language level 2 and upwards. |
3161 | // As such it is safe to assume for a CMYK printer that it would respect the overprint operands. |
3162 | overprint = overprintPreview || (processColorFormat == splashModeCMYK8 && level >= psLevel2); |
3163 | |
3164 | // set up the SplashOutputDev |
3165 | internalColorFormat = processColorFormat; |
3166 | if (processColorFormat == splashModeMono8) { |
3167 | numComps = 1; |
3168 | paperColor[0] = 0xff; |
3169 | } else if (processColorFormat == splashModeCMYK8) { |
3170 | numComps = 4; |
3171 | splashClearColor(dest: paperColor); |
3172 | |
3173 | // If overprinting is emulated, it is not sufficient to just store the CMYK values in a bitmap. |
3174 | // All separation channels need to be stored and collapsed at the end. |
3175 | // Cf. PDF32000_2008 Section 11.7.4.5 and Tables 148, 149 |
3176 | if (overprint) { |
3177 | internalColorFormat = splashModeDeviceN8; |
3178 | } |
3179 | } else if (processColorFormat == splashModeRGB8) { |
3180 | numComps = 3; |
3181 | paperColor[0] = paperColor[1] = paperColor[2] = 0xff; |
3182 | } else { |
3183 | error(category: errUnimplemented, pos: -1, msg: "Unsupported processColorMode. Falling back to RGB8." ); |
3184 | processColorFormat = splashModeRGB8; |
3185 | internalColorFormat = processColorFormat; |
3186 | numComps = 3; |
3187 | paperColor[0] = paperColor[1] = paperColor[2] = 0xff; |
3188 | } |
3189 | splashOut = new SplashOutputDev(internalColorFormat, 1, false, paperColor, false, splashThinLineDefault, overprint); |
3190 | splashOut->setFontAntialias(rasterAntialias); |
3191 | splashOut->setVectorAntialias(rasterAntialias); |
3192 | #ifdef USE_CMS |
3193 | splashOut->setDisplayProfile(getDisplayProfile()); |
3194 | splashOut->setDefaultGrayProfile(getDefaultGrayProfile()); |
3195 | splashOut->setDefaultRGBProfile(getDefaultRGBProfile()); |
3196 | splashOut->setDefaultCMYKProfile(getDefaultCMYKProfile()); |
3197 | #endif |
3198 | splashOut->startDoc(docA: doc); |
3199 | |
3200 | // break the page into stripes |
3201 | hDPI2 = xScale * rasterResolution; |
3202 | vDPI2 = yScale * rasterResolution; |
3203 | if (sliceW < 0 || sliceH < 0) { |
3204 | if (useMediaBox) { |
3205 | box = *page->getMediaBox(); |
3206 | } else { |
3207 | box = *page->getCropBox(); |
3208 | } |
3209 | sliceX = sliceY = 0; |
3210 | sliceW = (int)((box.x2 - box.x1) * hDPI2 / 72.0); |
3211 | sliceH = (int)((box.y2 - box.y1) * vDPI2 / 72.0); |
3212 | } |
3213 | int sliceArea; |
3214 | if (checkedMultiply(x: sliceW, y: sliceH, z: &sliceArea)) { |
3215 | delete splashOut; |
3216 | return false; |
3217 | } |
3218 | nStripes = (int)ceil(x: (double)(sliceArea) / (double)rasterizationSliceSize); |
3219 | if (unlikely(nStripes == 0)) { |
3220 | delete splashOut; |
3221 | return false; |
3222 | } |
3223 | stripeH = (sliceH + nStripes - 1) / nStripes; |
3224 | |
3225 | // render the stripes |
3226 | initialNumComps = numComps; |
3227 | for (stripeY = sliceY; stripeY < sliceH; stripeY += stripeH) { |
3228 | |
3229 | // rasterize a stripe |
3230 | page->makeBox(hDPI: hDPI2, vDPI: vDPI2, rotate: 0, useMediaBox, upsideDown: false, sliceX, sliceY: stripeY, sliceW, sliceH: stripeH, box: &box, crop: &crop); |
3231 | m0 = box.x2 - box.x1; |
3232 | m1 = 0; |
3233 | m2 = 0; |
3234 | m3 = box.y2 - box.y1; |
3235 | m4 = box.x1; |
3236 | m5 = box.y1; |
3237 | page->displaySlice(out: splashOut, hDPI: hDPI2, vDPI: vDPI2, rotate: (360 - page->getRotate()) % 360, useMediaBox, crop, sliceX, sliceY: stripeY, sliceW, sliceH: stripeH, printing, abortCheckCbk, abortCheckCbkData, annotDisplayDecideCbk, annotDisplayDecideCbkData); |
3238 | |
3239 | // draw the rasterized image |
3240 | bitmap = splashOut->getBitmap(); |
3241 | numComps = initialNumComps; |
3242 | w = bitmap->getWidth(); |
3243 | h = bitmap->getHeight(); |
3244 | writePS(s: "gsave\n" ); |
3245 | writePSFmt(fmt: "[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] concat\n" , m0, m1, m2, m3, m4, m5); |
3246 | switch (level) { |
3247 | case psLevel1: |
3248 | writePSFmt(fmt: "{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1{5:s}\n" , w, h, w, -h, h, useBinary ? "Bin" : "" ); |
3249 | p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize(); |
3250 | i = 0; |
3251 | if (useBinary) { |
3252 | for (y = 0; y < h; ++y) { |
3253 | for (x = 0; x < w; ++x) { |
3254 | hexBuf[i++] = *p++; |
3255 | if (i >= 64) { |
3256 | writePSBuf(s: hexBuf, len: i); |
3257 | i = 0; |
3258 | } |
3259 | } |
3260 | } |
3261 | } else { |
3262 | for (y = 0; y < h; ++y) { |
3263 | for (x = 0; x < w; ++x) { |
3264 | digit = *p / 16; |
3265 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
3266 | digit = *p++ % 16; |
3267 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
3268 | if (i >= 64) { |
3269 | hexBuf[i++] = '\n'; |
3270 | writePSBuf(s: hexBuf, len: i); |
3271 | i = 0; |
3272 | } |
3273 | } |
3274 | } |
3275 | } |
3276 | if (i != 0) { |
3277 | if (!useBinary) { |
3278 | hexBuf[i++] = '\n'; |
3279 | } |
3280 | writePSBuf(s: hexBuf, len: i); |
3281 | } |
3282 | break; |
3283 | case psLevel1Sep: |
3284 | p = bitmap->getDataPtr(); |
3285 | // Check for an all gray image |
3286 | if (getOptimizeColorSpace()) { |
3287 | isOptimizedGray = true; |
3288 | for (y = 0; y < h; ++y) { |
3289 | for (x = 0; x < w; ++x) { |
3290 | if (p[4 * x] != p[4 * x + 1] || p[4 * x] != p[4 * x + 2]) { |
3291 | isOptimizedGray = false; |
3292 | y = h; |
3293 | break; |
3294 | } |
3295 | } |
3296 | p += bitmap->getRowSize(); |
3297 | } |
3298 | } else { |
3299 | isOptimizedGray = false; |
3300 | } |
3301 | writePSFmt(fmt: "{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1{5:s}{6:s}\n" , w, h, w, -h, h, isOptimizedGray ? "" : "Sep" , useBinary ? "Bin" : "" ); |
3302 | p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize(); |
3303 | i = 0; |
3304 | col[0] = col[1] = col[2] = col[3] = 0; |
3305 | if (isOptimizedGray) { |
3306 | int g; |
3307 | if ((psProcessBlack & processColors) == 0) { |
3308 | // Check if the image uses black |
3309 | for (y = 0; y < h; ++y) { |
3310 | for (x = 0; x < w; ++x) { |
3311 | if (p[4 * x] > 0 || p[4 * x + 3] > 0) { |
3312 | col[3] = 1; |
3313 | y = h; |
3314 | break; |
3315 | } |
3316 | } |
3317 | p -= bitmap->getRowSize(); |
3318 | } |
3319 | p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize(); |
3320 | } |
3321 | for (y = 0; y < h; ++y) { |
3322 | if (useBinary) { |
3323 | // Binary gray image |
3324 | for (x = 0; x < w; ++x) { |
3325 | g = p[4 * x] + p[4 * x + 3]; |
3326 | g = 255 - g; |
3327 | if (g < 0) { |
3328 | g = 0; |
3329 | } |
3330 | hexBuf[i++] = (unsigned char)g; |
3331 | if (i >= 64) { |
3332 | writePSBuf(s: hexBuf, len: i); |
3333 | i = 0; |
3334 | } |
3335 | } |
3336 | } else { |
3337 | // Hex gray image |
3338 | for (x = 0; x < w; ++x) { |
3339 | g = p[4 * x] + p[4 * x + 3]; |
3340 | g = 255 - g; |
3341 | if (g < 0) { |
3342 | g = 0; |
3343 | } |
3344 | digit = g / 16; |
3345 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
3346 | digit = g % 16; |
3347 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
3348 | if (i >= 64) { |
3349 | hexBuf[i++] = '\n'; |
3350 | writePSBuf(s: hexBuf, len: i); |
3351 | i = 0; |
3352 | } |
3353 | } |
3354 | } |
3355 | p -= bitmap->getRowSize(); |
3356 | } |
3357 | } else if (((psProcessCyan | psProcessMagenta | psProcessYellow | psProcessBlack) & ~processColors) != 0) { |
3358 | // Color image, need to check color flags for each dot |
3359 | for (y = 0; y < h; ++y) { |
3360 | for (comp = 0; comp < 4; ++comp) { |
3361 | if (useBinary) { |
3362 | // Binary color image |
3363 | for (x = 0; x < w; ++x) { |
3364 | col[comp] |= p[4 * x + comp]; |
3365 | hexBuf[i++] = p[4 * x + comp]; |
3366 | if (i >= 64) { |
3367 | writePSBuf(s: hexBuf, len: i); |
3368 | i = 0; |
3369 | } |
3370 | } |
3371 | } else { |
3372 | // Gray color image |
3373 | for (x = 0; x < w; ++x) { |
3374 | col[comp] |= p[4 * x + comp]; |
3375 | digit = p[4 * x + comp] / 16; |
3376 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
3377 | digit = p[4 * x + comp] % 16; |
3378 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
3379 | if (i >= 64) { |
3380 | hexBuf[i++] = '\n'; |
3381 | writePSBuf(s: hexBuf, len: i); |
3382 | i = 0; |
3383 | } |
3384 | } |
3385 | } |
3386 | } |
3387 | p -= bitmap->getRowSize(); |
3388 | } |
3389 | } else { |
3390 | // Color image, do not need to check color flags |
3391 | for (y = 0; y < h; ++y) { |
3392 | for (comp = 0; comp < 4; ++comp) { |
3393 | if (useBinary) { |
3394 | // Binary color image |
3395 | for (x = 0; x < w; ++x) { |
3396 | hexBuf[i++] = p[4 * x + comp]; |
3397 | if (i >= 64) { |
3398 | writePSBuf(s: hexBuf, len: i); |
3399 | i = 0; |
3400 | } |
3401 | } |
3402 | } else { |
3403 | // Hex color image |
3404 | for (x = 0; x < w; ++x) { |
3405 | digit = p[4 * x + comp] / 16; |
3406 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
3407 | digit = p[4 * x + comp] % 16; |
3408 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
3409 | if (i >= 64) { |
3410 | hexBuf[i++] = '\n'; |
3411 | writePSBuf(s: hexBuf, len: i); |
3412 | i = 0; |
3413 | } |
3414 | } |
3415 | } |
3416 | } |
3417 | p -= bitmap->getRowSize(); |
3418 | } |
3419 | } |
3420 | if (i != 0) { |
3421 | if (!useBinary) { |
3422 | hexBuf[i++] = '\n'; |
3423 | } |
3424 | writePSBuf(s: hexBuf, len: i); |
3425 | } |
3426 | if (col[0]) { |
3427 | processColors |= psProcessCyan; |
3428 | } |
3429 | if (col[1]) { |
3430 | processColors |= psProcessMagenta; |
3431 | } |
3432 | if (col[2]) { |
3433 | processColors |= psProcessYellow; |
3434 | } |
3435 | if (col[3]) { |
3436 | processColors |= psProcessBlack; |
3437 | } |
3438 | break; |
3439 | case psLevel2: |
3440 | case psLevel2Sep: |
3441 | case psLevel3: |
3442 | case psLevel3Sep: |
3443 | p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize(); |
3444 | if (processColorFormat == splashModeCMYK8 && internalColorFormat != splashModeCMYK8) { |
3445 | str0 = new SplashBitmapCMYKEncoder(bitmap); |
3446 | } else { |
3447 | str0 = new MemStream((char *)p, 0, w * h * numComps, Object(objNull)); |
3448 | } |
3449 | // Check for a color image that uses only gray |
3450 | if (!getOptimizeColorSpace()) { |
3451 | isOptimizedGray = false; |
3452 | } else if (numComps == 4) { |
3453 | int compCyan; |
3454 | isOptimizedGray = true; |
3455 | while ((compCyan = str0->getChar()) != EOF) { |
3456 | if (str0->getChar() != compCyan || str0->getChar() != compCyan) { |
3457 | isOptimizedGray = false; |
3458 | break; |
3459 | } |
3460 | str0->getChar(); |
3461 | } |
3462 | } else if (numComps == 3) { |
3463 | int compRed; |
3464 | isOptimizedGray = true; |
3465 | while ((compRed = str0->getChar()) != EOF) { |
3466 | if (str0->getChar() != compRed || str0->getChar() != compRed) { |
3467 | isOptimizedGray = false; |
3468 | break; |
3469 | } |
3470 | } |
3471 | } else { |
3472 | isOptimizedGray = false; |
3473 | } |
3474 | str0->reset(); |
3475 | if (useFlate) { |
3476 | if (isOptimizedGray && numComps == 4) { |
3477 | str = new FlateEncoder(new CMYKGrayEncoder(str0)); |
3478 | numComps = 1; |
3479 | } else if (isOptimizedGray && numComps == 3) { |
3480 | str = new FlateEncoder(new RGBGrayEncoder(str0)); |
3481 | numComps = 1; |
3482 | } else { |
3483 | str = new FlateEncoder(str0); |
3484 | } |
3485 | } else if (useLZW) { |
3486 | if (isOptimizedGray && numComps == 4) { |
3487 | str = new LZWEncoder(new CMYKGrayEncoder(str0)); |
3488 | numComps = 1; |
3489 | } else if (isOptimizedGray && numComps == 3) { |
3490 | str = new LZWEncoder(new RGBGrayEncoder(str0)); |
3491 | numComps = 1; |
3492 | } else { |
3493 | str = new LZWEncoder(str0); |
3494 | } |
3495 | } else { |
3496 | if (isOptimizedGray && numComps == 4) { |
3497 | str = new RunLengthEncoder(new CMYKGrayEncoder(str0)); |
3498 | numComps = 1; |
3499 | } else if (isOptimizedGray && numComps == 3) { |
3500 | str = new RunLengthEncoder(new RGBGrayEncoder(str0)); |
3501 | numComps = 1; |
3502 | } else { |
3503 | str = new RunLengthEncoder(str0); |
3504 | } |
3505 | } |
3506 | if (numComps == 1) { |
3507 | writePS(s: "/DeviceGray setcolorspace\n" ); |
3508 | } else if (numComps == 3) { |
3509 | writePS(s: "/DeviceRGB setcolorspace\n" ); |
3510 | } else { |
3511 | writePS(s: "/DeviceCMYK setcolorspace\n" ); |
3512 | } |
3513 | writePS(s: "<<\n /ImageType 1\n" ); |
3514 | writePSFmt(fmt: " /Width {0:d}\n" , bitmap->getWidth()); |
3515 | writePSFmt(fmt: " /Height {0:d}\n" , bitmap->getHeight()); |
3516 | writePSFmt(fmt: " /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n" , w, -h, h); |
3517 | writePS(s: " /BitsPerComponent 8\n" ); |
3518 | if (numComps == 1) { |
3519 | // the optimized gray variants are implemented as a subtractive color space, |
3520 | // such that the range is flipped for them |
3521 | if (isOptimizedGray) { |
3522 | writePS(s: " /Decode [1 0]\n" ); |
3523 | } else { |
3524 | writePS(s: " /Decode [0 1]\n" ); |
3525 | } |
3526 | } else if (numComps == 3) { |
3527 | writePS(s: " /Decode [0 1 0 1 0 1]\n" ); |
3528 | } else { |
3529 | writePS(s: " /Decode [0 1 0 1 0 1 0 1]\n" ); |
3530 | } |
3531 | writePS(s: " /DataSource currentfile\n" ); |
3532 | if (useBinary) { |
3533 | /* nothing to do */; |
3534 | } else if (useASCIIHex) { |
3535 | writePS(s: " /ASCIIHexDecode filter\n" ); |
3536 | } else { |
3537 | writePS(s: " /ASCII85Decode filter\n" ); |
3538 | } |
3539 | if (useFlate) { |
3540 | writePS(s: " /FlateDecode filter\n" ); |
3541 | } else if (useLZW) { |
3542 | writePS(s: " /LZWDecode filter\n" ); |
3543 | } else { |
3544 | writePS(s: " /RunLengthDecode filter\n" ); |
3545 | } |
3546 | writePS(s: ">>\n" ); |
3547 | if (useBinary) { |
3548 | /* nothing to do */; |
3549 | } else if (useASCIIHex) { |
3550 | str = new ASCIIHexEncoder(str); |
3551 | } else { |
3552 | str = new ASCII85Encoder(str); |
3553 | } |
3554 | str->reset(); |
3555 | if (useBinary) { |
3556 | // Count the bytes to write a document comment |
3557 | int len = 0; |
3558 | while (str->getChar() != EOF) { |
3559 | len++; |
3560 | } |
3561 | str->reset(); |
3562 | writePSFmt(fmt: "%%BeginData: {0:d} Binary Bytes\n" , len + 6 + 1); |
3563 | } |
3564 | writePS(s: "image\n" ); |
3565 | while ((c = str->getChar()) != EOF) { |
3566 | writePSChar(c); |
3567 | } |
3568 | str->close(); |
3569 | delete str; |
3570 | delete str0; |
3571 | writePSChar(c: '\n'); |
3572 | if (useBinary) { |
3573 | writePS(s: "%%EndData\n" ); |
3574 | } |
3575 | processColors |= (numComps == 1) ? psProcessBlack : psProcessCMYK; |
3576 | break; |
3577 | } |
3578 | writePS(s: "grestore\n" ); |
3579 | } |
3580 | |
3581 | delete splashOut; |
3582 | |
3583 | // finish the PS page |
3584 | endPage(); |
3585 | |
3586 | return false; |
3587 | } |
3588 | |
3589 | void PSOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA) |
3590 | { |
3591 | Page *page; |
3592 | int x1, y1, x2, y2, width, height, t; |
3593 | int imgWidth, imgHeight, imgWidth2, imgHeight2; |
3594 | bool landscape; |
3595 | GooString *s; |
3596 | |
3597 | if (!postInitDone) { |
3598 | postInit(); |
3599 | } |
3600 | xref = xrefA; |
3601 | if (mode == psModePS) { |
3602 | GooString pageLabel; |
3603 | const bool gotLabel = doc->getCatalog()->indexToLabel(index: pageNum - 1, label: &pageLabel); |
3604 | if (gotLabel) { |
3605 | // See bug13338 for why we try to avoid parentheses... |
3606 | bool needParens; |
3607 | GooString *filteredString = filterPSLabel(label: &pageLabel, needParens: &needParens); |
3608 | if (needParens) { |
3609 | writePSFmt(fmt: "%%Page: ({0:t}) {1:d}\n" , filteredString, seqPage); |
3610 | } else { |
3611 | writePSFmt(fmt: "%%Page: {0:t} {1:d}\n" , filteredString, seqPage); |
3612 | } |
3613 | delete filteredString; |
3614 | } else { |
3615 | writePSFmt(fmt: "%%Page: {0:d} {1:d}\n" , pageNum, seqPage); |
3616 | } |
3617 | if (paperMatch) { |
3618 | page = doc->getCatalog()->getPage(i: pageNum); |
3619 | imgLLX = imgLLY = 0; |
3620 | if (noCrop) { |
3621 | imgURX = (int)ceil(x: page->getMediaWidth()); |
3622 | imgURY = (int)ceil(x: page->getMediaHeight()); |
3623 | } else { |
3624 | imgURX = (int)ceil(x: page->getCropWidth()); |
3625 | imgURY = (int)ceil(x: page->getCropHeight()); |
3626 | } |
3627 | if (state->getRotate() == 90 || state->getRotate() == 270) { |
3628 | t = imgURX; |
3629 | imgURX = imgURY; |
3630 | imgURY = t; |
3631 | } |
3632 | } |
3633 | } |
3634 | |
3635 | // underlays |
3636 | if (underlayCbk) { |
3637 | (*underlayCbk)(this, underlayCbkData); |
3638 | } |
3639 | if (overlayCbk) { |
3640 | saveState(state: nullptr); |
3641 | } |
3642 | |
3643 | xScale = yScale = 1; |
3644 | switch (mode) { |
3645 | |
3646 | case psModePS: |
3647 | // rotate, translate, and scale page |
3648 | imgWidth = imgURX - imgLLX; |
3649 | imgHeight = imgURY - imgLLY; |
3650 | x1 = (int)floor(x: state->getX1()); |
3651 | y1 = (int)floor(x: state->getY1()); |
3652 | x2 = (int)ceil(x: state->getX2()); |
3653 | y2 = (int)ceil(x: state->getY2()); |
3654 | if (unlikely(checkedSubtraction(x2, x1, &width))) { |
3655 | error(category: errSyntaxError, pos: -1, msg: "width too big" ); |
3656 | return; |
3657 | } |
3658 | if (unlikely(checkedSubtraction(y2, y1, &height))) { |
3659 | error(category: errSyntaxError, pos: -1, msg: "height too big" ); |
3660 | return; |
3661 | } |
3662 | tx = ty = 0; |
3663 | // rotation and portrait/landscape mode |
3664 | if (paperMatch) { |
3665 | rotate = (360 - state->getRotate()) % 360; |
3666 | landscape = false; |
3667 | } else if (rotate0 >= 0) { |
3668 | rotate = (360 - rotate0) % 360; |
3669 | landscape = false; |
3670 | } else { |
3671 | rotate = (360 - state->getRotate()) % 360; |
3672 | if (rotate == 0 || rotate == 180) { |
3673 | if ((width < height && imgWidth > imgHeight && height > imgHeight) || (width > height && imgWidth < imgHeight && width > imgWidth)) { |
3674 | rotate += 90; |
3675 | landscape = true; |
3676 | } else { |
3677 | landscape = false; |
3678 | } |
3679 | } else { // rotate == 90 || rotate == 270 |
3680 | if ((height < width && imgWidth > imgHeight && width > imgHeight) || (height > width && imgWidth < imgHeight && height > imgWidth)) { |
3681 | rotate = 270 - rotate; |
3682 | landscape = true; |
3683 | } else { |
3684 | landscape = false; |
3685 | } |
3686 | } |
3687 | } |
3688 | if (rotate == 0) { |
3689 | imgWidth2 = imgWidth; |
3690 | imgHeight2 = imgHeight; |
3691 | } else if (rotate == 90) { |
3692 | ty = -imgWidth; |
3693 | imgWidth2 = imgHeight; |
3694 | imgHeight2 = imgWidth; |
3695 | } else if (rotate == 180) { |
3696 | imgWidth2 = imgWidth; |
3697 | imgHeight2 = imgHeight; |
3698 | tx = -imgWidth; |
3699 | ty = -imgHeight; |
3700 | } else { // rotate == 270 |
3701 | tx = -imgHeight; |
3702 | imgWidth2 = imgHeight; |
3703 | imgHeight2 = imgWidth; |
3704 | } |
3705 | // shrink or expand |
3706 | if (xScale0 > 0 && yScale0 > 0) { |
3707 | xScale = xScale0; |
3708 | yScale = yScale0; |
3709 | } else if ((psShrinkLarger && (width > imgWidth2 || height > imgHeight2)) || (psExpandSmaller && (width < imgWidth2 && height < imgHeight2))) { |
3710 | if (unlikely(width == 0)) { |
3711 | error(category: errSyntaxError, pos: -1, msg: "width 0, xScale would be infinite" ); |
3712 | return; |
3713 | } |
3714 | xScale = (double)imgWidth2 / (double)width; |
3715 | yScale = (double)imgHeight2 / (double)height; |
3716 | if (yScale < xScale) { |
3717 | xScale = yScale; |
3718 | } else { |
3719 | yScale = xScale; |
3720 | } |
3721 | } |
3722 | // deal with odd bounding boxes or clipping |
3723 | if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) { |
3724 | tx -= xScale * clipLLX0; |
3725 | ty -= yScale * clipLLY0; |
3726 | } else { |
3727 | tx -= xScale * x1; |
3728 | ty -= yScale * y1; |
3729 | } |
3730 | // center |
3731 | if (tx0 >= 0 && ty0 >= 0) { |
3732 | tx += (rotate == 0 || rotate == 180) ? tx0 : ty0; |
3733 | ty += (rotate == 0 || rotate == 180) ? ty0 : -tx0; |
3734 | } else if (psCenter) { |
3735 | if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) { |
3736 | tx += (imgWidth2 - xScale * (clipURX0 - clipLLX0)) / 2; |
3737 | ty += (imgHeight2 - yScale * (clipURY0 - clipLLY0)) / 2; |
3738 | } else { |
3739 | tx += (imgWidth2 - xScale * width) / 2; |
3740 | ty += (imgHeight2 - yScale * height) / 2; |
3741 | } |
3742 | } |
3743 | tx += (rotate == 0 || rotate == 180) ? imgLLX : imgLLY; |
3744 | ty += (rotate == 0 || rotate == 180) ? imgLLY : -imgLLX; |
3745 | |
3746 | if (paperMatch) { |
3747 | const PSOutPaperSize &paperSize = paperSizes[pagePaperSize[pageNum]]; |
3748 | writePSFmt(fmt: "%%PageMedia: {0:s}\n" , paperSize.name.c_str()); |
3749 | } |
3750 | |
3751 | // Create a matrix with the same transform that will be output to PS |
3752 | Matrix m; |
3753 | switch (rotate) { |
3754 | default: |
3755 | case 0: |
3756 | m.init(xx: 1, yx: 0, xy: 0, yy: 1, x0: 0, y0: 0); |
3757 | break; |
3758 | case 90: |
3759 | m.init(xx: 0, yx: 1, xy: -1, yy: 0, x0: 0, y0: 0); |
3760 | break; |
3761 | case 180: |
3762 | m.init(xx: -1, yx: 0, xy: 0, yy: -1, x0: 0, y0: 0); |
3763 | break; |
3764 | case 270: |
3765 | m.init(xx: 0, yx: -1, xy: 1, yy: 0, x0: 0, y0: 0); |
3766 | break; |
3767 | } |
3768 | m.translate(tx, ty); |
3769 | m.scale(sx: xScale, sy: yScale); |
3770 | |
3771 | double bboxX1, bboxY1, bboxX2, bboxY2; |
3772 | m.transform(x: 0, y: 0, tx: &bboxX1, ty: &bboxY1); |
3773 | m.transform(x: width, y: height, tx: &bboxX2, ty: &bboxY2); |
3774 | |
3775 | writePSFmt(fmt: "%%PageBoundingBox: {0:g} {1:g} {2:g} {3:g}\n" , floor(x: std::min(a: bboxX1, b: bboxX2)), floor(x: std::min(a: bboxY1, b: bboxY2)), ceil(x: std::max(a: bboxX1, b: bboxX2)), ceil(x: std::max(a: bboxY1, b: bboxY2))); |
3776 | |
3777 | writePSFmt(fmt: "%%PageOrientation: {0:s}\n" , landscape ? "Landscape" : "Portrait" ); |
3778 | writePS(s: "%%BeginPageSetup\n" ); |
3779 | if (paperMatch) { |
3780 | writePSFmt(fmt: "{0:d} {1:d} pdfSetupPaper\n" , imgURX, imgURY); |
3781 | } |
3782 | writePS(s: "pdfStartPage\n" ); |
3783 | if (rotate) { |
3784 | writePSFmt(fmt: "{0:d} rotate\n" , rotate); |
3785 | } |
3786 | if (tx != 0 || ty != 0) { |
3787 | writePSFmt(fmt: "{0:.6g} {1:.6g} translate\n" , tx, ty); |
3788 | } |
3789 | if (xScale != 1 || yScale != 1) { |
3790 | writePSFmt(fmt: "{0:.6f} {1:.6f} scale\n" , xScale, yScale); |
3791 | } |
3792 | if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) { |
3793 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} {3:.6g} re W\n" , clipLLX0, clipLLY0, clipURX0 - clipLLX0, clipURY0 - clipLLY0); |
3794 | } else { |
3795 | writePSFmt(fmt: "{0:d} {1:d} {2:d} {3:d} re W\n" , x1, y1, x2 - x1, y2 - y1); |
3796 | } |
3797 | |
3798 | ++seqPage; |
3799 | break; |
3800 | |
3801 | case psModeEPS: |
3802 | writePS(s: "pdfStartPage\n" ); |
3803 | tx = ty = 0; |
3804 | rotate = (360 - state->getRotate()) % 360; |
3805 | if (rotate == 0) { |
3806 | } else if (rotate == 90) { |
3807 | writePS(s: "90 rotate\n" ); |
3808 | tx = -epsX1; |
3809 | ty = -epsY2; |
3810 | } else if (rotate == 180) { |
3811 | writePS(s: "180 rotate\n" ); |
3812 | tx = -(epsX1 + epsX2); |
3813 | ty = -(epsY1 + epsY2); |
3814 | } else { // rotate == 270 |
3815 | writePS(s: "270 rotate\n" ); |
3816 | tx = -epsX2; |
3817 | ty = -epsY1; |
3818 | } |
3819 | if (tx != 0 || ty != 0) { |
3820 | writePSFmt(fmt: "{0:.6g} {1:.6g} translate\n" , tx, ty); |
3821 | } |
3822 | break; |
3823 | |
3824 | case psModeForm: |
3825 | writePS(s: "/PaintProc {\n" ); |
3826 | writePS(s: "begin xpdf begin\n" ); |
3827 | writePS(s: "pdfStartPage\n" ); |
3828 | tx = ty = 0; |
3829 | rotate = 0; |
3830 | break; |
3831 | } |
3832 | |
3833 | if (customCodeCbk) { |
3834 | if ((s = (*customCodeCbk)(this, psOutCustomPageSetup, pageNum, customCodeCbkData))) { |
3835 | writePS(s: s->c_str()); |
3836 | delete s; |
3837 | } |
3838 | } |
3839 | |
3840 | writePS(s: "%%EndPageSetup\n" ); |
3841 | } |
3842 | |
3843 | void PSOutputDev::endPage() |
3844 | { |
3845 | if (overlayCbk) { |
3846 | restoreState(state: nullptr); |
3847 | (*overlayCbk)(this, overlayCbkData); |
3848 | } |
3849 | |
3850 | for (const auto &item : iccEmitted) { |
3851 | writePSFmt(fmt: "userdict /{0:s} undef\n" , item.c_str()); |
3852 | } |
3853 | iccEmitted.clear(); |
3854 | |
3855 | if (mode == psModeForm) { |
3856 | writePS(s: "pdfEndPage\n" ); |
3857 | writePS(s: "end end\n" ); |
3858 | writePS(s: "} def\n" ); |
3859 | writePS(s: "end end\n" ); |
3860 | } else { |
3861 | if (!manualCtrl) { |
3862 | writePS(s: "showpage\n" ); |
3863 | } |
3864 | writePS(s: "%%PageTrailer\n" ); |
3865 | writePageTrailer(); |
3866 | } |
3867 | } |
3868 | |
3869 | void PSOutputDev::saveState(GfxState *state) |
3870 | { |
3871 | writePS(s: "q\n" ); |
3872 | ++numSaves; |
3873 | } |
3874 | |
3875 | void PSOutputDev::restoreState(GfxState *state) |
3876 | { |
3877 | writePS(s: "Q\n" ); |
3878 | --numSaves; |
3879 | } |
3880 | |
3881 | void PSOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32) |
3882 | { |
3883 | writePSFmt(fmt: "[{0:.6gs} {1:.6gs} {2:.6gs} {3:.6gs} {4:.6gs} {5:.6gs}] cm\n" , m11, m12, m21, m22, m31, m32); |
3884 | } |
3885 | |
3886 | void PSOutputDev::updateLineDash(GfxState *state) |
3887 | { |
3888 | double start; |
3889 | |
3890 | const std::vector<double> &dash = state->getLineDash(start: &start); |
3891 | writePS(s: "[" ); |
3892 | for (std::vector<double>::size_type i = 0; i < dash.size(); ++i) { |
3893 | writePSFmt(fmt: "{0:.6g}{1:w}" , dash[i] < 0 ? 0 : dash[i], (i == dash.size() - 1) ? 0 : 1); |
3894 | } |
3895 | writePSFmt(fmt: "] {0:.6g} d\n" , start); |
3896 | } |
3897 | |
3898 | void PSOutputDev::updateFlatness(GfxState *state) |
3899 | { |
3900 | writePSFmt(fmt: "{0:d} i\n" , state->getFlatness()); |
3901 | } |
3902 | |
3903 | void PSOutputDev::updateLineJoin(GfxState *state) |
3904 | { |
3905 | writePSFmt(fmt: "{0:d} j\n" , state->getLineJoin()); |
3906 | } |
3907 | |
3908 | void PSOutputDev::updateLineCap(GfxState *state) |
3909 | { |
3910 | writePSFmt(fmt: "{0:d} J\n" , state->getLineCap()); |
3911 | } |
3912 | |
3913 | void PSOutputDev::updateMiterLimit(GfxState *state) |
3914 | { |
3915 | writePSFmt(fmt: "{0:.6g} M\n" , state->getMiterLimit()); |
3916 | } |
3917 | |
3918 | void PSOutputDev::updateLineWidth(GfxState *state) |
3919 | { |
3920 | writePSFmt(fmt: "{0:.6g} w\n" , state->getLineWidth()); |
3921 | } |
3922 | |
3923 | void PSOutputDev::updateFillColorSpace(GfxState *state) |
3924 | { |
3925 | if (inUncoloredPattern) { |
3926 | return; |
3927 | } |
3928 | switch (level) { |
3929 | case psLevel1: |
3930 | case psLevel1Sep: |
3931 | break; |
3932 | case psLevel2: |
3933 | case psLevel3: |
3934 | if (state->getFillColorSpace()->getMode() != csPattern) { |
3935 | dumpColorSpaceL2(state, colorSpace: state->getFillColorSpace(), genXform: true, updateColors: false, map01: false); |
3936 | writePS(s: " cs\n" ); |
3937 | } |
3938 | break; |
3939 | case psLevel2Sep: |
3940 | case psLevel3Sep: |
3941 | break; |
3942 | } |
3943 | } |
3944 | |
3945 | void PSOutputDev::updateStrokeColorSpace(GfxState *state) |
3946 | { |
3947 | if (inUncoloredPattern) { |
3948 | return; |
3949 | } |
3950 | switch (level) { |
3951 | case psLevel1: |
3952 | case psLevel1Sep: |
3953 | break; |
3954 | case psLevel2: |
3955 | case psLevel3: |
3956 | if (state->getStrokeColorSpace()->getMode() != csPattern) { |
3957 | dumpColorSpaceL2(state, colorSpace: state->getStrokeColorSpace(), genXform: true, updateColors: false, map01: false); |
3958 | writePS(s: " CS\n" ); |
3959 | } |
3960 | break; |
3961 | case psLevel2Sep: |
3962 | case psLevel3Sep: |
3963 | break; |
3964 | } |
3965 | } |
3966 | |
3967 | void PSOutputDev::updateFillColor(GfxState *state) |
3968 | { |
3969 | GfxColor color; |
3970 | GfxGray gray; |
3971 | GfxCMYK cmyk; |
3972 | GfxSeparationColorSpace *sepCS; |
3973 | double c, m, y, k; |
3974 | int i; |
3975 | |
3976 | if (inUncoloredPattern) { |
3977 | return; |
3978 | } |
3979 | switch (level) { |
3980 | case psLevel1: |
3981 | state->getFillGray(gray: &gray); |
3982 | writePSFmt(fmt: "{0:.4g} g\n" , colToDbl(x: gray)); |
3983 | break; |
3984 | case psLevel2: |
3985 | case psLevel3: |
3986 | if (state->getFillColorSpace()->getMode() != csPattern) { |
3987 | const GfxColor *colorPtr = state->getFillColor(); |
3988 | writePS(s: "[" ); |
3989 | for (i = 0; i < state->getFillColorSpace()->getNComps(); ++i) { |
3990 | if (i > 0) { |
3991 | writePS(s: " " ); |
3992 | } |
3993 | writePSFmt(fmt: "{0:.4g}" , colToDbl(x: colorPtr->c[i])); |
3994 | } |
3995 | writePS(s: "] sc\n" ); |
3996 | } |
3997 | break; |
3998 | case psLevel1Sep: |
3999 | case psLevel2Sep: |
4000 | case psLevel3Sep: |
4001 | if (state->getFillColorSpace()->getMode() == csSeparation && (level > psLevel1Sep || getPassLevel1CustomColor())) { |
4002 | sepCS = (GfxSeparationColorSpace *)state->getFillColorSpace(); |
4003 | color.c[0] = gfxColorComp1; |
4004 | sepCS->getCMYK(color: &color, cmyk: &cmyk); |
4005 | writePSFmt(fmt: "{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) ck\n" , colToDbl(x: state->getFillColor()->c[0]), colToDbl(x: cmyk.c), colToDbl(x: cmyk.m), colToDbl(x: cmyk.y), colToDbl(x: cmyk.k), sepCS->getName()); |
4006 | addCustomColor(sepCS); |
4007 | } else { |
4008 | state->getFillCMYK(cmyk: &cmyk); |
4009 | c = colToDbl(x: cmyk.c); |
4010 | m = colToDbl(x: cmyk.m); |
4011 | y = colToDbl(x: cmyk.y); |
4012 | k = colToDbl(x: cmyk.k); |
4013 | if (getOptimizeColorSpace()) { |
4014 | double g; |
4015 | g = 0.299 * c + 0.587 * m + 0.114 * y; |
4016 | if ((fabs(x: m - c) < 0.01 && fabs(x: m - y) < 0.01) || (fabs(x: m - c) < 0.2 && fabs(x: m - y) < 0.2 && k + g > 1.5)) { |
4017 | c = m = y = 0.0; |
4018 | k += g; |
4019 | if (k > 1.0) { |
4020 | k = 1.0; |
4021 | } |
4022 | } |
4023 | } |
4024 | writePSFmt(fmt: "{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n" , c, m, y, k); |
4025 | addProcessColor(c, m, y, k); |
4026 | } |
4027 | break; |
4028 | } |
4029 | t3Cacheable = false; |
4030 | } |
4031 | |
4032 | void PSOutputDev::updateStrokeColor(GfxState *state) |
4033 | { |
4034 | GfxColor color; |
4035 | GfxGray gray; |
4036 | GfxCMYK cmyk; |
4037 | GfxSeparationColorSpace *sepCS; |
4038 | double c, m, y, k; |
4039 | int i; |
4040 | |
4041 | if (inUncoloredPattern) { |
4042 | return; |
4043 | } |
4044 | switch (level) { |
4045 | case psLevel1: |
4046 | state->getStrokeGray(gray: &gray); |
4047 | writePSFmt(fmt: "{0:.4g} G\n" , colToDbl(x: gray)); |
4048 | break; |
4049 | case psLevel2: |
4050 | case psLevel3: |
4051 | if (state->getStrokeColorSpace()->getMode() != csPattern) { |
4052 | const GfxColor *colorPtr = state->getStrokeColor(); |
4053 | writePS(s: "[" ); |
4054 | for (i = 0; i < state->getStrokeColorSpace()->getNComps(); ++i) { |
4055 | if (i > 0) { |
4056 | writePS(s: " " ); |
4057 | } |
4058 | writePSFmt(fmt: "{0:.4g}" , colToDbl(x: colorPtr->c[i])); |
4059 | } |
4060 | writePS(s: "] SC\n" ); |
4061 | } |
4062 | break; |
4063 | case psLevel1Sep: |
4064 | case psLevel2Sep: |
4065 | case psLevel3Sep: |
4066 | if (state->getStrokeColorSpace()->getMode() == csSeparation && (level > psLevel1Sep || getPassLevel1CustomColor())) { |
4067 | sepCS = (GfxSeparationColorSpace *)state->getStrokeColorSpace(); |
4068 | color.c[0] = gfxColorComp1; |
4069 | sepCS->getCMYK(color: &color, cmyk: &cmyk); |
4070 | writePSFmt(fmt: "{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) CK\n" , colToDbl(x: state->getStrokeColor()->c[0]), colToDbl(x: cmyk.c), colToDbl(x: cmyk.m), colToDbl(x: cmyk.y), colToDbl(x: cmyk.k), sepCS->getName()); |
4071 | addCustomColor(sepCS); |
4072 | } else { |
4073 | state->getStrokeCMYK(cmyk: &cmyk); |
4074 | c = colToDbl(x: cmyk.c); |
4075 | m = colToDbl(x: cmyk.m); |
4076 | y = colToDbl(x: cmyk.y); |
4077 | k = colToDbl(x: cmyk.k); |
4078 | if (getOptimizeColorSpace()) { |
4079 | double g; |
4080 | g = 0.299 * c + 0.587 * m + 0.114 * y; |
4081 | if ((fabs(x: m - c) < 0.01 && fabs(x: m - y) < 0.01) || (fabs(x: m - c) < 0.2 && fabs(x: m - y) < 0.2 && k + g > 1.5)) { |
4082 | c = m = y = 0.0; |
4083 | k += g; |
4084 | if (k > 1.0) { |
4085 | k = 1.0; |
4086 | } |
4087 | } |
4088 | } |
4089 | writePSFmt(fmt: "{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n" , c, m, y, k); |
4090 | addProcessColor(c, m, y, k); |
4091 | } |
4092 | break; |
4093 | } |
4094 | t3Cacheable = false; |
4095 | } |
4096 | |
4097 | void PSOutputDev::addProcessColor(double c, double m, double y, double k) |
4098 | { |
4099 | if (c > 0) { |
4100 | processColors |= psProcessCyan; |
4101 | } |
4102 | if (m > 0) { |
4103 | processColors |= psProcessMagenta; |
4104 | } |
4105 | if (y > 0) { |
4106 | processColors |= psProcessYellow; |
4107 | } |
4108 | if (k > 0) { |
4109 | processColors |= psProcessBlack; |
4110 | } |
4111 | } |
4112 | |
4113 | void PSOutputDev::addCustomColor(GfxSeparationColorSpace *sepCS) |
4114 | { |
4115 | PSOutCustomColor *cc; |
4116 | GfxColor color; |
4117 | GfxCMYK cmyk; |
4118 | |
4119 | if (!sepCS->getName()->cmp(sA: "Black" )) { |
4120 | processColors |= psProcessBlack; |
4121 | return; |
4122 | } |
4123 | if (!sepCS->getName()->cmp(sA: "Cyan" )) { |
4124 | processColors |= psProcessCyan; |
4125 | return; |
4126 | } |
4127 | if (!sepCS->getName()->cmp(sA: "Yellow" )) { |
4128 | processColors |= psProcessYellow; |
4129 | return; |
4130 | } |
4131 | if (!sepCS->getName()->cmp(sA: "Magenta" )) { |
4132 | processColors |= psProcessMagenta; |
4133 | return; |
4134 | } |
4135 | if (!sepCS->getName()->cmp(sA: "All" )) { |
4136 | return; |
4137 | } |
4138 | if (!sepCS->getName()->cmp(sA: "None" )) { |
4139 | return; |
4140 | } |
4141 | for (cc = customColors; cc; cc = cc->next) { |
4142 | if (!cc->name->cmp(str: sepCS->getName())) { |
4143 | return; |
4144 | } |
4145 | } |
4146 | color.c[0] = gfxColorComp1; |
4147 | sepCS->getCMYK(color: &color, cmyk: &cmyk); |
4148 | cc = new PSOutCustomColor(colToDbl(x: cmyk.c), colToDbl(x: cmyk.m), colToDbl(x: cmyk.y), colToDbl(x: cmyk.k), sepCS->getName()->copy()); |
4149 | cc->next = customColors; |
4150 | customColors = cc; |
4151 | } |
4152 | |
4153 | void PSOutputDev::updateFillOverprint(GfxState *state) |
4154 | { |
4155 | if (level >= psLevel2) { |
4156 | writePSFmt(fmt: "{0:s} op\n" , state->getFillOverprint() ? "true" : "false" ); |
4157 | } |
4158 | } |
4159 | |
4160 | void PSOutputDev::updateStrokeOverprint(GfxState *state) |
4161 | { |
4162 | if (level >= psLevel2) { |
4163 | writePSFmt(fmt: "{0:s} OP\n" , state->getStrokeOverprint() ? "true" : "false" ); |
4164 | } |
4165 | } |
4166 | |
4167 | void PSOutputDev::updateOverprintMode(GfxState *state) |
4168 | { |
4169 | if (level >= psLevel3) { |
4170 | writePSFmt(fmt: "{0:s} opm\n" , state->getOverprintMode() ? "true" : "false" ); |
4171 | } |
4172 | } |
4173 | |
4174 | void PSOutputDev::updateTransfer(GfxState *state) |
4175 | { |
4176 | Function **funcs; |
4177 | int i; |
4178 | |
4179 | funcs = state->getTransfer(); |
4180 | if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) { |
4181 | if (level >= psLevel2) { |
4182 | for (i = 0; i < 4; ++i) { |
4183 | cvtFunction(func: funcs[i]); |
4184 | } |
4185 | writePS(s: "setcolortransfer\n" ); |
4186 | } else { |
4187 | cvtFunction(func: funcs[3]); |
4188 | writePS(s: "settransfer\n" ); |
4189 | } |
4190 | } else if (funcs[0]) { |
4191 | cvtFunction(func: funcs[0]); |
4192 | writePS(s: "settransfer\n" ); |
4193 | } else { |
4194 | writePS(s: "{} settransfer\n" ); |
4195 | } |
4196 | } |
4197 | |
4198 | void PSOutputDev::updateFont(GfxState *state) |
4199 | { |
4200 | if (state->getFont()) { |
4201 | writePSFmt(fmt: "/F{0:d}_{1:d} {2:.6g} Tf\n" , state->getFont()->getID()->num, state->getFont()->getID()->gen, fabs(x: state->getFontSize()) < 0.0001 ? 0.0001 : state->getFontSize()); |
4202 | } |
4203 | } |
4204 | |
4205 | void PSOutputDev::updateTextMat(GfxState *state) |
4206 | { |
4207 | const double *mat = state->getTextMat(); |
4208 | if (fabs(x: mat[0] * mat[3] - mat[1] * mat[2]) < 0.00001) { |
4209 | // avoid a singular (or close-to-singular) matrix |
4210 | writePSFmt(fmt: "[0.00001 0 0 0.00001 {0:.6g} {1:.6g}] Tm\n" , mat[4], mat[5]); |
4211 | } else { |
4212 | writePSFmt(fmt: "[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] Tm\n" , mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); |
4213 | } |
4214 | } |
4215 | |
4216 | void PSOutputDev::updateCharSpace(GfxState *state) |
4217 | { |
4218 | writePSFmt(fmt: "{0:.6g} Tc\n" , state->getCharSpace()); |
4219 | } |
4220 | |
4221 | void PSOutputDev::updateRender(GfxState *state) |
4222 | { |
4223 | int rm; |
4224 | |
4225 | rm = state->getRender(); |
4226 | writePSFmt(fmt: "{0:d} Tr\n" , rm); |
4227 | rm &= 3; |
4228 | if (rm != 0 && rm != 3) { |
4229 | t3Cacheable = false; |
4230 | } |
4231 | } |
4232 | |
4233 | void PSOutputDev::updateRise(GfxState *state) |
4234 | { |
4235 | writePSFmt(fmt: "{0:.6g} Ts\n" , state->getRise()); |
4236 | } |
4237 | |
4238 | void PSOutputDev::updateWordSpace(GfxState *state) |
4239 | { |
4240 | writePSFmt(fmt: "{0:.6g} Tw\n" , state->getWordSpace()); |
4241 | } |
4242 | |
4243 | void PSOutputDev::updateHorizScaling(GfxState *state) |
4244 | { |
4245 | double h; |
4246 | |
4247 | h = state->getHorizScaling(); |
4248 | if (fabs(x: h) < 0.01) { |
4249 | h = 0.01; |
4250 | } |
4251 | writePSFmt(fmt: "{0:.6g} Tz\n" , h); |
4252 | } |
4253 | |
4254 | void PSOutputDev::updateTextPos(GfxState *state) |
4255 | { |
4256 | writePSFmt(fmt: "{0:.6g} {1:.6g} Td\n" , state->getLineX(), state->getLineY()); |
4257 | } |
4258 | |
4259 | void PSOutputDev::updateTextShift(GfxState *state, double shift) |
4260 | { |
4261 | if (state->getFont()->getWMode()) { |
4262 | writePSFmt(fmt: "{0:.6g} TJmV\n" , shift); |
4263 | } else { |
4264 | writePSFmt(fmt: "{0:.6g} TJm\n" , shift); |
4265 | } |
4266 | } |
4267 | |
4268 | void PSOutputDev::saveTextPos(GfxState *state) |
4269 | { |
4270 | writePS(s: "currentpoint\n" ); |
4271 | } |
4272 | |
4273 | void PSOutputDev::restoreTextPos(GfxState *state) |
4274 | { |
4275 | writePS(s: "m\n" ); |
4276 | } |
4277 | |
4278 | void PSOutputDev::stroke(GfxState *state) |
4279 | { |
4280 | doPath(path: state->getPath()); |
4281 | if (inType3Char && t3FillColorOnly) { |
4282 | // if we're constructing a cacheable Type 3 glyph, we need to do |
4283 | // everything in the fill color |
4284 | writePS(s: "Sf\n" ); |
4285 | } else { |
4286 | writePS(s: "S\n" ); |
4287 | } |
4288 | } |
4289 | |
4290 | void PSOutputDev::fill(GfxState *state) |
4291 | { |
4292 | doPath(path: state->getPath()); |
4293 | writePS(s: "f\n" ); |
4294 | } |
4295 | |
4296 | void PSOutputDev::eoFill(GfxState *state) |
4297 | { |
4298 | doPath(path: state->getPath()); |
4299 | writePS(s: "f*\n" ); |
4300 | } |
4301 | |
4302 | bool PSOutputDev::tilingPatternFillL1(GfxState *state, Catalog *cat, Object *str, const double *pmat, int paintType, int tilingType, Dict *resDict, const double *mat, const double *bbox, int x0, int y0, int x1, int y1, double xStep, |
4303 | double yStep) |
4304 | { |
4305 | PDFRectangle box; |
4306 | Gfx *gfx; |
4307 | |
4308 | // define a Type 3 font |
4309 | writePS(s: "8 dict begin\n" ); |
4310 | writePS(s: "/FontType 3 def\n" ); |
4311 | writePS(s: "/FontMatrix [1 0 0 1 0 0] def\n" ); |
4312 | writePSFmt(fmt: "/FontBBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}] def\n" , bbox[0], bbox[1], bbox[2], bbox[3]); |
4313 | writePS(s: "/Encoding 256 array def\n" ); |
4314 | writePS(s: " 0 1 255 { Encoding exch /.notdef put } for\n" ); |
4315 | writePS(s: " Encoding 120 /x put\n" ); |
4316 | writePS(s: "/BuildGlyph {\n" ); |
4317 | writePS(s: " exch /CharProcs get exch\n" ); |
4318 | writePS(s: " 2 copy known not { pop /.notdef } if\n" ); |
4319 | writePS(s: " get exec\n" ); |
4320 | writePS(s: "} bind def\n" ); |
4321 | writePS(s: "/BuildChar {\n" ); |
4322 | writePS(s: " 1 index /Encoding get exch get\n" ); |
4323 | writePS(s: " 1 index /BuildGlyph get exec\n" ); |
4324 | writePS(s: "} bind def\n" ); |
4325 | writePS(s: "/CharProcs 1 dict def\n" ); |
4326 | writePS(s: "CharProcs begin\n" ); |
4327 | box.x1 = bbox[0]; |
4328 | box.y1 = bbox[1]; |
4329 | box.x2 = bbox[2]; |
4330 | box.y2 = bbox[3]; |
4331 | gfx = new Gfx(doc, this, resDict, &box, nullptr); |
4332 | writePS(s: "/x {\n" ); |
4333 | if (paintType == 2) { |
4334 | writePSFmt(fmt: "{0:.6g} 0 {1:.6g} {2:.6g} {3:.6g} {4:.6g} setcachedevice\n" , xStep, bbox[0], bbox[1], bbox[2], bbox[3]); |
4335 | t3FillColorOnly = true; |
4336 | } else { |
4337 | if (x1 - 1 <= x0) { |
4338 | writePS(s: "1 0 setcharwidth\n" ); |
4339 | } else { |
4340 | writePSFmt(fmt: "{0:.6g} 0 setcharwidth\n" , xStep); |
4341 | } |
4342 | t3FillColorOnly = false; |
4343 | } |
4344 | inType3Char = true; |
4345 | if (paintType == 2) { |
4346 | inUncoloredPattern = true; |
4347 | // ensure any PS procedures that contain sCol or fCol do not change the color |
4348 | writePS(s: "/pdfLastFill true def\n" ); |
4349 | writePS(s: "/pdfLastStroke true def\n" ); |
4350 | } |
4351 | ++numTilingPatterns; |
4352 | gfx->display(obj: str); |
4353 | --numTilingPatterns; |
4354 | if (paintType == 2) { |
4355 | inUncoloredPattern = false; |
4356 | // ensure the next PS procedures that uses sCol or fCol will update the color |
4357 | writePS(s: "/pdfLastFill false def\n" ); |
4358 | writePS(s: "/pdfLastStroke false def\n" ); |
4359 | } |
4360 | inType3Char = false; |
4361 | writePS(s: "} def\n" ); |
4362 | delete gfx; |
4363 | writePS(s: "end\n" ); |
4364 | writePS(s: "currentdict end\n" ); |
4365 | writePSFmt(fmt: "/xpdfTile{0:d} exch definefont pop\n" , numTilingPatterns); |
4366 | |
4367 | // draw the tiles |
4368 | writePSFmt(fmt: "/xpdfTile{0:d} findfont setfont\n" , numTilingPatterns); |
4369 | writePS(s: "fCol\n" ); |
4370 | writePSFmt(fmt: "gsave [{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] concat\n" , mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); |
4371 | writePSFmt(fmt: "{0:d} 1 {1:d} {{ {2:.6g} exch {3:.6g} mul m {4:d} 1 {5:d} {{ pop (x) show }} for }} for\n" , y0, y1 - 1, x0 * xStep, yStep, x0, x1 - 1); |
4372 | writePS(s: "grestore\n" ); |
4373 | |
4374 | return true; |
4375 | } |
4376 | |
4377 | bool PSOutputDev::tilingPatternFillL2(GfxState *state, Catalog *cat, Object *str, const double *pmat, int paintType, int tilingType, Dict *resDict, const double *mat, const double *bbox, int x0, int y0, int x1, int y1, double xStep, |
4378 | double yStep) |
4379 | { |
4380 | PDFRectangle box; |
4381 | Gfx *gfx; |
4382 | |
4383 | if (paintType == 2) { |
4384 | // setpattern with PaintType 2 needs the paint color |
4385 | writePS(s: "currentcolor\n" ); |
4386 | } |
4387 | writePS(s: "<<\n /PatternType 1\n" ); |
4388 | writePSFmt(fmt: " /PaintType {0:d}\n" , paintType); |
4389 | writePSFmt(fmt: " /TilingType {0:d}\n" , tilingType); |
4390 | writePSFmt(fmt: " /BBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}]\n" , bbox[0], bbox[1], bbox[2], bbox[3]); |
4391 | writePSFmt(fmt: " /XStep {0:.6g}\n" , xStep); |
4392 | writePSFmt(fmt: " /YStep {0:.6g}\n" , yStep); |
4393 | writePS(s: " /PaintProc { \n" ); |
4394 | box.x1 = bbox[0]; |
4395 | box.y1 = bbox[1]; |
4396 | box.x2 = bbox[2]; |
4397 | box.y2 = bbox[3]; |
4398 | gfx = new Gfx(doc, this, resDict, &box, nullptr); |
4399 | inType3Char = true; |
4400 | if (paintType == 2) { |
4401 | inUncoloredPattern = true; |
4402 | // ensure any PS procedures that contain sCol or fCol do not change the color |
4403 | writePS(s: "/pdfLastFill true def\n" ); |
4404 | writePS(s: "/pdfLastStroke true def\n" ); |
4405 | } |
4406 | gfx->display(obj: str); |
4407 | if (paintType == 2) { |
4408 | inUncoloredPattern = false; |
4409 | // ensure the next PS procedures that uses sCol or fCol will update the color |
4410 | writePS(s: "/pdfLastFill false def\n" ); |
4411 | writePS(s: "/pdfLastStroke false def\n" ); |
4412 | } |
4413 | inType3Char = false; |
4414 | delete gfx; |
4415 | writePS(s: " }\n" ); |
4416 | writePS(s: ">>\n" ); |
4417 | writePSFmt(fmt: "[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}]\n" , mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); |
4418 | writePS(s: "makepattern setpattern\n" ); |
4419 | writePS(s: "clippath fill\n" ); // Gfx sets up a clip before calling out->tilingPatternFill() |
4420 | |
4421 | return true; |
4422 | } |
4423 | |
4424 | bool PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat, GfxTilingPattern *tPat, const double *mat, int x0, int y0, int x1, int y1, double xStep, double yStep) |
4425 | { |
4426 | std::set<int>::iterator patternRefIt; |
4427 | const int patternRefNum = tPat->getPatternRefNum(); |
4428 | if (patternRefNum != -1) { |
4429 | if (patternsBeingTiled.find(x: patternRefNum) == patternsBeingTiled.end()) { |
4430 | patternRefIt = patternsBeingTiled.insert(x: patternRefNum).first; |
4431 | } else { |
4432 | // pretend we drew it anyway |
4433 | error(category: errSyntaxError, pos: -1, msg: "Loop in pattern fills" ); |
4434 | return true; |
4435 | } |
4436 | } |
4437 | |
4438 | const double *bbox = tPat->getBBox(); |
4439 | const double *pmat = tPat->getMatrix(); |
4440 | const int paintType = tPat->getPaintType(); |
4441 | const int tilingType = tPat->getTilingType(); |
4442 | Dict *resDict = tPat->getResDict(); |
4443 | Object *str = tPat->getContentStream(); |
4444 | |
4445 | bool res; |
4446 | if (x1 - x0 == 1 && y1 - y0 == 1) { |
4447 | // Don't need to use patterns if only one instance of the pattern is used |
4448 | PDFRectangle box; |
4449 | Gfx *gfx; |
4450 | |
4451 | const double singleStep_x = x0 * xStep; |
4452 | const double singleStep_y = y0 * yStep; |
4453 | const double singleStep_tx = singleStep_x * mat[0] + singleStep_y * mat[2] + mat[4]; |
4454 | const double singleStep_ty = singleStep_x * mat[1] + singleStep_y * mat[3] + mat[5]; |
4455 | box.x1 = bbox[0]; |
4456 | box.y1 = bbox[1]; |
4457 | box.x2 = bbox[2]; |
4458 | box.y2 = bbox[3]; |
4459 | gfx = new Gfx(doc, this, resDict, &box, nullptr, nullptr, nullptr, gfxA); |
4460 | writePSFmt(fmt: "[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] cm\n" , mat[0], mat[1], mat[2], mat[3], singleStep_tx, singleStep_ty); |
4461 | inType3Char = true; |
4462 | gfx->display(obj: str); |
4463 | inType3Char = false; |
4464 | delete gfx; |
4465 | res = true; |
4466 | } else if (level == psLevel1 || level == psLevel1Sep) { |
4467 | res = tilingPatternFillL1(state, cat, str, pmat, paintType, tilingType, resDict, mat, bbox, x0, y0, x1, y1, xStep, yStep); |
4468 | } else { |
4469 | res = tilingPatternFillL2(state, cat, str, pmat, paintType, tilingType, resDict, mat, bbox, x0, y0, x1, y1, xStep, yStep); |
4470 | } |
4471 | |
4472 | if (patternRefNum != -1) { |
4473 | patternsBeingTiled.erase(position: patternRefIt); |
4474 | } |
4475 | |
4476 | return res; |
4477 | } |
4478 | |
4479 | bool PSOutputDev::functionShadedFill(GfxState *state, GfxFunctionShading *shading) |
4480 | { |
4481 | double x0, y0, x1, y1; |
4482 | int i; |
4483 | |
4484 | if (level == psLevel2Sep || level == psLevel3Sep) { |
4485 | if (shading->getColorSpace()->getMode() != csDeviceCMYK) { |
4486 | return false; |
4487 | } |
4488 | processColors |= psProcessCMYK; |
4489 | } |
4490 | |
4491 | shading->getDomain(x0A: &x0, y0A: &y0, x1A: &x1, y1A: &y1); |
4492 | const double *mat = shading->getMatrix(); |
4493 | writePSFmt(fmt: "/mat [{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] def\n" , mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); |
4494 | writePSFmt(fmt: "/n {0:d} def\n" , shading->getColorSpace()->getNComps()); |
4495 | if (shading->getNFuncs() == 1) { |
4496 | writePS(s: "/func " ); |
4497 | cvtFunction(func: shading->getFunc(i: 0)); |
4498 | writePS(s: "def\n" ); |
4499 | } else { |
4500 | writePS(s: "/func {\n" ); |
4501 | for (i = 0; i < shading->getNFuncs(); ++i) { |
4502 | if (i < shading->getNFuncs() - 1) { |
4503 | writePS(s: "2 copy\n" ); |
4504 | } |
4505 | cvtFunction(func: shading->getFunc(i)); |
4506 | writePS(s: "exec\n" ); |
4507 | if (i < shading->getNFuncs() - 1) { |
4508 | writePS(s: "3 1 roll\n" ); |
4509 | } |
4510 | } |
4511 | writePS(s: "} def\n" ); |
4512 | } |
4513 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} {3:.6g} 0 funcSH\n" , x0, y0, x1, y1); |
4514 | |
4515 | return true; |
4516 | } |
4517 | |
4518 | bool PSOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double /*tMin*/, double /*tMax*/) |
4519 | { |
4520 | double xMin, yMin, xMax, yMax; |
4521 | double x0, y0, x1, y1, dx, dy, mul; |
4522 | double tMin, tMax, t, t0, t1; |
4523 | int i; |
4524 | |
4525 | if (level == psLevel2Sep || level == psLevel3Sep) { |
4526 | if (shading->getColorSpace()->getMode() != csDeviceCMYK) { |
4527 | return false; |
4528 | } |
4529 | processColors |= psProcessCMYK; |
4530 | } |
4531 | |
4532 | // get the clip region bbox |
4533 | state->getUserClipBBox(xMin: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax); |
4534 | |
4535 | // compute min and max t values, based on the four corners of the |
4536 | // clip region bbox |
4537 | shading->getCoords(x0A: &x0, y0A: &y0, x1A: &x1, y1A: &y1); |
4538 | dx = x1 - x0; |
4539 | dy = y1 - y0; |
4540 | if (fabs(x: dx) < 0.01 && fabs(x: dy) < 0.01) { |
4541 | return true; |
4542 | } else { |
4543 | mul = 1 / (dx * dx + dy * dy); |
4544 | tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul; |
4545 | t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul; |
4546 | if (t < tMin) { |
4547 | tMin = t; |
4548 | } else if (t > tMax) { |
4549 | tMax = t; |
4550 | } |
4551 | t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul; |
4552 | if (t < tMin) { |
4553 | tMin = t; |
4554 | } else if (t > tMax) { |
4555 | tMax = t; |
4556 | } |
4557 | t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul; |
4558 | if (t < tMin) { |
4559 | tMin = t; |
4560 | } else if (t > tMax) { |
4561 | tMax = t; |
4562 | } |
4563 | if (tMin < 0 && !shading->getExtend0()) { |
4564 | tMin = 0; |
4565 | } |
4566 | if (tMax > 1 && !shading->getExtend1()) { |
4567 | tMax = 1; |
4568 | } |
4569 | } |
4570 | |
4571 | // get the function domain |
4572 | t0 = shading->getDomain0(); |
4573 | t1 = shading->getDomain1(); |
4574 | |
4575 | // generate the PS code |
4576 | writePSFmt(fmt: "/t0 {0:.6g} def\n" , t0); |
4577 | writePSFmt(fmt: "/t1 {0:.6g} def\n" , t1); |
4578 | writePSFmt(fmt: "/dt {0:.6g} def\n" , t1 - t0); |
4579 | writePSFmt(fmt: "/x0 {0:.6g} def\n" , x0); |
4580 | writePSFmt(fmt: "/y0 {0:.6g} def\n" , y0); |
4581 | writePSFmt(fmt: "/dx {0:.6g} def\n" , x1 - x0); |
4582 | writePSFmt(fmt: "/x1 {0:.6g} def\n" , x1); |
4583 | writePSFmt(fmt: "/y1 {0:.6g} def\n" , y1); |
4584 | writePSFmt(fmt: "/dy {0:.6g} def\n" , y1 - y0); |
4585 | writePSFmt(fmt: "/xMin {0:.6g} def\n" , xMin); |
4586 | writePSFmt(fmt: "/yMin {0:.6g} def\n" , yMin); |
4587 | writePSFmt(fmt: "/xMax {0:.6g} def\n" , xMax); |
4588 | writePSFmt(fmt: "/yMax {0:.6g} def\n" , yMax); |
4589 | writePSFmt(fmt: "/n {0:d} def\n" , shading->getColorSpace()->getNComps()); |
4590 | if (shading->getNFuncs() == 1) { |
4591 | writePS(s: "/func " ); |
4592 | cvtFunction(func: shading->getFunc(i: 0)); |
4593 | writePS(s: "def\n" ); |
4594 | } else { |
4595 | writePS(s: "/func {\n" ); |
4596 | for (i = 0; i < shading->getNFuncs(); ++i) { |
4597 | if (i < shading->getNFuncs() - 1) { |
4598 | writePS(s: "dup\n" ); |
4599 | } |
4600 | cvtFunction(func: shading->getFunc(i)); |
4601 | writePS(s: "exec\n" ); |
4602 | if (i < shading->getNFuncs() - 1) { |
4603 | writePS(s: "exch\n" ); |
4604 | } |
4605 | } |
4606 | writePS(s: "} def\n" ); |
4607 | } |
4608 | writePSFmt(fmt: "{0:.6g} {1:.6g} 0 axialSH\n" , tMin, tMax); |
4609 | |
4610 | return true; |
4611 | } |
4612 | |
4613 | bool PSOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double /*sMin*/, double /*sMax*/) |
4614 | { |
4615 | double xMin, yMin, xMax, yMax; |
4616 | double x0, y0, r0, x1, y1, r1, t0, t1; |
4617 | double xa, ya, ra; |
4618 | double sMin, sMax, h, ta; |
4619 | double sLeft, sRight, sTop, sBottom, sZero, sDiag; |
4620 | bool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero; |
4621 | bool haveSMin, haveSMax; |
4622 | double theta, alpha, a1, a2; |
4623 | bool enclosed; |
4624 | int i; |
4625 | |
4626 | if (level == psLevel2Sep || level == psLevel3Sep) { |
4627 | if (shading->getColorSpace()->getMode() != csDeviceCMYK) { |
4628 | return false; |
4629 | } |
4630 | processColors |= psProcessCMYK; |
4631 | } |
4632 | |
4633 | // get the shading info |
4634 | shading->getCoords(x0A: &x0, y0A: &y0, r0A: &r0, x1A: &x1, y1A: &y1, r1A: &r1); |
4635 | t0 = shading->getDomain0(); |
4636 | t1 = shading->getDomain1(); |
4637 | |
4638 | // Compute the point at which r(s) = 0; check for the enclosed |
4639 | // circles case; and compute the angles for the tangent lines. |
4640 | // Compute the point at which r(s) = 0; check for the enclosed |
4641 | // circles case; and compute the angles for the tangent lines. |
4642 | h = sqrt(x: (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); |
4643 | if (h == 0) { |
4644 | enclosed = true; |
4645 | theta = 0; // make gcc happy |
4646 | } else if (r1 - r0 == 0) { |
4647 | enclosed = false; |
4648 | theta = 0; |
4649 | } else if (fabs(x: r1 - r0) >= h) { |
4650 | enclosed = true; |
4651 | theta = 0; // make gcc happy |
4652 | } else { |
4653 | enclosed = false; |
4654 | theta = asin(x: (r1 - r0) / h); |
4655 | } |
4656 | if (enclosed) { |
4657 | a1 = 0; |
4658 | a2 = 360; |
4659 | } else { |
4660 | alpha = atan2(y: y1 - y0, x: x1 - x0); |
4661 | a1 = (180 / M_PI) * (alpha + theta) + 90; |
4662 | a2 = (180 / M_PI) * (alpha - theta) - 90; |
4663 | while (a2 < a1) { |
4664 | a2 += 360; |
4665 | } |
4666 | } |
4667 | |
4668 | // compute the (possibly extended) s range |
4669 | state->getUserClipBBox(xMin: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax); |
4670 | if (enclosed) { |
4671 | sMin = 0; |
4672 | sMax = 1; |
4673 | } else { |
4674 | // solve x(sLeft) + r(sLeft) = xMin |
4675 | if ((haveSLeft = fabs(x: (x1 + r1) - (x0 + r0)) > 0.000001)) { |
4676 | sLeft = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0)); |
4677 | } else { |
4678 | sLeft = 0; // make gcc happy |
4679 | } |
4680 | // solve x(sRight) - r(sRight) = xMax |
4681 | if ((haveSRight = fabs(x: (x1 - r1) - (x0 - r0)) > 0.000001)) { |
4682 | sRight = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0)); |
4683 | } else { |
4684 | sRight = 0; // make gcc happy |
4685 | } |
4686 | // solve y(sBottom) + r(sBottom) = yMin |
4687 | if ((haveSBottom = fabs(x: (y1 + r1) - (y0 + r0)) > 0.000001)) { |
4688 | sBottom = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0)); |
4689 | } else { |
4690 | sBottom = 0; // make gcc happy |
4691 | } |
4692 | // solve y(sTop) - r(sTop) = yMax |
4693 | if ((haveSTop = fabs(x: (y1 - r1) - (y0 - r0)) > 0.000001)) { |
4694 | sTop = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0)); |
4695 | } else { |
4696 | sTop = 0; // make gcc happy |
4697 | } |
4698 | // solve r(sZero) = 0 |
4699 | if ((haveSZero = fabs(x: r1 - r0) > 0.000001)) { |
4700 | sZero = -r0 / (r1 - r0); |
4701 | } else { |
4702 | sZero = 0; // make gcc happy |
4703 | } |
4704 | // solve r(sDiag) = sqrt((xMax-xMin)^2 + (yMax-yMin)^2) |
4705 | if (haveSZero) { |
4706 | sDiag = (sqrt(x: (xMax - xMin) * (xMax - xMin) + (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0); |
4707 | } else { |
4708 | sDiag = 0; // make gcc happy |
4709 | } |
4710 | // compute sMin |
4711 | if (shading->getExtend0()) { |
4712 | sMin = 0; |
4713 | haveSMin = false; |
4714 | if (x0 < x1 && haveSLeft && sLeft < 0) { |
4715 | sMin = sLeft; |
4716 | haveSMin = true; |
4717 | } else if (x0 > x1 && haveSRight && sRight < 0) { |
4718 | sMin = sRight; |
4719 | haveSMin = true; |
4720 | } |
4721 | if (y0 < y1 && haveSBottom && sBottom < 0) { |
4722 | if (!haveSMin || sBottom > sMin) { |
4723 | sMin = sBottom; |
4724 | haveSMin = true; |
4725 | } |
4726 | } else if (y0 > y1 && haveSTop && sTop < 0) { |
4727 | if (!haveSMin || sTop > sMin) { |
4728 | sMin = sTop; |
4729 | haveSMin = true; |
4730 | } |
4731 | } |
4732 | if (haveSZero && sZero < 0) { |
4733 | if (!haveSMin || sZero > sMin) { |
4734 | sMin = sZero; |
4735 | } |
4736 | } |
4737 | } else { |
4738 | sMin = 0; |
4739 | } |
4740 | // compute sMax |
4741 | if (shading->getExtend1()) { |
4742 | sMax = 1; |
4743 | haveSMax = false; |
4744 | if (x1 < x0 && haveSLeft && sLeft > 1) { |
4745 | sMax = sLeft; |
4746 | haveSMax = true; |
4747 | } else if (x1 > x0 && haveSRight && sRight > 1) { |
4748 | sMax = sRight; |
4749 | haveSMax = true; |
4750 | } |
4751 | if (y1 < y0 && haveSBottom && sBottom > 1) { |
4752 | if (!haveSMax || sBottom < sMax) { |
4753 | sMax = sBottom; |
4754 | haveSMax = true; |
4755 | } |
4756 | } else if (y1 > y0 && haveSTop && sTop > 1) { |
4757 | if (!haveSMax || sTop < sMax) { |
4758 | sMax = sTop; |
4759 | haveSMax = true; |
4760 | } |
4761 | } |
4762 | if (haveSZero && sDiag > 1) { |
4763 | if (!haveSMax || sDiag < sMax) { |
4764 | sMax = sDiag; |
4765 | } |
4766 | } |
4767 | } else { |
4768 | sMax = 1; |
4769 | } |
4770 | } |
4771 | |
4772 | // generate the PS code |
4773 | writePSFmt(fmt: "/x0 {0:.6g} def\n" , x0); |
4774 | writePSFmt(fmt: "/x1 {0:.6g} def\n" , x1); |
4775 | writePSFmt(fmt: "/dx {0:.6g} def\n" , x1 - x0); |
4776 | writePSFmt(fmt: "/y0 {0:.6g} def\n" , y0); |
4777 | writePSFmt(fmt: "/y1 {0:.6g} def\n" , y1); |
4778 | writePSFmt(fmt: "/dy {0:.6g} def\n" , y1 - y0); |
4779 | writePSFmt(fmt: "/r0 {0:.6g} def\n" , r0); |
4780 | writePSFmt(fmt: "/r1 {0:.6g} def\n" , r1); |
4781 | writePSFmt(fmt: "/dr {0:.6g} def\n" , r1 - r0); |
4782 | writePSFmt(fmt: "/t0 {0:.6g} def\n" , t0); |
4783 | writePSFmt(fmt: "/t1 {0:.6g} def\n" , t1); |
4784 | writePSFmt(fmt: "/dt {0:.6g} def\n" , t1 - t0); |
4785 | writePSFmt(fmt: "/n {0:d} def\n" , shading->getColorSpace()->getNComps()); |
4786 | writePSFmt(fmt: "/encl {0:s} def\n" , enclosed ? "true" : "false" ); |
4787 | writePSFmt(fmt: "/a1 {0:.6g} def\n" , a1); |
4788 | writePSFmt(fmt: "/a2 {0:.6g} def\n" , a2); |
4789 | if (shading->getNFuncs() == 1) { |
4790 | writePS(s: "/func " ); |
4791 | cvtFunction(func: shading->getFunc(i: 0)); |
4792 | writePS(s: "def\n" ); |
4793 | } else { |
4794 | writePS(s: "/func {\n" ); |
4795 | for (i = 0; i < shading->getNFuncs(); ++i) { |
4796 | if (i < shading->getNFuncs() - 1) { |
4797 | writePS(s: "dup\n" ); |
4798 | } |
4799 | cvtFunction(func: shading->getFunc(i)); |
4800 | writePS(s: "exec\n" ); |
4801 | if (i < shading->getNFuncs() - 1) { |
4802 | writePS(s: "exch\n" ); |
4803 | } |
4804 | } |
4805 | writePS(s: "} def\n" ); |
4806 | } |
4807 | writePSFmt(fmt: "{0:.6g} {1:.6g} 0 radialSH\n" , sMin, sMax); |
4808 | |
4809 | // extend the 'enclosed' case |
4810 | if (enclosed) { |
4811 | // extend the smaller circle |
4812 | if ((shading->getExtend0() && r0 <= r1) || (shading->getExtend1() && r1 < r0)) { |
4813 | if (r0 <= r1) { |
4814 | ta = t0; |
4815 | ra = r0; |
4816 | xa = x0; |
4817 | ya = y0; |
4818 | } else { |
4819 | ta = t1; |
4820 | ra = r1; |
4821 | xa = x1; |
4822 | ya = y1; |
4823 | } |
4824 | if (level == psLevel2Sep || level == psLevel3Sep) { |
4825 | writePSFmt(fmt: "{0:.6g} radialCol aload pop k\n" , ta); |
4826 | } else { |
4827 | writePSFmt(fmt: "{0:.6g} radialCol sc\n" , ta); |
4828 | } |
4829 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} 0 360 arc h f*\n" , xa, ya, ra); |
4830 | } |
4831 | |
4832 | // extend the larger circle |
4833 | if ((shading->getExtend0() && r0 > r1) || (shading->getExtend1() && r1 >= r0)) { |
4834 | if (r0 > r1) { |
4835 | ta = t0; |
4836 | ra = r0; |
4837 | xa = x0; |
4838 | ya = y0; |
4839 | } else { |
4840 | ta = t1; |
4841 | ra = r1; |
4842 | xa = x1; |
4843 | ya = y1; |
4844 | } |
4845 | if (level == psLevel2Sep || level == psLevel3Sep) { |
4846 | writePSFmt(fmt: "{0:.6g} radialCol aload pop k\n" , ta); |
4847 | } else { |
4848 | writePSFmt(fmt: "{0:.6g} radialCol sc\n" , ta); |
4849 | } |
4850 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} 0 360 arc h\n" , xa, ya, ra); |
4851 | writePSFmt(fmt: "{0:.6g} {1:.6g} m {2:.6g} {3:.6g} l {4:.6g} {5:.6g} l {6:.6g} {7:.6g} l h f*\n" , xMin, yMin, xMin, yMax, xMax, yMax, xMax, yMin); |
4852 | } |
4853 | } |
4854 | |
4855 | return true; |
4856 | } |
4857 | |
4858 | bool PSOutputDev::patchMeshShadedFill(GfxState *state, GfxPatchMeshShading *shading) |
4859 | { |
4860 | // TODO: support parametrized shading |
4861 | if (level < psLevel3 || shading->isParameterized()) { |
4862 | return false; |
4863 | } |
4864 | |
4865 | writePS(s: "%% Begin patchMeshShadedFill\n" ); |
4866 | |
4867 | // ShadingType 7 shadings are pretty much the same for pdf and ps. |
4868 | // As such, we basically just need to invert GfxPatchMeshShading::parse here. |
4869 | |
4870 | writePS(s: "<<\n" ); |
4871 | writePS(s: " /ShadingType 7\n" ); |
4872 | writePS(s: " /ColorSpace " ); |
4873 | dumpColorSpaceL2(state, colorSpace: shading->getColorSpace(), genXform: false, updateColors: false, map01: false); |
4874 | writePS(s: "\n" ); |
4875 | writePS(s: " /DataSource [\n" ); |
4876 | |
4877 | const int ncomps = shading->getColorSpace()->getNComps(); |
4878 | |
4879 | for (int i = 0; i < shading->getNPatches(); ++i) { |
4880 | const auto &patch = *shading->getPatch(i); |
4881 | // Print Flag, for us always f = 0 |
4882 | writePS(s: " 0 \n" ); |
4883 | |
4884 | // Print coordinates |
4885 | const std::array<std::pair<int, int>, 16> coordindices = { ._M_elems: { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 3 }, { 2, 3 }, { 3, 3 }, { 3, 2 }, { 3, 1 }, { 3, 0 }, { 2, 0 }, { 1, 0 }, { 1, 1 }, { 1, 2 }, { 2, 2 }, { 2, 1 } } }; |
4886 | for (const auto &index : coordindices) { |
4887 | writePSFmt(fmt: " {0:.6g} {1:.6g}\n" , patch.x[index.first][index.second], patch.y[index.first][index.second]); |
4888 | } |
4889 | |
4890 | // Print colors |
4891 | const std::array<std::pair<int, int>, 4> colindices = { ._M_elems: { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 0 } } }; |
4892 | for (const auto &index : colindices) { |
4893 | writePS(s: " " ); |
4894 | for (int comp = 0; comp < ncomps; ++comp) { |
4895 | writePSFmt(fmt: " {0:.6g}" , colToDbl(x: patch.color[index.first][index.second].c[comp])); |
4896 | } |
4897 | writePS(s: "\n" ); |
4898 | } |
4899 | } |
4900 | |
4901 | writePS(s: " ]\n" ); |
4902 | |
4903 | writePS(s: ">> shfill\n" ); |
4904 | writePS(s: "%% End patchMeshShadedFill\n" ); |
4905 | return true; |
4906 | } |
4907 | |
4908 | void PSOutputDev::clip(GfxState *state) |
4909 | { |
4910 | doPath(path: state->getPath()); |
4911 | writePS(s: "W\n" ); |
4912 | } |
4913 | |
4914 | void PSOutputDev::eoClip(GfxState *state) |
4915 | { |
4916 | doPath(path: state->getPath()); |
4917 | writePS(s: "W*\n" ); |
4918 | } |
4919 | |
4920 | void PSOutputDev::clipToStrokePath(GfxState *state) |
4921 | { |
4922 | doPath(path: state->getPath()); |
4923 | writePS(s: "Ws\n" ); |
4924 | } |
4925 | |
4926 | void PSOutputDev::doPath(const GfxPath *path) |
4927 | { |
4928 | double x0, y0, x1, y1, x2, y2, x3, y3, x4, y4; |
4929 | int n, m, i, j; |
4930 | |
4931 | n = path->getNumSubpaths(); |
4932 | |
4933 | if (n == 1 && path->getSubpath(i: 0)->getNumPoints() == 5) { |
4934 | const GfxSubpath *subpath = path->getSubpath(i: 0); |
4935 | x0 = subpath->getX(i: 0); |
4936 | y0 = subpath->getY(i: 0); |
4937 | x4 = subpath->getX(i: 4); |
4938 | y4 = subpath->getY(i: 4); |
4939 | if (x4 == x0 && y4 == y0) { |
4940 | x1 = subpath->getX(i: 1); |
4941 | y1 = subpath->getY(i: 1); |
4942 | x2 = subpath->getX(i: 2); |
4943 | y2 = subpath->getY(i: 2); |
4944 | x3 = subpath->getX(i: 3); |
4945 | y3 = subpath->getY(i: 3); |
4946 | if (x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) { |
4947 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n" , x0 < x2 ? x0 : x2, y0 < y1 ? y0 : y1, fabs(x: x2 - x0), fabs(x: y1 - y0)); |
4948 | return; |
4949 | } else if (x0 == x3 && x1 == x2 && y0 == y1 && y2 == y3) { |
4950 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n" , x0 < x1 ? x0 : x1, y0 < y2 ? y0 : y2, fabs(x: x1 - x0), fabs(x: y2 - y0)); |
4951 | return; |
4952 | } |
4953 | } |
4954 | } |
4955 | |
4956 | for (i = 0; i < n; ++i) { |
4957 | const GfxSubpath *subpath = path->getSubpath(i); |
4958 | m = subpath->getNumPoints(); |
4959 | writePSFmt(fmt: "{0:.6g} {1:.6g} m\n" , subpath->getX(i: 0), subpath->getY(i: 0)); |
4960 | j = 1; |
4961 | while (j < m) { |
4962 | if (subpath->getCurve(i: j)) { |
4963 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g} c\n" , subpath->getX(i: j), subpath->getY(i: j), subpath->getX(i: j + 1), subpath->getY(i: j + 1), subpath->getX(i: j + 2), subpath->getY(i: j + 2)); |
4964 | j += 3; |
4965 | } else { |
4966 | writePSFmt(fmt: "{0:.6g} {1:.6g} l\n" , subpath->getX(i: j), subpath->getY(i: j)); |
4967 | ++j; |
4968 | } |
4969 | } |
4970 | if (subpath->isClosed()) { |
4971 | writePS(s: "h\n" ); |
4972 | } |
4973 | } |
4974 | } |
4975 | |
4976 | void PSOutputDev::drawString(GfxState *state, const GooString *s) |
4977 | { |
4978 | std::shared_ptr<GfxFont> font; |
4979 | int wMode; |
4980 | int *codeToGID; |
4981 | GooString *s2; |
4982 | double dx, dy, originX, originY; |
4983 | const char *p; |
4984 | const UnicodeMap *uMap; |
4985 | CharCode code; |
4986 | const Unicode *u; |
4987 | char buf[8]; |
4988 | double *dxdy; |
4989 | int dxdySize, len, nChars, uLen, n, m, i, j; |
4990 | int maxGlyphInt; |
4991 | CharCode maxGlyph; |
4992 | |
4993 | // for pdftohtml, output PS without text |
4994 | if (displayText == false) { |
4995 | return; |
4996 | } |
4997 | |
4998 | // check for invisible text -- this is used by Acrobat Capture |
4999 | if (state->getRender() == 3) { |
5000 | return; |
5001 | } |
5002 | |
5003 | // ignore empty strings |
5004 | if (s->getLength() == 0) { |
5005 | return; |
5006 | } |
5007 | |
5008 | // get the font |
5009 | if (!(font = state->getFont())) { |
5010 | return; |
5011 | } |
5012 | maxGlyphInt = (font->getName() ? perFontMaxValidGlyph[*font->getName()] : 0); |
5013 | if (maxGlyphInt < 0) { |
5014 | maxGlyphInt = 0; |
5015 | } |
5016 | maxGlyph = (CharCode)maxGlyphInt; |
5017 | wMode = font->getWMode(); |
5018 | |
5019 | // check for a subtitute 16-bit font |
5020 | uMap = nullptr; |
5021 | codeToGID = nullptr; |
5022 | if (font->isCIDFont()) { |
5023 | for (i = 0; i < font16EncLen; ++i) { |
5024 | if (*font->getID() == font16Enc[i].fontID) { |
5025 | if (!font16Enc[i].enc) { |
5026 | // font substitution failed, so don't output any text |
5027 | return; |
5028 | } |
5029 | uMap = globalParams->getUnicodeMap(encodingName: font16Enc[i].enc->toStr()); |
5030 | break; |
5031 | } |
5032 | } |
5033 | |
5034 | // check for a code-to-GID map |
5035 | } else { |
5036 | for (i = 0; i < font8InfoLen; ++i) { |
5037 | if (*font->getID() == font8Info[i].fontID) { |
5038 | codeToGID = font8Info[i].codeToGID; |
5039 | break; |
5040 | } |
5041 | } |
5042 | } |
5043 | |
5044 | // compute the positioning (dx, dy) for each char in the string |
5045 | nChars = 0; |
5046 | p = s->c_str(); |
5047 | len = s->getLength(); |
5048 | s2 = new GooString(); |
5049 | dxdySize = font->isCIDFont() ? 8 : s->getLength(); |
5050 | dxdy = (double *)gmallocn(count: 2 * dxdySize, size: sizeof(double)); |
5051 | while (len > 0) { |
5052 | n = font->getNextChar(s: p, len, code: &code, u: &u, uLen: &uLen, dx: &dx, dy: &dy, ox: &originX, oy: &originY); |
5053 | dx *= state->getFontSize(); |
5054 | dy *= state->getFontSize(); |
5055 | if (wMode) { |
5056 | dy += state->getCharSpace(); |
5057 | if (n == 1 && *p == ' ') { |
5058 | dy += state->getWordSpace(); |
5059 | } |
5060 | } else { |
5061 | dx += state->getCharSpace(); |
5062 | if (n == 1 && *p == ' ') { |
5063 | dx += state->getWordSpace(); |
5064 | } |
5065 | } |
5066 | dx *= state->getHorizScaling(); |
5067 | if (font->isCIDFont()) { |
5068 | if (uMap) { |
5069 | if (nChars + uLen > dxdySize) { |
5070 | do { |
5071 | dxdySize *= 2; |
5072 | } while (nChars + uLen > dxdySize); |
5073 | dxdy = (double *)greallocn(p: dxdy, count: 2 * dxdySize, size: sizeof(double)); |
5074 | } |
5075 | for (i = 0; i < uLen; ++i) { |
5076 | m = uMap->mapUnicode(u: u[i], buf, bufSize: (int)sizeof(buf)); |
5077 | for (j = 0; j < m; ++j) { |
5078 | s2->append(c: buf[j]); |
5079 | } |
5080 | //~ this really needs to get the number of chars in the target |
5081 | //~ encoding - which may be more than the number of Unicode |
5082 | //~ chars |
5083 | dxdy[2 * nChars] = dx; |
5084 | dxdy[2 * nChars + 1] = dy; |
5085 | ++nChars; |
5086 | } |
5087 | } else if (maxGlyph > 0 && code > maxGlyph) { |
5088 | // Ignore this code. |
5089 | // Using it will exceed the number of glyphs in the font and generate |
5090 | // /rangecheck in --xyshow-- |
5091 | if (nChars > 0) { |
5092 | dxdy[2 * (nChars - 1)] += dx; |
5093 | dxdy[2 * (nChars - 1) + 1] += dy; |
5094 | } |
5095 | } else { |
5096 | if (nChars + 1 > dxdySize) { |
5097 | dxdySize *= 2; |
5098 | dxdy = (double *)greallocn(p: dxdy, count: 2 * dxdySize, size: sizeof(double)); |
5099 | } |
5100 | s2->append(c: (char)((code >> 8) & 0xff)); |
5101 | s2->append(c: (char)(code & 0xff)); |
5102 | dxdy[2 * nChars] = dx; |
5103 | dxdy[2 * nChars + 1] = dy; |
5104 | ++nChars; |
5105 | } |
5106 | } else { |
5107 | if (!codeToGID || codeToGID[code] >= 0) { |
5108 | s2->append(c: (char)code); |
5109 | dxdy[2 * nChars] = dx; |
5110 | dxdy[2 * nChars + 1] = dy; |
5111 | ++nChars; |
5112 | } |
5113 | } |
5114 | p += n; |
5115 | len -= n; |
5116 | } |
5117 | |
5118 | if (nChars > 0) { |
5119 | writePSString(s: s2->toStr()); |
5120 | writePS(s: "\n[" ); |
5121 | for (i = 0; i < 2 * nChars; ++i) { |
5122 | if (i > 0) { |
5123 | writePS(s: "\n" ); |
5124 | } |
5125 | writePSFmt(fmt: "{0:.6g}" , dxdy[i]); |
5126 | } |
5127 | writePS(s: "] Tj\n" ); |
5128 | } |
5129 | gfree(p: dxdy); |
5130 | delete s2; |
5131 | |
5132 | if (state->getRender() & 4) { |
5133 | haveTextClip = true; |
5134 | } |
5135 | } |
5136 | |
5137 | void PSOutputDev::beginTextObject(GfxState *state) { } |
5138 | |
5139 | void PSOutputDev::endTextObject(GfxState *state) |
5140 | { |
5141 | if (haveTextClip) { |
5142 | writePS(s: "Tclip\n" ); |
5143 | haveTextClip = false; |
5144 | } |
5145 | } |
5146 | |
5147 | void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg) |
5148 | { |
5149 | int len; |
5150 | |
5151 | len = height * ((width + 7) / 8); |
5152 | switch (level) { |
5153 | case psLevel1: |
5154 | case psLevel1Sep: |
5155 | doImageL1(ref, colorMap: nullptr, invert, inlineImg, str, width, height, len, maskColors: nullptr, maskStr: nullptr, maskWidth: 0, maskHeight: 0, maskInvert: false); |
5156 | break; |
5157 | case psLevel2: |
5158 | case psLevel2Sep: |
5159 | doImageL2(state, ref, colorMap: nullptr, invert, inlineImg, str, width, height, len, maskColors: nullptr, maskStr: nullptr, maskWidth: 0, maskHeight: 0, maskInvert: false); |
5160 | break; |
5161 | case psLevel3: |
5162 | case psLevel3Sep: |
5163 | doImageL3(state, ref, colorMap: nullptr, invert, inlineImg, str, width, height, len, maskColors: nullptr, maskStr: nullptr, maskWidth: 0, maskHeight: 0, maskInvert: false); |
5164 | break; |
5165 | } |
5166 | } |
5167 | |
5168 | void PSOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool inlineImg, double *baseMatrix) |
5169 | { |
5170 | if (level != psLevel1 && level != psLevel1Sep) { |
5171 | maskToClippingPath(maskStr: str, maskWidth: width, maskHeight: height, maskInvert: invert); |
5172 | } |
5173 | } |
5174 | |
5175 | void PSOutputDev::unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix) |
5176 | { |
5177 | if (level != psLevel1 && level != psLevel1Sep) { |
5178 | writePS(s: "pdfImClipEnd\n" ); |
5179 | } |
5180 | } |
5181 | |
5182 | void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg) |
5183 | { |
5184 | int len; |
5185 | |
5186 | len = height * ((width * colorMap->getNumPixelComps() * colorMap->getBits() + 7) / 8); |
5187 | switch (level) { |
5188 | case psLevel1: |
5189 | doImageL1(ref, colorMap, invert: false, inlineImg, str, width, height, len, maskColors, maskStr: nullptr, maskWidth: 0, maskHeight: 0, maskInvert: false); |
5190 | break; |
5191 | case psLevel1Sep: |
5192 | //~ handle indexed, separation, ... color spaces |
5193 | doImageL1Sep(ref, colorMap, invert: false, inlineImg, str, width, height, len, maskColors, maskStr: nullptr, maskWidth: 0, maskHeight: 0, maskInvert: false); |
5194 | break; |
5195 | case psLevel2: |
5196 | case psLevel2Sep: |
5197 | doImageL2(state, ref, colorMap, invert: false, inlineImg, str, width, height, len, maskColors, maskStr: nullptr, maskWidth: 0, maskHeight: 0, maskInvert: false); |
5198 | break; |
5199 | case psLevel3: |
5200 | case psLevel3Sep: |
5201 | doImageL3(state, ref, colorMap, invert: false, inlineImg, str, width, height, len, maskColors, maskStr: nullptr, maskWidth: 0, maskHeight: 0, maskInvert: false); |
5202 | break; |
5203 | } |
5204 | t3Cacheable = false; |
5205 | } |
5206 | |
5207 | void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate) |
5208 | { |
5209 | int len; |
5210 | |
5211 | len = height * ((width * colorMap->getNumPixelComps() * colorMap->getBits() + 7) / 8); |
5212 | switch (level) { |
5213 | case psLevel1: |
5214 | doImageL1(ref, colorMap, invert: false, inlineImg: false, str, width, height, len, maskColors: nullptr, maskStr, maskWidth, maskHeight, maskInvert); |
5215 | break; |
5216 | case psLevel1Sep: |
5217 | //~ handle indexed, separation, ... color spaces |
5218 | doImageL1Sep(ref, colorMap, invert: false, inlineImg: false, str, width, height, len, maskColors: nullptr, maskStr, maskWidth, maskHeight, maskInvert); |
5219 | break; |
5220 | case psLevel2: |
5221 | case psLevel2Sep: |
5222 | doImageL2(state, ref, colorMap, invert: false, inlineImg: false, str, width, height, len, maskColors: nullptr, maskStr, maskWidth, maskHeight, maskInvert); |
5223 | break; |
5224 | case psLevel3: |
5225 | case psLevel3Sep: |
5226 | doImageL3(state, ref, colorMap, invert: false, inlineImg: false, str, width, height, len, maskColors: nullptr, maskStr, maskWidth, maskHeight, maskInvert); |
5227 | break; |
5228 | } |
5229 | t3Cacheable = false; |
5230 | } |
5231 | |
5232 | void PSOutputDev::doImageL1(Object *ref, GfxImageColorMap *colorMap, bool invert, bool inlineImg, Stream *str, int width, int height, int len, const int *maskColors, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert) |
5233 | { |
5234 | ImageStream *imgStr; |
5235 | unsigned char pixBuf[gfxColorMaxComps]; |
5236 | GfxGray gray; |
5237 | int col, x, y, c, i; |
5238 | char hexBuf[32 * 2 + 2]; // 32 values X 2 chars/value + line ending + null |
5239 | unsigned char digit, grayValue; |
5240 | |
5241 | // explicit masking |
5242 | if (maskStr && !(maskColors && colorMap)) { |
5243 | maskToClippingPath(maskStr, maskWidth, maskHeight, maskInvert); |
5244 | } |
5245 | |
5246 | if ((inType3Char || preloadImagesForms) && !colorMap) { |
5247 | if (inlineImg) { |
5248 | // create an array |
5249 | str = new FixedLengthEncoder(str, len); |
5250 | str = new ASCIIHexEncoder(str); |
5251 | str->reset(); |
5252 | col = 0; |
5253 | writePS(s: "[<" ); |
5254 | do { |
5255 | do { |
5256 | c = str->getChar(); |
5257 | } while (c == '\n' || c == '\r'); |
5258 | if (c == '>' || c == EOF) { |
5259 | break; |
5260 | } |
5261 | writePSChar(c); |
5262 | ++col; |
5263 | // each line is: "<...data...><eol>" |
5264 | // so max data length = 255 - 4 = 251 |
5265 | // but make it 240 just to be safe |
5266 | // chunks are 2 bytes each, so we need to stop on an even col number |
5267 | if (col == 240) { |
5268 | writePS(s: ">\n<" ); |
5269 | col = 0; |
5270 | } |
5271 | } while (c != '>' && c != EOF); |
5272 | writePS(s: ">]\n" ); |
5273 | writePS(s: "0\n" ); |
5274 | str->close(); |
5275 | delete str; |
5276 | } else { |
5277 | // make sure the image is setup, it sometimes is not like on bug #17645 |
5278 | setupImage(id: ref->getRef(), str, mask: false); |
5279 | // set up to use the array already created by setupImages() |
5280 | writePSFmt(fmt: "ImData_{0:d}_{1:d} 0 0\n" , ref->getRefNum(), ref->getRefGen()); |
5281 | } |
5282 | } |
5283 | |
5284 | // image/imagemask command |
5285 | if ((inType3Char || preloadImagesForms) && !colorMap) { |
5286 | writePSFmt(fmt: "{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1a\n" , width, height, invert ? "true" : "false" , width, -height, height); |
5287 | } else if (colorMap) { |
5288 | writePSFmt(fmt: "{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1{5:s}\n" , width, height, width, -height, height, useBinary ? "Bin" : "" ); |
5289 | } else { |
5290 | writePSFmt(fmt: "{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1{6:s}\n" , width, height, invert ? "true" : "false" , width, -height, height, useBinary ? "Bin" : "" ); |
5291 | } |
5292 | |
5293 | // image data |
5294 | if (!((inType3Char || preloadImagesForms) && !colorMap)) { |
5295 | |
5296 | if (colorMap) { |
5297 | |
5298 | // set up to process the data stream |
5299 | imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); |
5300 | imgStr->reset(); |
5301 | |
5302 | // process the data stream |
5303 | i = 0; |
5304 | for (y = 0; y < height; ++y) { |
5305 | |
5306 | // write the line |
5307 | for (x = 0; x < width; ++x) { |
5308 | imgStr->getPixel(pix: pixBuf); |
5309 | colorMap->getGray(x: pixBuf, gray: &gray); |
5310 | grayValue = colToByte(x: gray); |
5311 | if (useBinary) { |
5312 | hexBuf[i++] = grayValue; |
5313 | } else { |
5314 | digit = grayValue / 16; |
5315 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
5316 | digit = grayValue % 16; |
5317 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
5318 | } |
5319 | if (i >= 64) { |
5320 | if (!useBinary) { |
5321 | hexBuf[i++] = '\n'; |
5322 | } |
5323 | writePSBuf(s: hexBuf, len: i); |
5324 | i = 0; |
5325 | } |
5326 | } |
5327 | } |
5328 | if (i != 0) { |
5329 | if (!useBinary) { |
5330 | hexBuf[i++] = '\n'; |
5331 | } |
5332 | writePSBuf(s: hexBuf, len: i); |
5333 | } |
5334 | str->close(); |
5335 | delete imgStr; |
5336 | |
5337 | // imagemask |
5338 | } else { |
5339 | str->reset(); |
5340 | i = 0; |
5341 | for (y = 0; y < height; ++y) { |
5342 | for (x = 0; x < width; x += 8) { |
5343 | grayValue = str->getChar(); |
5344 | if (useBinary) { |
5345 | hexBuf[i++] = grayValue; |
5346 | } else { |
5347 | digit = grayValue / 16; |
5348 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
5349 | digit = grayValue % 16; |
5350 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
5351 | } |
5352 | if (i >= 64) { |
5353 | if (!useBinary) { |
5354 | hexBuf[i++] = '\n'; |
5355 | } |
5356 | writePSBuf(s: hexBuf, len: i); |
5357 | i = 0; |
5358 | } |
5359 | } |
5360 | } |
5361 | if (i != 0) { |
5362 | if (!useBinary) { |
5363 | hexBuf[i++] = '\n'; |
5364 | } |
5365 | writePSBuf(s: hexBuf, len: i); |
5366 | } |
5367 | str->close(); |
5368 | } |
5369 | } |
5370 | |
5371 | if (maskStr && !(maskColors && colorMap)) { |
5372 | writePS(s: "pdfImClipEnd\n" ); |
5373 | } |
5374 | } |
5375 | |
5376 | void PSOutputDev::doImageL1Sep(Object *ref, GfxImageColorMap *colorMap, bool invert, bool inlineImg, Stream *str, int width, int height, int len, const int *maskColors, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert) |
5377 | { |
5378 | ImageStream *imgStr; |
5379 | unsigned char *lineBuf; |
5380 | unsigned char pixBuf[gfxColorMaxComps]; |
5381 | GfxCMYK cmyk; |
5382 | int x, y, i, comp; |
5383 | bool checkProcessColor; |
5384 | char hexBuf[32 * 2 + 2]; // 32 values X 2 chars/value + line ending + null |
5385 | unsigned char digit; |
5386 | bool isGray; |
5387 | |
5388 | // explicit masking |
5389 | if (maskStr && !(maskColors && colorMap)) { |
5390 | maskToClippingPath(maskStr, maskWidth, maskHeight, maskInvert); |
5391 | } |
5392 | |
5393 | // allocate a line buffer |
5394 | lineBuf = (unsigned char *)gmallocn(count: width, size: 4); |
5395 | |
5396 | // scan for all gray |
5397 | if (getOptimizeColorSpace()) { |
5398 | ImageStream *imgCheckStr; |
5399 | imgCheckStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); |
5400 | imgCheckStr->reset(); |
5401 | isGray = true; |
5402 | for (y = 0; y < height; ++y) { |
5403 | for (x = 0; x < width; ++x) { |
5404 | imgCheckStr->getPixel(pix: pixBuf); |
5405 | colorMap->getCMYK(x: pixBuf, cmyk: &cmyk); |
5406 | if (colToByte(x: cmyk.c) != colToByte(x: cmyk.m) || colToByte(x: cmyk.c) != colToByte(x: cmyk.y)) { |
5407 | isGray = false; |
5408 | y = height; // end outer loop |
5409 | break; |
5410 | } |
5411 | } |
5412 | } |
5413 | imgCheckStr->close(); |
5414 | delete imgCheckStr; |
5415 | } else { |
5416 | isGray = false; |
5417 | } |
5418 | |
5419 | // set up to process the data stream |
5420 | imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); |
5421 | imgStr->reset(); |
5422 | |
5423 | // width, height, matrix, bits per component |
5424 | writePSFmt(fmt: "{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1{5:s}{6:s}\n" , width, height, width, -height, height, isGray ? "" : "Sep" , useBinary ? "Bin" : "" ); |
5425 | |
5426 | // process the data stream |
5427 | checkProcessColor = true; |
5428 | i = 0; |
5429 | |
5430 | if (isGray) { |
5431 | int g; |
5432 | for (y = 0; y < height; ++y) { |
5433 | |
5434 | // read the line |
5435 | if (checkProcessColor) { |
5436 | checkProcessColor = ((psProcessBlack & processColors) == 0); |
5437 | } |
5438 | for (x = 0; x < width; ++x) { |
5439 | imgStr->getPixel(pix: pixBuf); |
5440 | colorMap->getCMYK(x: pixBuf, cmyk: &cmyk); |
5441 | g = colToByte(x: cmyk.c) + colToByte(x: cmyk.k); |
5442 | if (checkProcessColor && g > 0) { |
5443 | processColors |= psProcessBlack; |
5444 | } |
5445 | g = 255 - g; |
5446 | if (g < 0) { |
5447 | g = 0; |
5448 | } |
5449 | if (useBinary) { |
5450 | hexBuf[i++] = g; |
5451 | } else { |
5452 | digit = g / 16; |
5453 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
5454 | digit = g % 16; |
5455 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
5456 | } |
5457 | if (i >= 64) { |
5458 | if (!useBinary) { |
5459 | hexBuf[i++] = '\n'; |
5460 | } |
5461 | writePSBuf(s: hexBuf, len: i); |
5462 | i = 0; |
5463 | } |
5464 | } |
5465 | } |
5466 | } else { |
5467 | for (y = 0; y < height; ++y) { |
5468 | |
5469 | // read the line |
5470 | if (checkProcessColor) { |
5471 | checkProcessColor = (((psProcessCyan | psProcessMagenta | psProcessYellow | psProcessBlack) & ~processColors) != 0); |
5472 | } |
5473 | if (checkProcessColor) { |
5474 | for (x = 0; x < width; ++x) { |
5475 | imgStr->getPixel(pix: pixBuf); |
5476 | colorMap->getCMYK(x: pixBuf, cmyk: &cmyk); |
5477 | lineBuf[4 * x + 0] = colToByte(x: cmyk.c); |
5478 | lineBuf[4 * x + 1] = colToByte(x: cmyk.m); |
5479 | lineBuf[4 * x + 2] = colToByte(x: cmyk.y); |
5480 | lineBuf[4 * x + 3] = colToByte(x: cmyk.k); |
5481 | addProcessColor(c: colToDbl(x: cmyk.c), m: colToDbl(x: cmyk.m), y: colToDbl(x: cmyk.y), k: colToDbl(x: cmyk.k)); |
5482 | } |
5483 | } else { |
5484 | for (x = 0; x < width; ++x) { |
5485 | imgStr->getPixel(pix: pixBuf); |
5486 | colorMap->getCMYK(x: pixBuf, cmyk: &cmyk); |
5487 | lineBuf[4 * x + 0] = colToByte(x: cmyk.c); |
5488 | lineBuf[4 * x + 1] = colToByte(x: cmyk.m); |
5489 | lineBuf[4 * x + 2] = colToByte(x: cmyk.y); |
5490 | lineBuf[4 * x + 3] = colToByte(x: cmyk.k); |
5491 | } |
5492 | } |
5493 | |
5494 | // write one line of each color component |
5495 | if (useBinary) { |
5496 | for (comp = 0; comp < 4; ++comp) { |
5497 | for (x = 0; x < width; ++x) { |
5498 | hexBuf[i++] = lineBuf[4 * x + comp]; |
5499 | if (i >= 64) { |
5500 | writePSBuf(s: hexBuf, len: i); |
5501 | i = 0; |
5502 | } |
5503 | } |
5504 | } |
5505 | } else { |
5506 | for (comp = 0; comp < 4; ++comp) { |
5507 | for (x = 0; x < width; ++x) { |
5508 | digit = lineBuf[4 * x + comp] / 16; |
5509 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
5510 | digit = lineBuf[4 * x + comp] % 16; |
5511 | hexBuf[i++] = digit + ((digit >= 10) ? 'a' - 10 : '0'); |
5512 | if (i >= 64) { |
5513 | hexBuf[i++] = '\n'; |
5514 | writePSBuf(s: hexBuf, len: i); |
5515 | i = 0; |
5516 | } |
5517 | } |
5518 | } |
5519 | } |
5520 | } |
5521 | } |
5522 | |
5523 | if (i != 0) { |
5524 | if (!useBinary) { |
5525 | hexBuf[i++] = '\n'; |
5526 | } |
5527 | writePSBuf(s: hexBuf, len: i); |
5528 | } |
5529 | |
5530 | str->close(); |
5531 | delete imgStr; |
5532 | gfree(p: lineBuf); |
5533 | |
5534 | if (maskStr && !(maskColors && colorMap)) { |
5535 | writePS(s: "pdfImClipEnd\n" ); |
5536 | } |
5537 | } |
5538 | |
5539 | void PSOutputDev::maskToClippingPath(Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert) |
5540 | { |
5541 | ImageStream *imgStr; |
5542 | unsigned char *line; |
5543 | PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut; |
5544 | int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize; |
5545 | bool emitRect, addRect, extendRect; |
5546 | int i, x0, x1, y, maskXor; |
5547 | |
5548 | imgStr = new ImageStream(maskStr, maskWidth, 1, 1); |
5549 | imgStr->reset(); |
5550 | rects0Len = rects1Len = rectsOutLen = 0; |
5551 | rectsSize = rectsOutSize = 64; |
5552 | rects0 = (PSOutImgClipRect *)gmallocn(count: rectsSize, size: sizeof(PSOutImgClipRect)); |
5553 | rects1 = (PSOutImgClipRect *)gmallocn(count: rectsSize, size: sizeof(PSOutImgClipRect)); |
5554 | rectsOut = (PSOutImgClipRect *)gmallocn(count: rectsOutSize, size: sizeof(PSOutImgClipRect)); |
5555 | maskXor = maskInvert ? 1 : 0; |
5556 | for (y = 0; y < maskHeight; ++y) { |
5557 | if (!(line = imgStr->getLine())) { |
5558 | break; |
5559 | } |
5560 | i = 0; |
5561 | rects1Len = 0; |
5562 | for (x0 = 0; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) { |
5563 | ; |
5564 | } |
5565 | for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) { |
5566 | ; |
5567 | } |
5568 | while (x0 < maskWidth || i < rects0Len) { |
5569 | emitRect = addRect = extendRect = false; |
5570 | if (x0 >= maskWidth) { |
5571 | emitRect = true; |
5572 | } else if (i >= rects0Len) { |
5573 | addRect = true; |
5574 | } else if (rects0[i].x0 < x0) { |
5575 | emitRect = true; |
5576 | } else if (x0 < rects0[i].x0) { |
5577 | addRect = true; |
5578 | } else if (rects0[i].x1 == x1) { |
5579 | extendRect = true; |
5580 | } else { |
5581 | emitRect = addRect = true; |
5582 | } |
5583 | if (emitRect) { |
5584 | if (rectsOutLen == rectsOutSize) { |
5585 | rectsOutSize *= 2; |
5586 | rectsOut = (PSOutImgClipRect *)greallocn(p: rectsOut, count: rectsOutSize, size: sizeof(PSOutImgClipRect)); |
5587 | } |
5588 | rectsOut[rectsOutLen].x0 = rects0[i].x0; |
5589 | rectsOut[rectsOutLen].x1 = rects0[i].x1; |
5590 | rectsOut[rectsOutLen].y0 = maskHeight - y; |
5591 | rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0; |
5592 | ++rectsOutLen; |
5593 | ++i; |
5594 | } |
5595 | if (addRect || extendRect) { |
5596 | if (rects1Len == rectsSize) { |
5597 | rectsSize *= 2; |
5598 | rects0 = (PSOutImgClipRect *)greallocn(p: rects0, count: rectsSize, size: sizeof(PSOutImgClipRect)); |
5599 | rects1 = (PSOutImgClipRect *)greallocn(p: rects1, count: rectsSize, size: sizeof(PSOutImgClipRect)); |
5600 | } |
5601 | rects1[rects1Len].x0 = x0; |
5602 | rects1[rects1Len].x1 = x1; |
5603 | if (addRect) { |
5604 | rects1[rects1Len].y0 = y; |
5605 | } |
5606 | if (extendRect) { |
5607 | rects1[rects1Len].y0 = rects0[i].y0; |
5608 | ++i; |
5609 | } |
5610 | ++rects1Len; |
5611 | for (x0 = x1; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) { |
5612 | ; |
5613 | } |
5614 | for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) { |
5615 | ; |
5616 | } |
5617 | } |
5618 | } |
5619 | rectsTmp = rects0; |
5620 | rects0 = rects1; |
5621 | rects1 = rectsTmp; |
5622 | i = rects0Len; |
5623 | rects0Len = rects1Len; |
5624 | rects1Len = i; |
5625 | } |
5626 | for (i = 0; i < rects0Len; ++i) { |
5627 | if (rectsOutLen == rectsOutSize) { |
5628 | rectsOutSize *= 2; |
5629 | rectsOut = (PSOutImgClipRect *)greallocn(p: rectsOut, count: rectsOutSize, size: sizeof(PSOutImgClipRect)); |
5630 | } |
5631 | rectsOut[rectsOutLen].x0 = rects0[i].x0; |
5632 | rectsOut[rectsOutLen].x1 = rects0[i].x1; |
5633 | rectsOut[rectsOutLen].y0 = maskHeight - y; |
5634 | rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0; |
5635 | ++rectsOutLen; |
5636 | } |
5637 | if (rectsOutLen < 65536 / 4) { |
5638 | writePSFmt(fmt: "{0:d} array 0\n" , rectsOutLen * 4); |
5639 | for (i = 0; i < rectsOutLen; ++i) { |
5640 | writePSFmt(fmt: "[{0:d} {1:d} {2:d} {3:d}] pr\n" , rectsOut[i].x0, rectsOut[i].y0, rectsOut[i].x1 - rectsOut[i].x0, rectsOut[i].y1 - rectsOut[i].y0); |
5641 | } |
5642 | writePSFmt(fmt: "pop {0:d} {1:d} pdfImClip\n" , maskWidth, maskHeight); |
5643 | } else { |
5644 | // would be over the limit of array size. |
5645 | // make each rectangle path and clip. |
5646 | writePS(s: "gsave newpath\n" ); |
5647 | for (i = 0; i < rectsOutLen; ++i) { |
5648 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n" , ((double)rectsOut[i].x0) / maskWidth, ((double)rectsOut[i].y0) / maskHeight, ((double)(rectsOut[i].x1 - rectsOut[i].x0)) / maskWidth, |
5649 | ((double)(rectsOut[i].y1 - rectsOut[i].y0)) / maskHeight); |
5650 | } |
5651 | writePS(s: "clip\n" ); |
5652 | } |
5653 | gfree(p: rectsOut); |
5654 | gfree(p: rects0); |
5655 | gfree(p: rects1); |
5656 | delete imgStr; |
5657 | maskStr->close(); |
5658 | } |
5659 | |
5660 | void PSOutputDev::doImageL2(GfxState *state, Object *ref, GfxImageColorMap *colorMap, bool invert, bool inlineImg, Stream *str, int width, int height, int len, const int *maskColors, Stream *maskStr, int maskWidth, int maskHeight, |
5661 | bool maskInvert) |
5662 | { |
5663 | Stream *str2; |
5664 | ImageStream *imgStr; |
5665 | unsigned char *line; |
5666 | PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut; |
5667 | int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize; |
5668 | bool emitRect, addRect, extendRect; |
5669 | GooString *s; |
5670 | int n, numComps; |
5671 | bool useLZW, useRLE, useASCII, useCompressed; |
5672 | GfxSeparationColorSpace *sepCS; |
5673 | GfxColor color; |
5674 | GfxCMYK cmyk; |
5675 | int c; |
5676 | int col, i, j, x0, x1, y; |
5677 | char dataBuf[4096]; |
5678 | |
5679 | rectsOutLen = 0; |
5680 | |
5681 | // color key masking |
5682 | if (maskColors && colorMap && !inlineImg) { |
5683 | // can't read the stream twice for inline images -- but masking |
5684 | // isn't allowed with inline images anyway |
5685 | numComps = colorMap->getNumPixelComps(); |
5686 | imgStr = new ImageStream(str, width, numComps, colorMap->getBits()); |
5687 | imgStr->reset(); |
5688 | rects0Len = rects1Len = 0; |
5689 | rectsSize = rectsOutSize = 64; |
5690 | rects0 = (PSOutImgClipRect *)gmallocn(count: rectsSize, size: sizeof(PSOutImgClipRect)); |
5691 | rects1 = (PSOutImgClipRect *)gmallocn(count: rectsSize, size: sizeof(PSOutImgClipRect)); |
5692 | rectsOut = (PSOutImgClipRect *)gmallocn(count: rectsOutSize, size: sizeof(PSOutImgClipRect)); |
5693 | for (y = 0; y < height; ++y) { |
5694 | if (!(line = imgStr->getLine())) { |
5695 | break; |
5696 | } |
5697 | i = 0; |
5698 | rects1Len = 0; |
5699 | for (x0 = 0; x0 < width; ++x0) { |
5700 | for (j = 0; j < numComps; ++j) { |
5701 | if (line[x0 * numComps + j] < maskColors[2 * j] || line[x0 * numComps + j] > maskColors[2 * j + 1]) { |
5702 | break; |
5703 | } |
5704 | } |
5705 | if (j < numComps) { |
5706 | break; |
5707 | } |
5708 | } |
5709 | for (x1 = x0; x1 < width; ++x1) { |
5710 | for (j = 0; j < numComps; ++j) { |
5711 | if (line[x1 * numComps + j] < maskColors[2 * j] || line[x1 * numComps + j] > maskColors[2 * j + 1]) { |
5712 | break; |
5713 | } |
5714 | } |
5715 | if (j == numComps) { |
5716 | break; |
5717 | } |
5718 | } |
5719 | while (x0 < width || i < rects0Len) { |
5720 | emitRect = addRect = extendRect = false; |
5721 | if (x0 >= width) { |
5722 | emitRect = true; |
5723 | } else if (i >= rects0Len) { |
5724 | addRect = true; |
5725 | } else if (rects0[i].x0 < x0) { |
5726 | emitRect = true; |
5727 | } else if (x0 < rects0[i].x0) { |
5728 | addRect = true; |
5729 | } else if (rects0[i].x1 == x1) { |
5730 | extendRect = true; |
5731 | } else { |
5732 | emitRect = addRect = true; |
5733 | } |
5734 | if (emitRect) { |
5735 | if (rectsOutLen == rectsOutSize) { |
5736 | rectsOutSize *= 2; |
5737 | rectsOut = (PSOutImgClipRect *)greallocn(p: rectsOut, count: rectsOutSize, size: sizeof(PSOutImgClipRect)); |
5738 | } |
5739 | rectsOut[rectsOutLen].x0 = rects0[i].x0; |
5740 | rectsOut[rectsOutLen].x1 = rects0[i].x1; |
5741 | rectsOut[rectsOutLen].y0 = height - y; |
5742 | rectsOut[rectsOutLen].y1 = height - rects0[i].y0; |
5743 | ++rectsOutLen; |
5744 | ++i; |
5745 | } |
5746 | if (addRect || extendRect) { |
5747 | if (rects1Len == rectsSize) { |
5748 | rectsSize *= 2; |
5749 | rects0 = (PSOutImgClipRect *)greallocn(p: rects0, count: rectsSize, size: sizeof(PSOutImgClipRect)); |
5750 | rects1 = (PSOutImgClipRect *)greallocn(p: rects1, count: rectsSize, size: sizeof(PSOutImgClipRect)); |
5751 | } |
5752 | rects1[rects1Len].x0 = x0; |
5753 | rects1[rects1Len].x1 = x1; |
5754 | if (addRect) { |
5755 | rects1[rects1Len].y0 = y; |
5756 | } |
5757 | if (extendRect) { |
5758 | rects1[rects1Len].y0 = rects0[i].y0; |
5759 | ++i; |
5760 | } |
5761 | ++rects1Len; |
5762 | for (x0 = x1; x0 < width; ++x0) { |
5763 | for (j = 0; j < numComps; ++j) { |
5764 | if (line[x0 * numComps + j] < maskColors[2 * j] || line[x0 * numComps + j] > maskColors[2 * j + 1]) { |
5765 | break; |
5766 | } |
5767 | } |
5768 | if (j < numComps) { |
5769 | break; |
5770 | } |
5771 | } |
5772 | for (x1 = x0; x1 < width; ++x1) { |
5773 | for (j = 0; j < numComps; ++j) { |
5774 | if (line[x1 * numComps + j] < maskColors[2 * j] || line[x1 * numComps + j] > maskColors[2 * j + 1]) { |
5775 | break; |
5776 | } |
5777 | } |
5778 | if (j == numComps) { |
5779 | break; |
5780 | } |
5781 | } |
5782 | } |
5783 | } |
5784 | rectsTmp = rects0; |
5785 | rects0 = rects1; |
5786 | rects1 = rectsTmp; |
5787 | i = rects0Len; |
5788 | rects0Len = rects1Len; |
5789 | rects1Len = i; |
5790 | } |
5791 | for (i = 0; i < rects0Len; ++i) { |
5792 | if (rectsOutLen == rectsOutSize) { |
5793 | rectsOutSize *= 2; |
5794 | rectsOut = (PSOutImgClipRect *)greallocn(p: rectsOut, count: rectsOutSize, size: sizeof(PSOutImgClipRect)); |
5795 | } |
5796 | rectsOut[rectsOutLen].x0 = rects0[i].x0; |
5797 | rectsOut[rectsOutLen].x1 = rects0[i].x1; |
5798 | rectsOut[rectsOutLen].y0 = height - y; |
5799 | rectsOut[rectsOutLen].y1 = height - rects0[i].y0; |
5800 | ++rectsOutLen; |
5801 | } |
5802 | if (rectsOutLen < 65536 / 4) { |
5803 | writePSFmt(fmt: "{0:d} array 0\n" , rectsOutLen * 4); |
5804 | for (i = 0; i < rectsOutLen; ++i) { |
5805 | writePSFmt(fmt: "[{0:d} {1:d} {2:d} {3:d}] pr\n" , rectsOut[i].x0, rectsOut[i].y0, rectsOut[i].x1 - rectsOut[i].x0, rectsOut[i].y1 - rectsOut[i].y0); |
5806 | } |
5807 | writePSFmt(fmt: "pop {0:d} {1:d} pdfImClip\n" , width, height); |
5808 | } else { |
5809 | // would be over the limit of array size. |
5810 | // make each rectangle path and clip. |
5811 | writePS(s: "gsave newpath\n" ); |
5812 | for (i = 0; i < rectsOutLen; ++i) { |
5813 | writePSFmt(fmt: "{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n" , ((double)rectsOut[i].x0) / width, ((double)rectsOut[i].y0) / height, ((double)(rectsOut[i].x1 - rectsOut[i].x0)) / width, |
5814 | ((double)(rectsOut[i].y1 - rectsOut[i].y0)) / height); |
5815 | } |
5816 | writePS(s: "clip\n" ); |
5817 | } |
5818 | gfree(p: rectsOut); |
5819 | gfree(p: rects0); |
5820 | gfree(p: rects1); |
5821 | delete imgStr; |
5822 | str->close(); |
5823 | |
5824 | // explicit masking |
5825 | } else if (maskStr) { |
5826 | maskToClippingPath(maskStr, maskWidth, maskHeight, maskInvert); |
5827 | } |
5828 | |
5829 | // color space |
5830 | if (colorMap) { |
5831 | // Do not update the process color list for custom colors |
5832 | bool isCustomColor = (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) && colorMap->getColorSpace()->getMode() == csDeviceN; |
5833 | dumpColorSpaceL2(state, colorSpace: colorMap->getColorSpace(), genXform: false, updateColors: !isCustomColor, map01: false); |
5834 | writePS(s: " setcolorspace\n" ); |
5835 | } |
5836 | |
5837 | // set up the image data |
5838 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
5839 | if (inlineImg) { |
5840 | // create an array |
5841 | str2 = new FixedLengthEncoder(str, len); |
5842 | if (getEnableLZW()) { |
5843 | str2 = new LZWEncoder(str2); |
5844 | } else { |
5845 | str2 = new RunLengthEncoder(str2); |
5846 | } |
5847 | if (useASCIIHex) { |
5848 | str2 = new ASCIIHexEncoder(str2); |
5849 | } else { |
5850 | str2 = new ASCII85Encoder(str2); |
5851 | } |
5852 | str2->reset(); |
5853 | col = 0; |
5854 | writePS(s: (char *)(useASCIIHex ? "[<" : "[<~" )); |
5855 | do { |
5856 | do { |
5857 | c = str2->getChar(); |
5858 | } while (c == '\n' || c == '\r'); |
5859 | if (c == (useASCIIHex ? '>' : '~') || c == EOF) { |
5860 | break; |
5861 | } |
5862 | if (c == 'z') { |
5863 | writePSChar(c); |
5864 | ++col; |
5865 | } else { |
5866 | writePSChar(c); |
5867 | ++col; |
5868 | for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) { |
5869 | do { |
5870 | c = str2->getChar(); |
5871 | } while (c == '\n' || c == '\r'); |
5872 | if (c == (useASCIIHex ? '>' : '~') || c == EOF) { |
5873 | break; |
5874 | } |
5875 | writePSChar(c); |
5876 | ++col; |
5877 | } |
5878 | } |
5879 | // each line is: "<~...data...~><eol>" |
5880 | // so max data length = 255 - 6 = 249 |
5881 | // chunks are 1 or 5 bytes each, so we have to stop at 245 |
5882 | // but make it 240 just to be safe |
5883 | if (col > 240) { |
5884 | writePS(s: (char *)(useASCIIHex ? ">\n<" : "~>\n<~" )); |
5885 | col = 0; |
5886 | } |
5887 | } while (c != (useASCIIHex ? '>' : '~') && c != EOF); |
5888 | writePS(s: (char *)(useASCIIHex ? ">\n" : "~>\n" )); |
5889 | // add an extra entry because the LZWDecode/RunLengthDecode filter may |
5890 | // read past the end |
5891 | writePS(s: "<>]\n" ); |
5892 | writePS(s: "0\n" ); |
5893 | str2->close(); |
5894 | delete str2; |
5895 | } else { |
5896 | // make sure the image is setup, it sometimes is not like on bug #17645 |
5897 | setupImage(id: ref->getRef(), str, mask: false); |
5898 | // set up to use the array already created by setupImages() |
5899 | writePSFmt(fmt: "ImData_{0:d}_{1:d} 0 0\n" , ref->getRefNum(), ref->getRefGen()); |
5900 | } |
5901 | } |
5902 | |
5903 | // image dictionary |
5904 | writePS(s: "<<\n /ImageType 1\n" ); |
5905 | |
5906 | // width, height, matrix, bits per component |
5907 | writePSFmt(fmt: " /Width {0:d}\n" , width); |
5908 | writePSFmt(fmt: " /Height {0:d}\n" , height); |
5909 | writePSFmt(fmt: " /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n" , width, -height, height); |
5910 | if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) { |
5911 | writePS(s: " /BitsPerComponent 8\n" ); |
5912 | } else { |
5913 | writePSFmt(fmt: " /BitsPerComponent {0:d}\n" , colorMap ? colorMap->getBits() : 1); |
5914 | } |
5915 | |
5916 | // decode |
5917 | if (colorMap) { |
5918 | writePS(s: " /Decode [" ); |
5919 | if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap->getColorSpace()->getMode() == csSeparation) { |
5920 | // this matches up with the code in the pdfImSep operator |
5921 | n = (1 << colorMap->getBits()) - 1; |
5922 | writePSFmt(fmt: "{0:.4g} {1:.4g}" , colorMap->getDecodeLow(i: 0) * n, colorMap->getDecodeHigh(i: 0) * n); |
5923 | } else if (colorMap->getColorSpace()->getMode() == csDeviceN) { |
5924 | numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getAlt()->getNComps(); |
5925 | for (i = 0; i < numComps; ++i) { |
5926 | if (i > 0) { |
5927 | writePS(s: " " ); |
5928 | } |
5929 | writePS(s: "0 1" ); |
5930 | } |
5931 | } else { |
5932 | numComps = colorMap->getNumPixelComps(); |
5933 | for (i = 0; i < numComps; ++i) { |
5934 | if (i > 0) { |
5935 | writePS(s: " " ); |
5936 | } |
5937 | writePSFmt(fmt: "{0:.4g} {1:.4g}" , colorMap->getDecodeLow(i), colorMap->getDecodeHigh(i)); |
5938 | } |
5939 | } |
5940 | writePS(s: "]\n" ); |
5941 | } else { |
5942 | writePSFmt(fmt: " /Decode [{0:d} {1:d}]\n" , invert ? 1 : 0, invert ? 0 : 1); |
5943 | } |
5944 | |
5945 | // data source |
5946 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
5947 | if (inlineImg) { |
5948 | writePS(s: " /DataSource { pdfImStr }\n" ); |
5949 | } else { |
5950 | writePS(s: " /DataSource { dup 65535 ge { pop 1 add 0 } if 2 index 2" |
5951 | " index get 1 index get exch 1 add exch }\n" ); |
5952 | } |
5953 | } else { |
5954 | writePS(s: " /DataSource currentfile\n" ); |
5955 | } |
5956 | |
5957 | // filters |
5958 | if ((mode == psModeForm || inType3Char || preloadImagesForms) && uncompressPreloadedImages) { |
5959 | s = nullptr; |
5960 | useLZW = useRLE = false; |
5961 | useCompressed = false; |
5962 | useASCII = false; |
5963 | } else { |
5964 | s = str->getPSFilter(psLevel: level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3, indent: " " ); |
5965 | if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) || inlineImg || !s) { |
5966 | if (getEnableLZW()) { |
5967 | useLZW = true; |
5968 | useRLE = false; |
5969 | } else { |
5970 | useRLE = true; |
5971 | useLZW = false; |
5972 | } |
5973 | useASCII = !(mode == psModeForm || inType3Char || preloadImagesForms); |
5974 | useCompressed = false; |
5975 | } else { |
5976 | useLZW = useRLE = false; |
5977 | useASCII = str->isBinary() && !(mode == psModeForm || inType3Char || preloadImagesForms); |
5978 | useCompressed = true; |
5979 | } |
5980 | } |
5981 | if (useASCII) { |
5982 | writePSFmt(fmt: " /ASCII{0:s}Decode filter\n" , useASCIIHex ? "Hex" : "85" ); |
5983 | } |
5984 | if (useLZW) { |
5985 | writePS(s: " /LZWDecode filter\n" ); |
5986 | } else if (useRLE) { |
5987 | writePS(s: " /RunLengthDecode filter\n" ); |
5988 | } |
5989 | if (useCompressed) { |
5990 | writePS(s: s->c_str()); |
5991 | } |
5992 | if (s) { |
5993 | delete s; |
5994 | } |
5995 | |
5996 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
5997 | |
5998 | // end of image dictionary |
5999 | writePSFmt(fmt: ">>\n{0:s}\n" , colorMap ? "image" : "imagemask" ); |
6000 | |
6001 | // get rid of the array and index |
6002 | if (!inlineImg) { |
6003 | writePS(s: "pop " ); |
6004 | } |
6005 | writePS(s: "pop pop\n" ); |
6006 | |
6007 | } else { |
6008 | |
6009 | // cut off inline image streams at appropriate length |
6010 | if (inlineImg) { |
6011 | str = new FixedLengthEncoder(str, len); |
6012 | } else if (useCompressed) { |
6013 | str = str->getUndecodedStream(); |
6014 | } |
6015 | |
6016 | // recode DeviceN data |
6017 | if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) { |
6018 | str = new DeviceNRecoder(str, width, height, colorMap); |
6019 | } |
6020 | |
6021 | // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters |
6022 | if (useLZW) { |
6023 | str = new LZWEncoder(str); |
6024 | } else if (useRLE) { |
6025 | str = new RunLengthEncoder(str); |
6026 | } |
6027 | if (useASCII) { |
6028 | if (useASCIIHex) { |
6029 | str = new ASCIIHexEncoder(str); |
6030 | } else { |
6031 | str = new ASCII85Encoder(str); |
6032 | } |
6033 | } |
6034 | |
6035 | // end of image dictionary |
6036 | writePS(s: ">>\n" ); |
6037 | #ifdef OPI_SUPPORT |
6038 | if (opi13Nest) { |
6039 | if (inlineImg) { |
6040 | // this can't happen -- OPI dictionaries are in XObjects |
6041 | error(category: errSyntaxError, pos: -1, msg: "OPI in inline image" ); |
6042 | n = 0; |
6043 | } else { |
6044 | // need to read the stream to count characters -- the length |
6045 | // is data-dependent (because of ASCII and LZW/RLE filters) |
6046 | str->reset(); |
6047 | n = 0; |
6048 | while ((c = str->getChar()) != EOF) { |
6049 | ++n; |
6050 | } |
6051 | str->close(); |
6052 | } |
6053 | // +6/7 for "pdfIm\n" / "pdfImM\n" |
6054 | // +8 for newline + trailer |
6055 | n += colorMap ? 14 : 15; |
6056 | writePSFmt(fmt: "%%BeginData: {0:d} Hex Bytes\n" , n); |
6057 | } |
6058 | #endif |
6059 | if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap && colorMap->getColorSpace()->getMode() == csSeparation && colorMap->getBits() == 8) { |
6060 | color.c[0] = gfxColorComp1; |
6061 | sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace(); |
6062 | sepCS->getCMYK(color: &color, cmyk: &cmyk); |
6063 | writePSFmt(fmt: "{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n" , colToDbl(x: cmyk.c), colToDbl(x: cmyk.m), colToDbl(x: cmyk.y), colToDbl(x: cmyk.k), sepCS->getName()); |
6064 | } else { |
6065 | writePSFmt(fmt: "{0:s}\n" , colorMap ? "pdfIm" : "pdfImM" ); |
6066 | } |
6067 | |
6068 | // copy the stream data |
6069 | str->reset(); |
6070 | i = 0; |
6071 | while ((c = str->getChar()) != EOF) { |
6072 | dataBuf[i++] = c; |
6073 | if (i >= (int)sizeof(dataBuf)) { |
6074 | writePSBuf(s: dataBuf, len: i); |
6075 | i = 0; |
6076 | } |
6077 | } |
6078 | if (i > 0) { |
6079 | writePSBuf(s: dataBuf, len: i); |
6080 | } |
6081 | str->close(); |
6082 | |
6083 | // add newline and trailer to the end |
6084 | writePSChar(c: '\n'); |
6085 | writePS(s: "%-EOD-\n" ); |
6086 | #ifdef OPI_SUPPORT |
6087 | if (opi13Nest) { |
6088 | writePS(s: "%%EndData\n" ); |
6089 | } |
6090 | #endif |
6091 | |
6092 | // delete encoders |
6093 | if (useLZW || useRLE || useASCII || inlineImg) { |
6094 | delete str; |
6095 | } |
6096 | } |
6097 | |
6098 | if ((maskColors && colorMap && !inlineImg) || maskStr) { |
6099 | if (rectsOutLen < 65536 / 4) { |
6100 | writePS(s: "pdfImClipEnd\n" ); |
6101 | } else { |
6102 | writePS(s: "grestore\n" ); |
6103 | } |
6104 | } |
6105 | } |
6106 | |
6107 | //~ this doesn't currently support OPI |
6108 | void PSOutputDev::doImageL3(GfxState *state, Object *ref, GfxImageColorMap *colorMap, bool invert, bool inlineImg, Stream *str, int width, int height, int len, const int *maskColors, Stream *maskStr, int maskWidth, int maskHeight, |
6109 | bool maskInvert) |
6110 | { |
6111 | Stream *str2; |
6112 | GooString *s; |
6113 | int n, numComps; |
6114 | bool useFlate, useLZW, useRLE, useASCII, useCompressed; |
6115 | bool maskUseFlate, maskUseLZW, maskUseRLE, maskUseASCII, maskUseCompressed; |
6116 | GooString *maskFilters; |
6117 | GfxSeparationColorSpace *sepCS; |
6118 | GfxColor color; |
6119 | GfxCMYK cmyk; |
6120 | int c; |
6121 | int col, i; |
6122 | |
6123 | useFlate = useLZW = useRLE = useASCII = useCompressed = false; |
6124 | maskUseFlate = maskUseLZW = maskUseRLE = maskUseASCII = maskUseCompressed = false; |
6125 | maskFilters = nullptr; // make gcc happy |
6126 | |
6127 | // explicit masking |
6128 | if (maskStr) { |
6129 | |
6130 | // mask data source |
6131 | if ((mode == psModeForm || inType3Char || preloadImagesForms) && uncompressPreloadedImages) { |
6132 | s = nullptr; |
6133 | } else { |
6134 | s = maskStr->getPSFilter(psLevel: 3, indent: " " ); |
6135 | if (!s) { |
6136 | if (getEnableFlate()) { |
6137 | maskUseFlate = true; |
6138 | } else if (getEnableLZW()) { |
6139 | maskUseLZW = true; |
6140 | } else { |
6141 | maskUseRLE = true; |
6142 | } |
6143 | maskUseASCII = !(mode == psModeForm || inType3Char || preloadImagesForms); |
6144 | } else { |
6145 | maskUseASCII = maskStr->isBinary() && !(mode == psModeForm || inType3Char || preloadImagesForms); |
6146 | maskUseCompressed = true; |
6147 | } |
6148 | } |
6149 | maskFilters = new GooString(); |
6150 | if (maskUseASCII) { |
6151 | maskFilters->appendf(fmt: " /ASCII{0:s}Decode filter\n" , useASCIIHex ? "Hex" : "85" ); |
6152 | } |
6153 | if (maskUseFlate) { |
6154 | maskFilters->append(str: " /FlateDecode filter\n" ); |
6155 | } else if (maskUseLZW) { |
6156 | maskFilters->append(str: " /LZWDecode filter\n" ); |
6157 | } else if (maskUseRLE) { |
6158 | maskFilters->append(str: " /RunLengthDecode filter\n" ); |
6159 | } |
6160 | if (maskUseCompressed) { |
6161 | maskFilters->append(str: s); |
6162 | } |
6163 | if (s) { |
6164 | delete s; |
6165 | } |
6166 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
6167 | writePSFmt(fmt: "MaskData_{0:d}_{1:d} pdfMaskInit\n" , ref->getRefNum(), ref->getRefGen()); |
6168 | } else { |
6169 | writePS(s: "currentfile\n" ); |
6170 | writePS(s: maskFilters->c_str()); |
6171 | writePS(s: "pdfMask\n" ); |
6172 | |
6173 | // add FlateEncode/LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters |
6174 | if (maskUseCompressed) { |
6175 | maskStr = maskStr->getUndecodedStream(); |
6176 | } |
6177 | if (maskUseFlate) { |
6178 | maskStr = new FlateEncoder(maskStr); |
6179 | } else if (maskUseLZW) { |
6180 | maskStr = new LZWEncoder(maskStr); |
6181 | } else if (maskUseRLE) { |
6182 | maskStr = new RunLengthEncoder(maskStr); |
6183 | } |
6184 | if (maskUseASCII) { |
6185 | if (useASCIIHex) { |
6186 | maskStr = new ASCIIHexEncoder(maskStr); |
6187 | } else { |
6188 | maskStr = new ASCII85Encoder(maskStr); |
6189 | } |
6190 | } |
6191 | |
6192 | // copy the stream data |
6193 | maskStr->reset(); |
6194 | while ((c = maskStr->getChar()) != EOF) { |
6195 | writePSChar(c); |
6196 | } |
6197 | maskStr->close(); |
6198 | writePSChar(c: '\n'); |
6199 | writePS(s: "%-EOD-\n" ); |
6200 | |
6201 | // delete encoders |
6202 | if (maskUseFlate || maskUseLZW || maskUseRLE || maskUseASCII) { |
6203 | delete maskStr; |
6204 | } |
6205 | } |
6206 | } |
6207 | |
6208 | // color space |
6209 | if (colorMap) { |
6210 | // Do not update the process color list for custom colors |
6211 | bool isCustomColor = (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) && colorMap->getColorSpace()->getMode() == csDeviceN; |
6212 | dumpColorSpaceL2(state, colorSpace: colorMap->getColorSpace(), genXform: false, updateColors: !isCustomColor, map01: false); |
6213 | writePS(s: " setcolorspace\n" ); |
6214 | } |
6215 | |
6216 | // set up the image data |
6217 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
6218 | if (inlineImg) { |
6219 | // create an array |
6220 | str2 = new FixedLengthEncoder(str, len); |
6221 | if (getEnableFlate()) { |
6222 | str2 = new FlateEncoder(str2); |
6223 | } else if (getEnableLZW()) { |
6224 | str2 = new LZWEncoder(str2); |
6225 | } else { |
6226 | str2 = new RunLengthEncoder(str2); |
6227 | } |
6228 | if (useASCIIHex) { |
6229 | str2 = new ASCIIHexEncoder(str2); |
6230 | } else { |
6231 | str2 = new ASCII85Encoder(str2); |
6232 | } |
6233 | str2->reset(); |
6234 | col = 0; |
6235 | writePS(s: (char *)(useASCIIHex ? "[<" : "[<~" )); |
6236 | do { |
6237 | do { |
6238 | c = str2->getChar(); |
6239 | } while (c == '\n' || c == '\r'); |
6240 | if (c == (useASCIIHex ? '>' : '~') || c == EOF) { |
6241 | break; |
6242 | } |
6243 | if (c == 'z') { |
6244 | writePSChar(c); |
6245 | ++col; |
6246 | } else { |
6247 | writePSChar(c); |
6248 | ++col; |
6249 | for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) { |
6250 | do { |
6251 | c = str2->getChar(); |
6252 | } while (c == '\n' || c == '\r'); |
6253 | if (c == (useASCIIHex ? '>' : '~') || c == EOF) { |
6254 | break; |
6255 | } |
6256 | writePSChar(c); |
6257 | ++col; |
6258 | } |
6259 | } |
6260 | // each line is: "<~...data...~><eol>" |
6261 | // so max data length = 255 - 6 = 249 |
6262 | // chunks are 1 or 5 bytes each, so we have to stop at 245 |
6263 | // but make it 240 just to be safe |
6264 | if (col > 240) { |
6265 | writePS(s: (char *)(useASCIIHex ? ">\n<" : "~>\n<~" )); |
6266 | col = 0; |
6267 | } |
6268 | } while (c != (useASCIIHex ? '>' : '~') && c != EOF); |
6269 | writePS(s: (char *)(useASCIIHex ? ">\n" : "~>\n" )); |
6270 | // add an extra entry because the FlateEncode/LZWDecode/RunLengthDecode filter may |
6271 | // read past the end |
6272 | writePS(s: "<>]\n" ); |
6273 | writePS(s: "0\n" ); |
6274 | str2->close(); |
6275 | delete str2; |
6276 | } else { |
6277 | // make sure the image is setup, it sometimes is not like on bug #17645 |
6278 | setupImage(id: ref->getRef(), str, mask: false); |
6279 | // set up to use the array already created by setupImages() |
6280 | writePSFmt(fmt: "ImData_{0:d}_{1:d} 0 0\n" , ref->getRefNum(), ref->getRefGen()); |
6281 | } |
6282 | } |
6283 | |
6284 | // explicit masking |
6285 | if (maskStr) { |
6286 | writePS(s: "<<\n /ImageType 3\n" ); |
6287 | writePS(s: " /InterleaveType 3\n" ); |
6288 | writePS(s: " /DataDict\n" ); |
6289 | } |
6290 | |
6291 | // image (data) dictionary |
6292 | writePSFmt(fmt: "<<\n /ImageType {0:d}\n" , (maskColors && colorMap) ? 4 : 1); |
6293 | |
6294 | // color key masking |
6295 | if (maskColors && colorMap) { |
6296 | writePS(s: " /MaskColor [\n" ); |
6297 | numComps = colorMap->getNumPixelComps(); |
6298 | for (i = 0; i < 2 * numComps; i += 2) { |
6299 | writePSFmt(fmt: " {0:d} {1:d}\n" , maskColors[i], maskColors[i + 1]); |
6300 | } |
6301 | writePS(s: " ]\n" ); |
6302 | } |
6303 | |
6304 | // width, height, matrix, bits per component |
6305 | writePSFmt(fmt: " /Width {0:d}\n" , width); |
6306 | writePSFmt(fmt: " /Height {0:d}\n" , height); |
6307 | writePSFmt(fmt: " /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n" , width, -height, height); |
6308 | if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) { |
6309 | writePS(s: " /BitsPerComponent 8\n" ); |
6310 | } else { |
6311 | writePSFmt(fmt: " /BitsPerComponent {0:d}\n" , colorMap ? colorMap->getBits() : 1); |
6312 | } |
6313 | |
6314 | // decode |
6315 | if (colorMap) { |
6316 | writePS(s: " /Decode [" ); |
6317 | if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap->getColorSpace()->getMode() == csSeparation) { |
6318 | // this matches up with the code in the pdfImSep operator |
6319 | n = (1 << colorMap->getBits()) - 1; |
6320 | writePSFmt(fmt: "{0:.4g} {1:.4g}" , colorMap->getDecodeLow(i: 0) * n, colorMap->getDecodeHigh(i: 0) * n); |
6321 | } else { |
6322 | numComps = colorMap->getNumPixelComps(); |
6323 | for (i = 0; i < numComps; ++i) { |
6324 | if (i > 0) { |
6325 | writePS(s: " " ); |
6326 | } |
6327 | writePSFmt(fmt: "{0:.4g} {1:.4g}" , colorMap->getDecodeLow(i), colorMap->getDecodeHigh(i)); |
6328 | } |
6329 | } |
6330 | writePS(s: "]\n" ); |
6331 | } else { |
6332 | writePSFmt(fmt: " /Decode [{0:d} {1:d}]\n" , invert ? 1 : 0, invert ? 0 : 1); |
6333 | } |
6334 | |
6335 | // data source |
6336 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
6337 | if (inlineImg) { |
6338 | writePS(s: " /DataSource { pdfImStr }\n" ); |
6339 | } else { |
6340 | writePS(s: " /DataSource { dup 65535 ge { pop 1 add 0 } if 2 index 2" |
6341 | " index get 1 index get exch 1 add exch }\n" ); |
6342 | } |
6343 | } else { |
6344 | writePS(s: " /DataSource currentfile\n" ); |
6345 | } |
6346 | |
6347 | // filters |
6348 | |
6349 | useFlate = useLZW = useRLE = false; |
6350 | useCompressed = false; |
6351 | useASCII = false; |
6352 | |
6353 | if ((mode == psModeForm || inType3Char || preloadImagesForms) && uncompressPreloadedImages) { |
6354 | s = nullptr; |
6355 | } else { |
6356 | s = str->getPSFilter(psLevel: level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3, indent: " " ); |
6357 | if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) || inlineImg || !s) { |
6358 | if (getEnableFlate()) { |
6359 | useFlate = true; |
6360 | } else if (getEnableLZW()) { |
6361 | useLZW = true; |
6362 | } else { |
6363 | useRLE = true; |
6364 | } |
6365 | useASCII = !(mode == psModeForm || inType3Char || preloadImagesForms); |
6366 | } else { |
6367 | useASCII = str->isBinary() && !(mode == psModeForm || inType3Char || preloadImagesForms); |
6368 | useCompressed = true; |
6369 | } |
6370 | } |
6371 | if (useASCII) { |
6372 | writePSFmt(fmt: " /ASCII{0:s}Decode filter\n" , useASCIIHex ? "Hex" : "85" ); |
6373 | } |
6374 | if (useFlate) { |
6375 | writePS(s: " /FlateDecode filter\n" ); |
6376 | } else if (useLZW) { |
6377 | writePS(s: " /LZWDecode filter\n" ); |
6378 | } else if (useRLE) { |
6379 | writePS(s: " /RunLengthDecode filter\n" ); |
6380 | } |
6381 | if (useCompressed) { |
6382 | writePS(s: s->c_str()); |
6383 | } |
6384 | if (s) { |
6385 | delete s; |
6386 | } |
6387 | |
6388 | // end of image (data) dictionary |
6389 | writePS(s: ">>\n" ); |
6390 | |
6391 | // explicit masking |
6392 | if (maskStr) { |
6393 | writePS(s: " /MaskDict\n" ); |
6394 | writePS(s: "<<\n" ); |
6395 | writePS(s: " /ImageType 1\n" ); |
6396 | writePSFmt(fmt: " /Width {0:d}\n" , maskWidth); |
6397 | writePSFmt(fmt: " /Height {0:d}\n" , maskHeight); |
6398 | writePSFmt(fmt: " /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n" , maskWidth, -maskHeight, maskHeight); |
6399 | writePS(s: " /BitsPerComponent 1\n" ); |
6400 | writePSFmt(fmt: " /Decode [{0:d} {1:d}]\n" , maskInvert ? 1 : 0, maskInvert ? 0 : 1); |
6401 | |
6402 | // mask data source |
6403 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
6404 | writePS(s: " /DataSource {pdfMaskSrc}\n" ); |
6405 | writePS(s: maskFilters->c_str()); |
6406 | } else { |
6407 | writePS(s: " /DataSource maskStream\n" ); |
6408 | } |
6409 | delete maskFilters; |
6410 | |
6411 | writePS(s: ">>\n" ); |
6412 | writePS(s: ">>\n" ); |
6413 | } |
6414 | |
6415 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
6416 | |
6417 | // image command |
6418 | writePSFmt(fmt: "{0:s}\n" , colorMap ? "image" : "imagemask" ); |
6419 | |
6420 | } else { |
6421 | |
6422 | if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap && colorMap->getColorSpace()->getMode() == csSeparation && colorMap->getBits() == 8) { |
6423 | color.c[0] = gfxColorComp1; |
6424 | sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace(); |
6425 | sepCS->getCMYK(color: &color, cmyk: &cmyk); |
6426 | writePSFmt(fmt: "{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n" , colToDbl(x: cmyk.c), colToDbl(x: cmyk.m), colToDbl(x: cmyk.y), colToDbl(x: cmyk.k), sepCS->getName()); |
6427 | } else { |
6428 | writePSFmt(fmt: "{0:s}\n" , colorMap ? "pdfIm" : "pdfImM" ); |
6429 | } |
6430 | } |
6431 | |
6432 | // get rid of the array and index |
6433 | if (mode == psModeForm || inType3Char || preloadImagesForms) { |
6434 | if (!inlineImg) { |
6435 | writePS(s: "pop " ); |
6436 | } |
6437 | writePS(s: "pop pop\n" ); |
6438 | |
6439 | // image data |
6440 | } else { |
6441 | |
6442 | // cut off inline image streams at appropriate length |
6443 | if (inlineImg) { |
6444 | str = new FixedLengthEncoder(str, len); |
6445 | } else if (useCompressed) { |
6446 | str = str->getUndecodedStream(); |
6447 | } |
6448 | |
6449 | // add FlateEncode/LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters |
6450 | if (useFlate) { |
6451 | str = new FlateEncoder(str); |
6452 | } else if (useLZW) { |
6453 | str = new LZWEncoder(str); |
6454 | } else if (useRLE) { |
6455 | str = new RunLengthEncoder(str); |
6456 | } |
6457 | if (useASCII) { |
6458 | if (useASCIIHex) { |
6459 | str = new ASCIIHexEncoder(str); |
6460 | } else { |
6461 | str = new ASCII85Encoder(str); |
6462 | } |
6463 | } |
6464 | |
6465 | // copy the stream data |
6466 | str->reset(); |
6467 | while ((c = str->getChar()) != EOF) { |
6468 | writePSChar(c); |
6469 | } |
6470 | str->close(); |
6471 | |
6472 | // add newline and trailer to the end |
6473 | writePSChar(c: '\n'); |
6474 | writePS(s: "%-EOD-\n" ); |
6475 | |
6476 | // delete encoders |
6477 | if (useFlate || useLZW || useRLE || useASCII || inlineImg) { |
6478 | delete str; |
6479 | } |
6480 | } |
6481 | |
6482 | // close the mask stream |
6483 | if (maskStr) { |
6484 | if (!(mode == psModeForm || inType3Char || preloadImagesForms)) { |
6485 | writePS(s: "pdfMaskEnd\n" ); |
6486 | } |
6487 | } |
6488 | } |
6489 | |
6490 | void PSOutputDev::dumpColorSpaceL2(GfxState *state, GfxColorSpace *colorSpace, bool genXform, bool updateColors, bool map01) |
6491 | { |
6492 | GfxCalGrayColorSpace *calGrayCS; |
6493 | GfxCalRGBColorSpace *calRGBCS; |
6494 | GfxLabColorSpace *labCS; |
6495 | GfxIndexedColorSpace *indexedCS; |
6496 | GfxSeparationColorSpace *separationCS; |
6497 | GfxDeviceNColorSpace *deviceNCS; |
6498 | GfxColorSpace *baseCS; |
6499 | unsigned char *lookup, *p; |
6500 | double x[gfxColorMaxComps], y[gfxColorMaxComps]; |
6501 | double low[gfxColorMaxComps], range[gfxColorMaxComps]; |
6502 | GfxColor color; |
6503 | GfxCMYK cmyk; |
6504 | int n, numComps, numAltComps; |
6505 | int byte; |
6506 | int i, j, k; |
6507 | |
6508 | switch (colorSpace->getMode()) { |
6509 | |
6510 | case csDeviceGray: |
6511 | writePS(s: "/DeviceGray" ); |
6512 | if (genXform) { |
6513 | writePS(s: " {}" ); |
6514 | } |
6515 | if (updateColors) { |
6516 | processColors |= psProcessBlack; |
6517 | } |
6518 | break; |
6519 | |
6520 | case csCalGray: |
6521 | calGrayCS = (GfxCalGrayColorSpace *)colorSpace; |
6522 | writePS(s: "[/CIEBasedA <<\n" ); |
6523 | writePSFmt(fmt: " /DecodeA {{{0:.4g} exp}} bind\n" , calGrayCS->getGamma()); |
6524 | writePSFmt(fmt: " /MatrixA [{0:.4g} {1:.4g} {2:.4g}]\n" , calGrayCS->getWhiteX(), calGrayCS->getWhiteY(), calGrayCS->getWhiteZ()); |
6525 | writePSFmt(fmt: " /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n" , calGrayCS->getWhiteX(), calGrayCS->getWhiteY(), calGrayCS->getWhiteZ()); |
6526 | writePSFmt(fmt: " /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n" , calGrayCS->getBlackX(), calGrayCS->getBlackY(), calGrayCS->getBlackZ()); |
6527 | writePS(s: ">>]" ); |
6528 | if (genXform) { |
6529 | writePS(s: " {}" ); |
6530 | } |
6531 | if (updateColors) { |
6532 | processColors |= psProcessBlack; |
6533 | } |
6534 | break; |
6535 | |
6536 | case csDeviceRGB: |
6537 | writePS(s: "/DeviceRGB" ); |
6538 | if (genXform) { |
6539 | writePS(s: " {}" ); |
6540 | } |
6541 | if (updateColors) { |
6542 | processColors |= psProcessCMYK; |
6543 | } |
6544 | break; |
6545 | |
6546 | case csCalRGB: |
6547 | calRGBCS = (GfxCalRGBColorSpace *)colorSpace; |
6548 | writePS(s: "[/CIEBasedABC <<\n" ); |
6549 | writePSFmt(fmt: " /DecodeABC [{{{0:.4g} exp}} bind {{{1:.4g} exp}} bind {{{2:.4g} exp}} bind]\n" , calRGBCS->getGammaR(), calRGBCS->getGammaG(), calRGBCS->getGammaB()); |
6550 | writePSFmt(fmt: " /MatrixABC [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g} {8:.4g}]\n" , calRGBCS->getMatrix()[0], calRGBCS->getMatrix()[1], calRGBCS->getMatrix()[2], calRGBCS->getMatrix()[3], calRGBCS->getMatrix()[4], |
6551 | calRGBCS->getMatrix()[5], calRGBCS->getMatrix()[6], calRGBCS->getMatrix()[7], calRGBCS->getMatrix()[8]); |
6552 | writePSFmt(fmt: " /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n" , calRGBCS->getWhiteX(), calRGBCS->getWhiteY(), calRGBCS->getWhiteZ()); |
6553 | writePSFmt(fmt: " /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n" , calRGBCS->getBlackX(), calRGBCS->getBlackY(), calRGBCS->getBlackZ()); |
6554 | writePS(s: ">>]" ); |
6555 | if (genXform) { |
6556 | writePS(s: " {}" ); |
6557 | } |
6558 | if (updateColors) { |
6559 | processColors |= psProcessCMYK; |
6560 | } |
6561 | break; |
6562 | |
6563 | case csDeviceCMYK: |
6564 | writePS(s: "/DeviceCMYK" ); |
6565 | if (genXform) { |
6566 | writePS(s: " {}" ); |
6567 | } |
6568 | if (updateColors) { |
6569 | processColors |= psProcessCMYK; |
6570 | } |
6571 | break; |
6572 | |
6573 | case csLab: |
6574 | labCS = (GfxLabColorSpace *)colorSpace; |
6575 | writePS(s: "[/CIEBasedABC <<\n" ); |
6576 | if (map01) { |
6577 | writePS(s: " /RangeABC [0 1 0 1 0 1]\n" ); |
6578 | writePSFmt(fmt: " /DecodeABC [{{100 mul 16 add 116 div}} bind {{{0:.4g} mul {1:.4g} add}} bind {{{2:.4g} mul {3:.4g} add}} bind]\n" , (labCS->getAMax() - labCS->getAMin()) / 500.0, labCS->getAMin() / 500.0, |
6579 | (labCS->getBMax() - labCS->getBMin()) / 200.0, labCS->getBMin() / 200.0); |
6580 | } else { |
6581 | writePSFmt(fmt: " /RangeABC [0 100 {0:.4g} {1:.4g} {2:.4g} {3:.4g}]\n" , labCS->getAMin(), labCS->getAMax(), labCS->getBMin(), labCS->getBMax()); |
6582 | writePS(s: " /DecodeABC [{16 add 116 div} bind {500 div} bind {200 div} bind]\n" ); |
6583 | } |
6584 | writePS(s: " /MatrixABC [1 1 1 1 0 0 0 0 -1]\n" ); |
6585 | writePS(s: " /DecodeLMN\n" ); |
6586 | writePS(s: " [{dup 6 29 div ge {dup dup mul mul}\n" ); |
6587 | writePSFmt(fmt: " {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n" , labCS->getWhiteX()); |
6588 | writePS(s: " {dup 6 29 div ge {dup dup mul mul}\n" ); |
6589 | writePSFmt(fmt: " {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n" , labCS->getWhiteY()); |
6590 | writePS(s: " {dup 6 29 div ge {dup dup mul mul}\n" ); |
6591 | writePSFmt(fmt: " {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind]\n" , labCS->getWhiteZ()); |
6592 | writePSFmt(fmt: " /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n" , labCS->getWhiteX(), labCS->getWhiteY(), labCS->getWhiteZ()); |
6593 | writePSFmt(fmt: " /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n" , labCS->getBlackX(), labCS->getBlackY(), labCS->getBlackZ()); |
6594 | writePS(s: ">>]" ); |
6595 | if (genXform) { |
6596 | writePS(s: " {}" ); |
6597 | } |
6598 | if (updateColors) { |
6599 | processColors |= psProcessCMYK; |
6600 | } |
6601 | break; |
6602 | |
6603 | case csICCBased: |
6604 | #ifdef USE_CMS |
6605 | { |
6606 | GfxICCBasedColorSpace *iccBasedCS; |
6607 | iccBasedCS = (GfxICCBasedColorSpace *)colorSpace; |
6608 | Ref ref = iccBasedCS->getRef(); |
6609 | const bool validref = ref != Ref::INVALID(); |
6610 | int intent = state->getCmsRenderingIntent(); |
6611 | std::unique_ptr<GooString> name; |
6612 | if (validref) { |
6613 | name = GooString::format("ICCBased-{0:d}-{1:d}-{2:d}" , ref.num, ref.gen, intent); |
6614 | } else { |
6615 | const unsigned long long hash = std::hash<GfxLCMSProfilePtr> {}(iccBasedCS->getProfile()); |
6616 | name = GooString::format("ICCBased-hashed-{0:ullX}-{1:d}" , hash, intent); |
6617 | } |
6618 | const auto &it = iccEmitted.find(name->toStr()); |
6619 | if (it != iccEmitted.end()) { |
6620 | writePSFmt("{0:t}" , name.get()); |
6621 | if (genXform) { |
6622 | writePS(" {}" ); |
6623 | } |
6624 | } else { |
6625 | char *csa = iccBasedCS->getPostScriptCSA(); |
6626 | if (csa) { |
6627 | writePSFmt("userdict /{0:t} {1:s} put\n" , name.get(), csa); |
6628 | iccEmitted.emplace(name->toStr()); |
6629 | writePSFmt("{0:t}" , name.get()); |
6630 | if (genXform) { |
6631 | writePS(" {}" ); |
6632 | } |
6633 | } else { |
6634 | dumpColorSpaceL2(state, ((GfxICCBasedColorSpace *)colorSpace)->getAlt(), genXform, updateColors, false); |
6635 | } |
6636 | } |
6637 | } |
6638 | #else |
6639 | // there is no transform function to the alternate color space, so |
6640 | // we can use it directly |
6641 | dumpColorSpaceL2(state, colorSpace: ((GfxICCBasedColorSpace *)colorSpace)->getAlt(), genXform, updateColors, map01: false); |
6642 | #endif |
6643 | break; |
6644 | |
6645 | case csIndexed: |
6646 | indexedCS = (GfxIndexedColorSpace *)colorSpace; |
6647 | baseCS = indexedCS->getBase(); |
6648 | writePS(s: "[/Indexed " ); |
6649 | dumpColorSpaceL2(state, colorSpace: baseCS, genXform: false, updateColors: false, map01: true); |
6650 | n = indexedCS->getIndexHigh(); |
6651 | numComps = baseCS->getNComps(); |
6652 | lookup = indexedCS->getLookup(); |
6653 | writePSFmt(fmt: " {0:d} <\n" , n); |
6654 | if (baseCS->getMode() == csDeviceN && level != psLevel3 && level != psLevel3Sep) { |
6655 | const Function *func = ((GfxDeviceNColorSpace *)baseCS)->getTintTransformFunc(); |
6656 | baseCS->getDefaultRanges(decodeLow: low, decodeRange: range, maxImgPixel: indexedCS->getIndexHigh()); |
6657 | if (((GfxDeviceNColorSpace *)baseCS)->getAlt()->getMode() == csLab) { |
6658 | labCS = (GfxLabColorSpace *)((GfxDeviceNColorSpace *)baseCS)->getAlt(); |
6659 | } else { |
6660 | labCS = nullptr; |
6661 | } |
6662 | numAltComps = ((GfxDeviceNColorSpace *)baseCS)->getAlt()->getNComps(); |
6663 | p = lookup; |
6664 | for (i = 0; i <= n; i += 8) { |
6665 | writePS(s: " " ); |
6666 | for (j = i; j < i + 8 && j <= n; ++j) { |
6667 | for (k = 0; k < numComps; ++k) { |
6668 | x[k] = low[k] + (*p++ / 255.0) * range[k]; |
6669 | } |
6670 | func->transform(in: x, out: y); |
6671 | if (labCS) { |
6672 | y[0] /= 100.0; |
6673 | y[1] = (y[1] - labCS->getAMin()) / (labCS->getAMax() - labCS->getAMin()); |
6674 | y[2] = (y[2] - labCS->getBMin()) / (labCS->getBMax() - labCS->getBMin()); |
6675 | } |
6676 | for (k = 0; k < numAltComps; ++k) { |
6677 | byte = (int)(y[k] * 255 + 0.5); |
6678 | if (byte < 0) { |
6679 | byte = 0; |
6680 | } else if (byte > 255) { |
6681 | byte = 255; |
6682 | } |
6683 | writePSFmt(fmt: "{0:02x}" , byte); |
6684 | } |
6685 | if (updateColors) { |
6686 | color.c[0] = dblToCol(x: j); |
6687 | indexedCS->getCMYK(color: &color, cmyk: &cmyk); |
6688 | addProcessColor(c: colToDbl(x: cmyk.c), m: colToDbl(x: cmyk.m), y: colToDbl(x: cmyk.y), k: colToDbl(x: cmyk.k)); |
6689 | } |
6690 | } |
6691 | writePS(s: "\n" ); |
6692 | } |
6693 | } else { |
6694 | for (i = 0; i <= n; i += 8) { |
6695 | writePS(s: " " ); |
6696 | for (j = i; j < i + 8 && j <= n; ++j) { |
6697 | for (k = 0; k < numComps; ++k) { |
6698 | writePSFmt(fmt: "{0:02x}" , lookup[j * numComps + k]); |
6699 | } |
6700 | if (updateColors) { |
6701 | color.c[0] = dblToCol(x: j); |
6702 | indexedCS->getCMYK(color: &color, cmyk: &cmyk); |
6703 | addProcessColor(c: colToDbl(x: cmyk.c), m: colToDbl(x: cmyk.m), y: colToDbl(x: cmyk.y), k: colToDbl(x: cmyk.k)); |
6704 | } |
6705 | } |
6706 | writePS(s: "\n" ); |
6707 | } |
6708 | } |
6709 | writePS(s: ">]" ); |
6710 | if (genXform) { |
6711 | writePS(s: " {}" ); |
6712 | } |
6713 | break; |
6714 | |
6715 | case csSeparation: |
6716 | separationCS = (GfxSeparationColorSpace *)colorSpace; |
6717 | writePS(s: "[/Separation " ); |
6718 | writePSString(s: separationCS->getName()->toStr()); |
6719 | writePS(s: " " ); |
6720 | dumpColorSpaceL2(state, colorSpace: separationCS->getAlt(), genXform: false, updateColors: false, map01: false); |
6721 | writePS(s: "\n" ); |
6722 | cvtFunction(func: separationCS->getFunc()); |
6723 | writePS(s: "]" ); |
6724 | if (genXform) { |
6725 | writePS(s: " {}" ); |
6726 | } |
6727 | if (updateColors) { |
6728 | addCustomColor(sepCS: separationCS); |
6729 | } |
6730 | break; |
6731 | |
6732 | case csDeviceN: |
6733 | deviceNCS = (GfxDeviceNColorSpace *)colorSpace; |
6734 | if (level == psLevel3 || level == psLevel3Sep) { |
6735 | writePS(s: "[/DeviceN\n" ); |
6736 | writePS(s: " [ " ); |
6737 | for (i = 0; i < deviceNCS->getNComps(); i++) { |
6738 | writePSString(s: deviceNCS->getColorantName(i)); |
6739 | writePS(s: " " ); |
6740 | } |
6741 | writePS(s: "]\n" ); |
6742 | dumpColorSpaceL2(state, colorSpace: deviceNCS->getAlt(), genXform: false, updateColors, map01: false); |
6743 | writePS(s: "\n" ); |
6744 | cvtFunction(func: deviceNCS->getTintTransformFunc(), invertPSFunction: map01 && deviceNCS->getAlt()->getMode() == csLab); |
6745 | writePS(s: "]\n" ); |
6746 | if (genXform) { |
6747 | writePS(s: " {}" ); |
6748 | } |
6749 | } else { |
6750 | // DeviceN color spaces are a Level 3 PostScript feature. |
6751 | dumpColorSpaceL2(state, colorSpace: deviceNCS->getAlt(), genXform: false, updateColors, map01); |
6752 | if (genXform) { |
6753 | writePS(s: " " ); |
6754 | cvtFunction(func: deviceNCS->getTintTransformFunc()); |
6755 | } |
6756 | } |
6757 | break; |
6758 | |
6759 | case csPattern: |
6760 | //~ unimplemented |
6761 | break; |
6762 | } |
6763 | } |
6764 | |
6765 | #ifdef OPI_SUPPORT |
6766 | void PSOutputDev::opiBegin(GfxState *state, Dict *opiDict) |
6767 | { |
6768 | if (generateOPI) { |
6769 | Object dict = opiDict->lookup(key: "2.0" ); |
6770 | if (dict.isDict()) { |
6771 | opiBegin20(state, dict: dict.getDict()); |
6772 | } else { |
6773 | dict = opiDict->lookup(key: "1.3" ); |
6774 | if (dict.isDict()) { |
6775 | opiBegin13(state, dict: dict.getDict()); |
6776 | } |
6777 | } |
6778 | } |
6779 | } |
6780 | |
6781 | void PSOutputDev::opiBegin20(GfxState *state, Dict *dict) |
6782 | { |
6783 | double width, height, left, right, top, bottom; |
6784 | int w, h; |
6785 | |
6786 | writePS(s: "%%BeginOPI: 2.0\n" ); |
6787 | writePS(s: "%%Distilled\n" ); |
6788 | |
6789 | Object obj1 = dict->lookup(key: "F" ); |
6790 | Object obj2 = getFileSpecName(fileSpec: &obj1); |
6791 | if (obj2.isString()) { |
6792 | writePSFmt(fmt: "%%ImageFileName: {0:t}\n" , obj2.getString()); |
6793 | } |
6794 | |
6795 | obj1 = dict->lookup(key: "MainImage" ); |
6796 | if (obj1.isString()) { |
6797 | writePSFmt(fmt: "%%MainImage: {0:t}\n" , obj1.getString()); |
6798 | } |
6799 | |
6800 | //~ ignoring 'Tags' entry |
6801 | //~ need to use writePSString() and deal with >255-char lines |
6802 | |
6803 | obj1 = dict->lookup(key: "Size" ); |
6804 | if (obj1.isArray() && obj1.arrayGetLength() == 2) { |
6805 | obj2 = obj1.arrayGet(i: 0); |
6806 | width = obj2.getNum(); |
6807 | obj2 = obj1.arrayGet(i: 1); |
6808 | height = obj2.getNum(); |
6809 | writePSFmt(fmt: "%%ImageDimensions: {0:.6g} {1:.6g}\n" , width, height); |
6810 | } |
6811 | |
6812 | obj1 = dict->lookup(key: "CropRect" ); |
6813 | if (obj1.isArray() && obj1.arrayGetLength() == 4) { |
6814 | obj2 = obj1.arrayGet(i: 0); |
6815 | left = obj2.getNum(); |
6816 | obj2 = obj1.arrayGet(i: 1); |
6817 | top = obj2.getNum(); |
6818 | obj2 = obj1.arrayGet(i: 2); |
6819 | right = obj2.getNum(); |
6820 | obj2 = obj1.arrayGet(i: 3); |
6821 | bottom = obj2.getNum(); |
6822 | writePSFmt(fmt: "%%ImageCropRect: {0:.6g} {1:.6g} {2:.6g} {3:.6g}\n" , left, top, right, bottom); |
6823 | } |
6824 | |
6825 | obj1 = dict->lookup(key: "Overprint" ); |
6826 | if (obj1.isBool()) { |
6827 | writePSFmt(fmt: "%%ImageOverprint: {0:s}\n" , obj1.getBool() ? "true" : "false" ); |
6828 | } |
6829 | |
6830 | obj1 = dict->lookup(key: "Inks" ); |
6831 | if (obj1.isName()) { |
6832 | writePSFmt(fmt: "%%ImageInks: {0:s}\n" , obj1.getName()); |
6833 | } else if (obj1.isArray() && obj1.arrayGetLength() >= 1) { |
6834 | obj2 = obj1.arrayGet(i: 0); |
6835 | if (obj2.isName()) { |
6836 | writePSFmt(fmt: "%%ImageInks: {0:s} {1:d}" , obj2.getName(), (obj1.arrayGetLength() - 1) / 2); |
6837 | for (int i = 1; i + 1 < obj1.arrayGetLength(); i += 2) { |
6838 | Object obj3 = obj1.arrayGet(i); |
6839 | Object obj4 = obj1.arrayGet(i: i + 1); |
6840 | if (obj3.isString() && obj4.isNum()) { |
6841 | writePS(s: " " ); |
6842 | writePSString(s: obj3.getString()->toStr()); |
6843 | writePSFmt(fmt: " {0:.6g}" , obj4.getNum()); |
6844 | } |
6845 | } |
6846 | writePS(s: "\n" ); |
6847 | } |
6848 | } |
6849 | |
6850 | writePS(s: "gsave\n" ); |
6851 | |
6852 | writePS(s: "%%BeginIncludedImage\n" ); |
6853 | |
6854 | obj1 = dict->lookup(key: "IncludedImageDimensions" ); |
6855 | if (obj1.isArray() && obj1.arrayGetLength() == 2) { |
6856 | obj2 = obj1.arrayGet(i: 0); |
6857 | w = obj2.getInt(); |
6858 | obj2 = obj1.arrayGet(i: 1); |
6859 | h = obj2.getInt(); |
6860 | writePSFmt(fmt: "%%IncludedImageDimensions: {0:d} {1:d}\n" , w, h); |
6861 | } |
6862 | |
6863 | obj1 = dict->lookup(key: "IncludedImageQuality" ); |
6864 | if (obj1.isNum()) { |
6865 | writePSFmt(fmt: "%%IncludedImageQuality: {0:.6g}\n" , obj1.getNum()); |
6866 | } |
6867 | |
6868 | ++opi20Nest; |
6869 | } |
6870 | |
6871 | void PSOutputDev::opiBegin13(GfxState *state, Dict *dict) |
6872 | { |
6873 | int left, right, top, bottom, samples, bits, width, height; |
6874 | double c, m, y, k; |
6875 | double llx, lly, ulx, uly, urx, ury, lrx, lry; |
6876 | double tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry; |
6877 | double horiz, vert; |
6878 | int i, j; |
6879 | |
6880 | writePS(s: "save\n" ); |
6881 | writePS(s: "/opiMatrix2 matrix currentmatrix def\n" ); |
6882 | writePS(s: "opiMatrix setmatrix\n" ); |
6883 | |
6884 | Object obj1 = dict->lookup(key: "F" ); |
6885 | Object obj2 = getFileSpecName(fileSpec: &obj1); |
6886 | if (obj2.isString()) { |
6887 | writePSFmt(fmt: "%ALDImageFileName: {0:t}\n" , obj2.getString()); |
6888 | } |
6889 | |
6890 | obj1 = dict->lookup(key: "CropRect" ); |
6891 | if (obj1.isArray() && obj1.arrayGetLength() == 4) { |
6892 | obj2 = obj1.arrayGet(i: 0); |
6893 | left = obj2.getInt(); |
6894 | obj2 = obj1.arrayGet(i: 1); |
6895 | top = obj2.getInt(); |
6896 | obj2 = obj1.arrayGet(i: 2); |
6897 | right = obj2.getInt(); |
6898 | obj2 = obj1.arrayGet(i: 3); |
6899 | bottom = obj2.getInt(); |
6900 | writePSFmt(fmt: "%ALDImageCropRect: {0:d} {1:d} {2:d} {3:d}\n" , left, top, right, bottom); |
6901 | } |
6902 | |
6903 | obj1 = dict->lookup(key: "Color" ); |
6904 | if (obj1.isArray() && obj1.arrayGetLength() == 5) { |
6905 | obj2 = obj1.arrayGet(i: 0); |
6906 | c = obj2.getNum(); |
6907 | obj2 = obj1.arrayGet(i: 1); |
6908 | m = obj2.getNum(); |
6909 | obj2 = obj1.arrayGet(i: 2); |
6910 | y = obj2.getNum(); |
6911 | obj2 = obj1.arrayGet(i: 3); |
6912 | k = obj2.getNum(); |
6913 | obj2 = obj1.arrayGet(i: 4); |
6914 | if (obj2.isString()) { |
6915 | writePSFmt(fmt: "%ALDImageColor: {0:.4g} {1:.4g} {2:.4g} {3:.4g} " , c, m, y, k); |
6916 | writePSString(s: obj2.getString()->toStr()); |
6917 | writePS(s: "\n" ); |
6918 | } |
6919 | } |
6920 | |
6921 | obj1 = dict->lookup(key: "ColorType" ); |
6922 | if (obj1.isName()) { |
6923 | writePSFmt(fmt: "%ALDImageColorType: {0:s}\n" , obj1.getName()); |
6924 | } |
6925 | |
6926 | //~ ignores 'Comments' entry |
6927 | //~ need to handle multiple lines |
6928 | |
6929 | obj1 = dict->lookup(key: "CropFixed" ); |
6930 | if (obj1.isArray()) { |
6931 | obj2 = obj1.arrayGet(i: 0); |
6932 | ulx = obj2.getNum(); |
6933 | obj2 = obj1.arrayGet(i: 1); |
6934 | uly = obj2.getNum(); |
6935 | obj2 = obj1.arrayGet(i: 2); |
6936 | lrx = obj2.getNum(); |
6937 | obj2 = obj1.arrayGet(i: 3); |
6938 | lry = obj2.getNum(); |
6939 | writePSFmt(fmt: "%ALDImageCropFixed: {0:.6g} {1:.6g} {2:.6g} {3:.6g}\n" , ulx, uly, lrx, lry); |
6940 | } |
6941 | |
6942 | obj1 = dict->lookup(key: "GrayMap" ); |
6943 | if (obj1.isArray()) { |
6944 | writePS(s: "%ALDImageGrayMap:" ); |
6945 | for (i = 0; i < obj1.arrayGetLength(); i += 16) { |
6946 | if (i > 0) { |
6947 | writePS(s: "\n%%+" ); |
6948 | } |
6949 | for (j = 0; j < 16 && i + j < obj1.arrayGetLength(); ++j) { |
6950 | obj2 = obj1.arrayGet(i: i + j); |
6951 | writePSFmt(fmt: " {0:d}" , obj2.getInt()); |
6952 | } |
6953 | } |
6954 | writePS(s: "\n" ); |
6955 | } |
6956 | |
6957 | obj1 = dict->lookup(key: "ID" ); |
6958 | if (obj1.isString()) { |
6959 | writePSFmt(fmt: "%ALDImageID: {0:t}\n" , obj1.getString()); |
6960 | } |
6961 | |
6962 | obj1 = dict->lookup(key: "ImageType" ); |
6963 | if (obj1.isArray() && obj1.arrayGetLength() == 2) { |
6964 | obj2 = obj1.arrayGet(i: 0); |
6965 | samples = obj2.getInt(); |
6966 | obj2 = obj1.arrayGet(i: 1); |
6967 | bits = obj2.getInt(); |
6968 | writePSFmt(fmt: "%ALDImageType: {0:d} {1:d}\n" , samples, bits); |
6969 | } |
6970 | |
6971 | dict->lookup(key: "Overprint" ); |
6972 | if (obj1.isBool()) { |
6973 | writePSFmt(fmt: "%ALDImageOverprint: {0:s}\n" , obj1.getBool() ? "true" : "false" ); |
6974 | } |
6975 | |
6976 | obj1 = dict->lookup(key: "Position" ); |
6977 | if (obj1.isArray() && obj1.arrayGetLength() == 8) { |
6978 | obj2 = obj1.arrayGet(i: 0); |
6979 | llx = obj2.getNum(); |
6980 | obj2 = obj1.arrayGet(i: 1); |
6981 | lly = obj2.getNum(); |
6982 | obj2 = obj1.arrayGet(i: 2); |
6983 | ulx = obj2.getNum(); |
6984 | obj2 = obj1.arrayGet(i: 3); |
6985 | uly = obj2.getNum(); |
6986 | obj2 = obj1.arrayGet(i: 4); |
6987 | urx = obj2.getNum(); |
6988 | obj2 = obj1.arrayGet(i: 5); |
6989 | ury = obj2.getNum(); |
6990 | obj2 = obj1.arrayGet(i: 6); |
6991 | lrx = obj2.getNum(); |
6992 | obj2 = obj1.arrayGet(i: 7); |
6993 | lry = obj2.getNum(); |
6994 | opiTransform(state, x0: llx, y0: lly, x1: &tllx, y1: &tlly); |
6995 | opiTransform(state, x0: ulx, y0: uly, x1: &tulx, y1: &tuly); |
6996 | opiTransform(state, x0: urx, y0: ury, x1: &turx, y1: &tury); |
6997 | opiTransform(state, x0: lrx, y0: lry, x1: &tlrx, y1: &tlry); |
6998 | writePSFmt(fmt: "%ALDImagePosition: {0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g} {6:.6g} {7:.6g}\n" , tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry); |
6999 | } |
7000 | |
7001 | obj1 = dict->lookup(key: "Resolution" ); |
7002 | if (obj1.isArray() && obj1.arrayGetLength() == 2) { |
7003 | obj2 = obj1.arrayGet(i: 0); |
7004 | horiz = obj2.getNum(); |
7005 | obj2 = obj1.arrayGet(i: 1); |
7006 | vert = obj2.getNum(); |
7007 | writePSFmt(fmt: "%ALDImageResoution: {0:.6g} {1:.6g}\n" , horiz, vert); |
7008 | } |
7009 | |
7010 | obj1 = dict->lookup(key: "Size" ); |
7011 | if (obj1.isArray() && obj1.arrayGetLength() == 2) { |
7012 | obj2 = obj1.arrayGet(i: 0); |
7013 | width = obj2.getInt(); |
7014 | obj2 = obj1.arrayGet(i: 1); |
7015 | height = obj2.getInt(); |
7016 | writePSFmt(fmt: "%ALDImageDimensions: {0:d} {1:d}\n" , width, height); |
7017 | } |
7018 | |
7019 | //~ ignoring 'Tags' entry |
7020 | //~ need to use writePSString() and deal with >255-char lines |
7021 | |
7022 | obj1 = dict->lookup(key: "Tint" ); |
7023 | if (obj1.isNum()) { |
7024 | writePSFmt(fmt: "%ALDImageTint: {0:.6g}\n" , obj1.getNum()); |
7025 | } |
7026 | |
7027 | obj1 = dict->lookup(key: "Transparency" ); |
7028 | if (obj1.isBool()) { |
7029 | writePSFmt(fmt: "%ALDImageTransparency: {0:s}\n" , obj1.getBool() ? "true" : "false" ); |
7030 | } |
7031 | |
7032 | writePS(s: "%%BeginObject: image\n" ); |
7033 | writePS(s: "opiMatrix2 setmatrix\n" ); |
7034 | ++opi13Nest; |
7035 | } |
7036 | |
7037 | // Convert PDF user space coordinates to PostScript default user space |
7038 | // coordinates. This has to account for both the PDF CTM and the |
7039 | // PSOutputDev page-fitting transform. |
7040 | void PSOutputDev::opiTransform(GfxState *state, double x0, double y0, double *x1, double *y1) |
7041 | { |
7042 | double t; |
7043 | |
7044 | state->transform(x1: x0, y1: y0, x2: x1, y2: y1); |
7045 | *x1 += tx; |
7046 | *y1 += ty; |
7047 | if (rotate == 90) { |
7048 | t = *x1; |
7049 | *x1 = -*y1; |
7050 | *y1 = t; |
7051 | } else if (rotate == 180) { |
7052 | *x1 = -*x1; |
7053 | *y1 = -*y1; |
7054 | } else if (rotate == 270) { |
7055 | t = *x1; |
7056 | *x1 = *y1; |
7057 | *y1 = -t; |
7058 | } |
7059 | *x1 *= xScale; |
7060 | *y1 *= yScale; |
7061 | } |
7062 | |
7063 | void PSOutputDev::opiEnd(GfxState *state, Dict *opiDict) |
7064 | { |
7065 | if (generateOPI) { |
7066 | Object dict = opiDict->lookup(key: "2.0" ); |
7067 | if (dict.isDict()) { |
7068 | writePS(s: "%%EndIncludedImage\n" ); |
7069 | writePS(s: "%%EndOPI\n" ); |
7070 | writePS(s: "grestore\n" ); |
7071 | --opi20Nest; |
7072 | } else { |
7073 | dict = opiDict->lookup(key: "1.3" ); |
7074 | if (dict.isDict()) { |
7075 | writePS(s: "%%EndObject\n" ); |
7076 | writePS(s: "restore\n" ); |
7077 | --opi13Nest; |
7078 | } |
7079 | } |
7080 | } |
7081 | } |
7082 | #endif // OPI_SUPPORT |
7083 | |
7084 | void PSOutputDev::type3D0(GfxState *state, double wx, double wy) |
7085 | { |
7086 | writePSFmt(fmt: "{0:.6g} {1:.6g} setcharwidth\n" , wx, wy); |
7087 | writePS(s: "q\n" ); |
7088 | t3NeedsRestore = true; |
7089 | } |
7090 | |
7091 | void PSOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) |
7092 | { |
7093 | t3WX = wx; |
7094 | t3WY = wy; |
7095 | t3LLX = llx; |
7096 | t3LLY = lly; |
7097 | t3URX = urx; |
7098 | t3URY = ury; |
7099 | delete t3String; |
7100 | t3String = new GooString(); |
7101 | writePS(s: "q\n" ); |
7102 | t3FillColorOnly = true; |
7103 | t3Cacheable = true; |
7104 | t3NeedsRestore = true; |
7105 | } |
7106 | |
7107 | void PSOutputDev::drawForm(Ref ref) |
7108 | { |
7109 | writePSFmt(fmt: "f_{0:d}_{1:d}\n" , ref.num, ref.gen); |
7110 | } |
7111 | |
7112 | void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) |
7113 | { |
7114 | Stream *str; |
7115 | int c; |
7116 | |
7117 | if ((level == psLevel1 || level == psLevel1Sep) && level1Stream) { |
7118 | str = level1Stream; |
7119 | } else { |
7120 | str = psStream; |
7121 | } |
7122 | str->reset(); |
7123 | while ((c = str->getChar()) != EOF) { |
7124 | writePSChar(c); |
7125 | } |
7126 | str->close(); |
7127 | } |
7128 | |
7129 | //~ can nextFunc be reset to 0 -- maybe at the start of each page? |
7130 | //~ or maybe at the start of each color space / pattern? |
7131 | void PSOutputDev::cvtFunction(const Function *func, bool invertPSFunction) |
7132 | { |
7133 | const SampledFunction *func0; |
7134 | const ExponentialFunction *func2; |
7135 | const StitchingFunction *func3; |
7136 | const PostScriptFunction *func4; |
7137 | int thisFunc, m, n, nSamples, i, j, k; |
7138 | |
7139 | switch (func->getType()) { |
7140 | |
7141 | case Function::Type::Identity: |
7142 | writePS(s: "{}\n" ); |
7143 | break; |
7144 | |
7145 | case Function::Type::Sampled: |
7146 | func0 = (const SampledFunction *)func; |
7147 | thisFunc = nextFunc++; |
7148 | m = func0->getInputSize(); |
7149 | n = func0->getOutputSize(); |
7150 | nSamples = n; |
7151 | for (i = 0; i < m; ++i) { |
7152 | nSamples *= func0->getSampleSize(i); |
7153 | } |
7154 | writePSFmt(fmt: "/xpdfSamples{0:d} [\n" , thisFunc); |
7155 | for (i = 0; i < nSamples; ++i) { |
7156 | writePSFmt(fmt: "{0:.6g}\n" , func0->getSamples()[i]); |
7157 | } |
7158 | writePS(s: "] def\n" ); |
7159 | writePSFmt(fmt: "{{ {0:d} array {1:d} array {2:d} 2 roll\n" , 2 * m, m, m + 2); |
7160 | // [e01] [efrac] x0 x1 ... xm-1 |
7161 | for (i = m - 1; i >= 0; --i) { |
7162 | // [e01] [efrac] x0 x1 ... xi |
7163 | writePSFmt(fmt: "{0:.6g} sub {1:.6g} mul {2:.6g} add\n" , func0->getDomainMin(i), (func0->getEncodeMax(i) - func0->getEncodeMin(i)) / (func0->getDomainMax(i) - func0->getDomainMin(i)), func0->getEncodeMin(i)); |
7164 | // [e01] [efrac] x0 x1 ... xi-1 xi' |
7165 | writePSFmt(fmt: "dup 0 lt {{ pop 0 }} {{ dup {0:d} gt {{ pop {1:d} }} if }} ifelse\n" , func0->getSampleSize(i) - 1, func0->getSampleSize(i) - 1); |
7166 | // [e01] [efrac] x0 x1 ... xi-1 xi' |
7167 | writePS(s: "dup floor cvi exch dup ceiling cvi exch 2 index sub\n" ); |
7168 | // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi') xi'-floor(xi') |
7169 | writePSFmt(fmt: "{0:d} index {1:d} 3 2 roll put\n" , i + 3, i); |
7170 | // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi') |
7171 | writePSFmt(fmt: "{0:d} index {1:d} 3 2 roll put\n" , i + 3, 2 * i + 1); |
7172 | // [e01] [efrac] x0 x1 ... xi-1 floor(xi') |
7173 | writePSFmt(fmt: "{0:d} index {1:d} 3 2 roll put\n" , i + 2, 2 * i); |
7174 | // [e01] [efrac] x0 x1 ... xi-1 |
7175 | } |
7176 | // [e01] [efrac] |
7177 | for (i = 0; i < n; ++i) { |
7178 | // [e01] [efrac] y(0) ... y(i-1) |
7179 | for (j = 0; j < (1 << m); ++j) { |
7180 | // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(j-1) |
7181 | writePSFmt(fmt: "xpdfSamples{0:d}\n" , thisFunc); |
7182 | k = m - 1; |
7183 | writePSFmt(fmt: "{0:d} index {1:d} get\n" , i + j + 2, 2 * k + ((j >> k) & 1)); |
7184 | for (k = m - 2; k >= 0; --k) { |
7185 | writePSFmt(fmt: "{0:d} mul {1:d} index {2:d} get add\n" , func0->getSampleSize(i: k), i + j + 3, 2 * k + ((j >> k) & 1)); |
7186 | } |
7187 | if (n > 1) { |
7188 | writePSFmt(fmt: "{0:d} mul {1:d} add " , n, i); |
7189 | } |
7190 | writePS(s: "get\n" ); |
7191 | } |
7192 | // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^m-1) |
7193 | for (j = 0; j < m; ++j) { |
7194 | // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^(m-j)-1) |
7195 | for (k = 0; k < (1 << (m - j)); k += 2) { |
7196 | // [e01] [efrac] y(0) ... y(i-1) <k/2 s' values> <2^(m-j)-k s values> |
7197 | writePSFmt(fmt: "{0:d} index {1:d} get dup\n" , i + k / 2 + (1 << (m - j)) - k, j); |
7198 | writePS(s: "3 2 roll mul exch 1 exch sub 3 2 roll mul add\n" ); |
7199 | writePSFmt(fmt: "{0:d} 1 roll\n" , k / 2 + (1 << (m - j)) - k - 1); |
7200 | } |
7201 | // [e01] [efrac] s'(0) s'(1) ... s(2^(m-j-1)-1) |
7202 | } |
7203 | // [e01] [efrac] y(0) ... y(i-1) s |
7204 | writePSFmt(fmt: "{0:.6g} mul {1:.6g} add\n" , func0->getDecodeMax(i) - func0->getDecodeMin(i), func0->getDecodeMin(i)); |
7205 | writePSFmt(fmt: "dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n" , func0->getRangeMin(i), func0->getRangeMin(i), func0->getRangeMax(i), func0->getRangeMax(i)); |
7206 | // [e01] [efrac] y(0) ... y(i-1) y(i) |
7207 | } |
7208 | // [e01] [efrac] y(0) ... y(n-1) |
7209 | writePSFmt(fmt: "{0:d} {1:d} roll pop pop \n" , n + 2, n); |
7210 | if (invertPSFunction) { |
7211 | for (i = 0; i < n; ++i) { |
7212 | writePSFmt(fmt: "{0:d} -1 roll " , n); |
7213 | writePSFmt(fmt: "{0:.6g} sub {1:.6g} div " , func0->getRangeMin(i), func0->getRangeMax(i) - func0->getRangeMin(i)); |
7214 | } |
7215 | } |
7216 | writePS(s: "}\n" ); |
7217 | break; |
7218 | |
7219 | case Function::Type::Exponential: |
7220 | func2 = (const ExponentialFunction *)func; |
7221 | n = func2->getOutputSize(); |
7222 | writePSFmt(fmt: "{{ dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n" , func2->getDomainMin(i: 0), func2->getDomainMin(i: 0), func2->getDomainMax(i: 0), func2->getDomainMax(i: 0)); |
7223 | // x |
7224 | for (i = 0; i < n; ++i) { |
7225 | // x y(0) .. y(i-1) |
7226 | writePSFmt(fmt: "{0:d} index {1:.6g} exp {2:.6g} mul {3:.6g} add\n" , i, func2->getE(), func2->getC1()[i] - func2->getC0()[i], func2->getC0()[i]); |
7227 | if (func2->getHasRange()) { |
7228 | writePSFmt(fmt: "dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n" , func2->getRangeMin(i), func2->getRangeMin(i), func2->getRangeMax(i), func2->getRangeMax(i)); |
7229 | } |
7230 | } |
7231 | // x y(0) .. y(n-1) |
7232 | writePSFmt(fmt: "{0:d} {1:d} roll pop \n" , n + 1, n); |
7233 | if (invertPSFunction && func2->getHasRange()) { |
7234 | for (i = 0; i < n; ++i) { |
7235 | writePSFmt(fmt: "{0:d} -1 roll " , n); |
7236 | writePSFmt(fmt: "{0:.6g} sub {1:.6g} div " , func2->getRangeMin(i), func2->getRangeMax(i) - func2->getRangeMin(i)); |
7237 | } |
7238 | } |
7239 | writePS(s: "}\n" ); |
7240 | break; |
7241 | |
7242 | case Function::Type::Stitching: |
7243 | func3 = (const StitchingFunction *)func; |
7244 | thisFunc = nextFunc++; |
7245 | for (i = 0; i < func3->getNumFuncs(); ++i) { |
7246 | cvtFunction(func: func3->getFunc(i)); |
7247 | writePSFmt(fmt: "/xpdfFunc{0:d}_{1:d} exch def\n" , thisFunc, i); |
7248 | } |
7249 | writePSFmt(fmt: "{{ dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n" , func3->getDomainMin(i: 0), func3->getDomainMin(i: 0), func3->getDomainMax(i: 0), func3->getDomainMax(i: 0)); |
7250 | for (i = 0; i < func3->getNumFuncs() - 1; ++i) { |
7251 | writePSFmt(fmt: "dup {0:.6g} lt {{ {1:.6g} sub {2:.6g} mul {3:.6g} add xpdfFunc{4:d}_{5:d} }} {{\n" , func3->getBounds()[i + 1], func3->getBounds()[i], func3->getScale()[i], func3->getEncode()[2 * i], thisFunc, i); |
7252 | } |
7253 | writePSFmt(fmt: "{0:.6g} sub {1:.6g} mul {2:.6g} add xpdfFunc{3:d}_{4:d}\n" , func3->getBounds()[i], func3->getScale()[i], func3->getEncode()[2 * i], thisFunc, i); |
7254 | for (i = 0; i < func3->getNumFuncs() - 1; ++i) { |
7255 | writePS(s: "} ifelse\n" ); |
7256 | } |
7257 | if (invertPSFunction && func3->getHasRange()) { |
7258 | n = func3->getOutputSize(); |
7259 | for (i = 0; i < n; ++i) { |
7260 | writePSFmt(fmt: "{0:d} -1 roll " , n); |
7261 | writePSFmt(fmt: "{0:.6g} sub {1:.6g} div " , func3->getRangeMin(i), func3->getRangeMax(i) - func3->getRangeMin(i)); |
7262 | } |
7263 | } |
7264 | writePS(s: "}\n" ); |
7265 | break; |
7266 | |
7267 | case Function::Type::PostScript: |
7268 | func4 = (const PostScriptFunction *)func; |
7269 | if (invertPSFunction) { |
7270 | GooString *codeString = new GooString(func4->getCodeString()); |
7271 | for (i = codeString->getLength() - 1; i > 0; i--) { |
7272 | if (codeString->getChar(i) == '}') { |
7273 | codeString->del(i); |
7274 | break; |
7275 | } |
7276 | } |
7277 | writePS(s: codeString->c_str()); |
7278 | writePS(s: "\n" ); |
7279 | delete codeString; |
7280 | n = func4->getOutputSize(); |
7281 | for (i = 0; i < n; ++i) { |
7282 | writePSFmt(fmt: "{0:d} -1 roll " , n); |
7283 | writePSFmt(fmt: "{0:.6g} sub {1:.6g} div " , func4->getRangeMin(i), func4->getRangeMax(i) - func4->getRangeMin(i)); |
7284 | } |
7285 | writePS(s: "}\n" ); |
7286 | } else { |
7287 | writePS(s: func4->getCodeString()->c_str()); |
7288 | writePS(s: "\n" ); |
7289 | } |
7290 | break; |
7291 | } |
7292 | } |
7293 | |
7294 | void PSOutputDev::writePSChar(char c) |
7295 | { |
7296 | if (t3String) { |
7297 | t3String->append(c); |
7298 | } else { |
7299 | (*outputFunc)(outputStream, &c, 1); |
7300 | } |
7301 | } |
7302 | |
7303 | void PSOutputDev::writePS(const char *s) |
7304 | { |
7305 | if (t3String) { |
7306 | t3String->append(str: s); |
7307 | } else { |
7308 | (*outputFunc)(outputStream, s, strlen(s: s)); |
7309 | } |
7310 | } |
7311 | |
7312 | void PSOutputDev::writePSBuf(const char *s, int len) |
7313 | { |
7314 | if (t3String) { |
7315 | for (int i = 0; i < len; i++) { |
7316 | t3String->append(c: s[i]); |
7317 | } |
7318 | } else { |
7319 | (*outputFunc)(outputStream, s, len); |
7320 | } |
7321 | } |
7322 | |
7323 | void PSOutputDev::writePSFmt(const char *fmt, ...) |
7324 | { |
7325 | va_list args; |
7326 | |
7327 | va_start(args, fmt); |
7328 | if (t3String) { |
7329 | t3String->appendfv(fmt: (char *)fmt, argList: args); |
7330 | } else { |
7331 | const std::unique_ptr<GooString> buf = GooString::formatv(fmt: (char *)fmt, argList: args); |
7332 | (*outputFunc)(outputStream, buf->c_str(), buf->getLength()); |
7333 | } |
7334 | va_end(args); |
7335 | } |
7336 | |
7337 | void PSOutputDev::writePSString(const std::string &s) |
7338 | { |
7339 | unsigned char *p; |
7340 | int n, line; |
7341 | char buf[8]; |
7342 | |
7343 | writePSChar(c: '('); |
7344 | line = 1; |
7345 | for (p = (unsigned char *)s.c_str(), n = s.size(); n; ++p, --n) { |
7346 | if (line >= 64) { |
7347 | writePSChar(c: '\\'); |
7348 | writePSChar(c: '\n'); |
7349 | line = 0; |
7350 | } |
7351 | if (*p == '(' || *p == ')' || *p == '\\') { |
7352 | writePSChar(c: '\\'); |
7353 | writePSChar(c: (char)*p); |
7354 | line += 2; |
7355 | } else if (*p < 0x20 || *p >= 0x80) { |
7356 | sprintf(s: buf, format: "\\%03o" , *p); |
7357 | writePS(s: buf); |
7358 | line += 4; |
7359 | } else { |
7360 | writePSChar(c: (char)*p); |
7361 | ++line; |
7362 | } |
7363 | } |
7364 | writePSChar(c: ')'); |
7365 | } |
7366 | |
7367 | void PSOutputDev::writePSName(const char *s) |
7368 | { |
7369 | const char *p; |
7370 | char c; |
7371 | |
7372 | p = s; |
7373 | while ((c = *p++)) { |
7374 | if (c <= (char)0x20 || c >= (char)0x7f || c == '(' || c == ')' || c == '<' || c == '>' || c == '[' || c == ']' || c == '{' || c == '}' || c == '/' || c == '%' || c == '\\') { |
7375 | writePSFmt(fmt: "#{0:02x}" , c & 0xff); |
7376 | } else { |
7377 | writePSChar(c); |
7378 | } |
7379 | } |
7380 | } |
7381 | |
7382 | std::string PSOutputDev::filterPSName(const std::string &name) |
7383 | { |
7384 | std::string name2; |
7385 | |
7386 | // ghostscript chokes on names that begin with out-of-limits |
7387 | // numbers, e.g., 1e4foo is handled correctly (as a name), but |
7388 | // 1e999foo generates a limitcheck error |
7389 | const char c0 = name[0]; |
7390 | if (c0 >= '0' && c0 <= '9') { |
7391 | name2 += 'f'; |
7392 | } |
7393 | |
7394 | for (const char c : name) { |
7395 | if (c <= (char)0x20 || c >= (char)0x7f || c == '(' || c == ')' || c == '<' || c == '>' || c == '[' || c == ']' || c == '{' || c == '}' || c == '/' || c == '%') { |
7396 | char buf[8]; |
7397 | sprintf(s: buf, format: "#%02x" , c & 0xff); |
7398 | name2.append(s: buf); |
7399 | } else { |
7400 | name2 += c; |
7401 | } |
7402 | } |
7403 | return name2; |
7404 | } |
7405 | |
7406 | // Convert GooString to GooString, with appropriate escaping |
7407 | // of things that can't appear in a label |
7408 | // This is heavily based on the writePSTextLine() method |
7409 | GooString *PSOutputDev::filterPSLabel(GooString *label, bool *needParens) |
7410 | { |
7411 | int i, step; |
7412 | bool isNumeric; |
7413 | |
7414 | // - DSC comments must be printable ASCII; control chars and |
7415 | // backslashes have to be escaped (we do cheap UCS2-to-ASCII |
7416 | // conversion by simply ignoring the high byte) |
7417 | // - parentheses are escaped. this isn't strictly necessary for matched |
7418 | // parentheses, but shouldn't be a problem |
7419 | // - lines are limited to 255 chars (we limit to 200 here to allow |
7420 | // for the keyword, which was emitted by the caller) |
7421 | |
7422 | GooString *label2 = new GooString(); |
7423 | int labelLength = label->getLength(); |
7424 | |
7425 | if (labelLength == 0) { |
7426 | isNumeric = false; |
7427 | } else { |
7428 | // this gets changed later if we find a non-numeric character |
7429 | isNumeric = true; |
7430 | } |
7431 | |
7432 | if ((labelLength >= 2) && ((label->getChar(i: 0) & 0xff) == 0xfe) && ((label->getChar(i: 1) & 0xff) == 0xff)) { |
7433 | // UCS2 mode |
7434 | i = 3; |
7435 | step = 2; |
7436 | if ((label->getChar(i: labelLength - 1) == 0)) { |
7437 | // prune the trailing null (0x000 for UCS2) |
7438 | labelLength -= 2; |
7439 | } |
7440 | } else { |
7441 | i = 0; |
7442 | step = 1; |
7443 | } |
7444 | for (int j = 0; i < labelLength && j < 200; i += step) { |
7445 | char c = label->getChar(i); |
7446 | if ((c < '0') || (c > '9')) { |
7447 | isNumeric = false; |
7448 | } |
7449 | if (c == '\\') { |
7450 | label2->append(str: "\\\\" ); |
7451 | j += 2; |
7452 | } else if (c == ')') { |
7453 | label2->append(str: "\\)" ); |
7454 | } else if (c == '(') { |
7455 | label2->append(str: "\\(" ); |
7456 | } else if (c < 0x20 || c > 0x7e) { |
7457 | std::unique_ptr<GooString> aux = GooString::format(fmt: "\\{0:03o}" , c); |
7458 | label2->append(str: aux.get()); |
7459 | j += 4; |
7460 | } else { |
7461 | label2->append(c); |
7462 | ++j; |
7463 | } |
7464 | } |
7465 | if (needParens) { |
7466 | *needParens = !(isNumeric); |
7467 | } |
7468 | return label2; |
7469 | } |
7470 | |
7471 | // Write a DSC-compliant <textline>. |
7472 | void PSOutputDev::writePSTextLine(const GooString *s) |
7473 | { |
7474 | int i, j, step; |
7475 | int c; |
7476 | |
7477 | // - DSC comments must be printable ASCII; control chars and |
7478 | // backslashes have to be escaped (we do cheap Unicode-to-ASCII |
7479 | // conversion by simply ignoring the high byte) |
7480 | // - lines are limited to 255 chars (we limit to 200 here to allow |
7481 | // for the keyword, which was emitted by the caller) |
7482 | // - lines that start with a left paren are treated as <text> |
7483 | // instead of <textline>, so we escape a leading paren |
7484 | if (s->getLength() >= 2 && (s->getChar(i: 0) & 0xff) == 0xfe && (s->getChar(i: 1) & 0xff) == 0xff) { |
7485 | i = 3; |
7486 | step = 2; |
7487 | } else { |
7488 | i = 0; |
7489 | step = 1; |
7490 | } |
7491 | for (j = 0; i < s->getLength() && j < 200; i += step) { |
7492 | c = s->getChar(i) & 0xff; |
7493 | if (c == '\\') { |
7494 | writePS(s: "\\\\" ); |
7495 | j += 2; |
7496 | } else if (c < 0x20 || c > 0x7e || (j == 0 && c == '(')) { |
7497 | writePSFmt(fmt: "\\{0:03o}" , c); |
7498 | j += 4; |
7499 | } else { |
7500 | writePSChar(c); |
7501 | ++j; |
7502 | } |
7503 | } |
7504 | writePS(s: "\n" ); |
7505 | } |
7506 | |