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
114static 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
833static 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
880struct 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
887static 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.
904struct 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
912struct 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
919struct 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
939class PSOutCustomColor
940{
941public:
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
953PSOutCustomColor::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
963PSOutCustomColor::~PSOutCustomColor()
964{
965 delete name;
966}
967
968//------------------------------------------------------------------------
969
970struct PSOutImgClipRect
971{
972 int x0, x1, y0, y1;
973};
974
975//------------------------------------------------------------------------
976// DeviceNRecoder
977//------------------------------------------------------------------------
978
979class DeviceNRecoder : public FilterStream
980{
981public:
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
992private:
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
1005DeviceNRecoder::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
1017DeviceNRecoder::~DeviceNRecoder()
1018{
1019 if (imgStr) {
1020 delete imgStr;
1021 }
1022 if (str->isEncoder()) {
1023 delete str;
1024 }
1025}
1026
1027void DeviceNRecoder::reset()
1028{
1029 imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
1030 imgStr->reset();
1031}
1032
1033bool 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
1061extern "C" {
1062typedef void (*SignalFunc)(int);
1063}
1064
1065static void outputToFile(void *stream, const char *data, size_t len)
1066{
1067 fwrite(ptr: data, size: 1, n: len, s: (FILE *)stream);
1068}
1069
1070PSOutputDev::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
1127PSOutputDev::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
1168PSOutputDev::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
1193struct StandardMedia
1194{
1195 const char *name;
1196 int width;
1197 int height;
1198};
1199
1200static 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 */
1205static 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
1218void 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
1294void 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 pageRotate = 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
1496PSOutputDev::~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
1558void PSOutputDev::writeHeader(int nPages, const PDFRectangle *mediaBox, const PDFRectangle *cropBox, int pageRotate, 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
1657void 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
1702void 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
1772void PSOutputDev::writePageTrailer()
1773{
1774 if (mode != psModeForm) {
1775 writePS(s: "pdfEndPage\n");
1776 }
1777}
1778
1779void 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
1820void 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
1905void 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
1932void 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
2108void 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
2269err1:
2270 if (strObj.isStream()) {
2271 strObj.streamClose();
2272 }
2273}
2274
2275void 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
2348void 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
2388void 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
2428void 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
2460void 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
2489void 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
2499void 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
2548void 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
2594void 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
2622void 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
2668void 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
2758GooString *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
2786void 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
2838void 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
3004void 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
3029void 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
3103bool 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
3589void 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
3843void 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
3869void PSOutputDev::saveState(GfxState *state)
3870{
3871 writePS(s: "q\n");
3872 ++numSaves;
3873}
3874
3875void PSOutputDev::restoreState(GfxState *state)
3876{
3877 writePS(s: "Q\n");
3878 --numSaves;
3879}
3880
3881void 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
3886void 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
3898void PSOutputDev::updateFlatness(GfxState *state)
3899{
3900 writePSFmt(fmt: "{0:d} i\n", state->getFlatness());
3901}
3902
3903void PSOutputDev::updateLineJoin(GfxState *state)
3904{
3905 writePSFmt(fmt: "{0:d} j\n", state->getLineJoin());
3906}
3907
3908void PSOutputDev::updateLineCap(GfxState *state)
3909{
3910 writePSFmt(fmt: "{0:d} J\n", state->getLineCap());
3911}
3912
3913void PSOutputDev::updateMiterLimit(GfxState *state)
3914{
3915 writePSFmt(fmt: "{0:.6g} M\n", state->getMiterLimit());
3916}
3917
3918void PSOutputDev::updateLineWidth(GfxState *state)
3919{
3920 writePSFmt(fmt: "{0:.6g} w\n", state->getLineWidth());
3921}
3922
3923void 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
3945void 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
3967void 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
4032void 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
4097void 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
4113void 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
4153void PSOutputDev::updateFillOverprint(GfxState *state)
4154{
4155 if (level >= psLevel2) {
4156 writePSFmt(fmt: "{0:s} op\n", state->getFillOverprint() ? "true" : "false");
4157 }
4158}
4159
4160void PSOutputDev::updateStrokeOverprint(GfxState *state)
4161{
4162 if (level >= psLevel2) {
4163 writePSFmt(fmt: "{0:s} OP\n", state->getStrokeOverprint() ? "true" : "false");
4164 }
4165}
4166
4167void PSOutputDev::updateOverprintMode(GfxState *state)
4168{
4169 if (level >= psLevel3) {
4170 writePSFmt(fmt: "{0:s} opm\n", state->getOverprintMode() ? "true" : "false");
4171 }
4172}
4173
4174void 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
4198void 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
4205void 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
4216void PSOutputDev::updateCharSpace(GfxState *state)
4217{
4218 writePSFmt(fmt: "{0:.6g} Tc\n", state->getCharSpace());
4219}
4220
4221void 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
4233void PSOutputDev::updateRise(GfxState *state)
4234{
4235 writePSFmt(fmt: "{0:.6g} Ts\n", state->getRise());
4236}
4237
4238void PSOutputDev::updateWordSpace(GfxState *state)
4239{
4240 writePSFmt(fmt: "{0:.6g} Tw\n", state->getWordSpace());
4241}
4242
4243void 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
4254void PSOutputDev::updateTextPos(GfxState *state)
4255{
4256 writePSFmt(fmt: "{0:.6g} {1:.6g} Td\n", state->getLineX(), state->getLineY());
4257}
4258
4259void 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
4268void PSOutputDev::saveTextPos(GfxState *state)
4269{
4270 writePS(s: "currentpoint\n");
4271}
4272
4273void PSOutputDev::restoreTextPos(GfxState *state)
4274{
4275 writePS(s: "m\n");
4276}
4277
4278void 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
4290void PSOutputDev::fill(GfxState *state)
4291{
4292 doPath(path: state->getPath());
4293 writePS(s: "f\n");
4294}
4295
4296void PSOutputDev::eoFill(GfxState *state)
4297{
4298 doPath(path: state->getPath());
4299 writePS(s: "f*\n");
4300}
4301
4302bool 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
4377bool 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
4424bool 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
4479bool 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
4518bool 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
4613bool 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
4858bool 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
4908void PSOutputDev::clip(GfxState *state)
4909{
4910 doPath(path: state->getPath());
4911 writePS(s: "W\n");
4912}
4913
4914void PSOutputDev::eoClip(GfxState *state)
4915{
4916 doPath(path: state->getPath());
4917 writePS(s: "W*\n");
4918}
4919
4920void PSOutputDev::clipToStrokePath(GfxState *state)
4921{
4922 doPath(path: state->getPath());
4923 writePS(s: "Ws\n");
4924}
4925
4926void 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
4976void 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
5137void PSOutputDev::beginTextObject(GfxState *state) { }
5138
5139void PSOutputDev::endTextObject(GfxState *state)
5140{
5141 if (haveTextClip) {
5142 writePS(s: "Tclip\n");
5143 haveTextClip = false;
5144 }
5145}
5146
5147void 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
5168void 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
5175void PSOutputDev::unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix)
5176{
5177 if (level != psLevel1 && level != psLevel1Sep) {
5178 writePS(s: "pdfImClipEnd\n");
5179 }
5180}
5181
5182void 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
5207void 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
5232void 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
5376void 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
5539void 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
5660void 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
6108void 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
6490void 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
6766void 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
6781void 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
6871void 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.
7040void 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
7063void 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
7084void 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
7091void 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
7107void PSOutputDev::drawForm(Ref ref)
7108{
7109 writePSFmt(fmt: "f_{0:d}_{1:d}\n", ref.num, ref.gen);
7110}
7111
7112void 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?
7131void 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
7294void PSOutputDev::writePSChar(char c)
7295{
7296 if (t3String) {
7297 t3String->append(c);
7298 } else {
7299 (*outputFunc)(outputStream, &c, 1);
7300 }
7301}
7302
7303void 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
7312void 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
7323void 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
7337void 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
7367void 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
7382std::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
7409GooString *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>.
7472void 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

source code of poppler/poppler/PSOutputDev.cc