| //======================================================================== |
| // |
| // PSOutputDev.cc |
| // |
| // Copyright 1996-2013 Glyph & Cog, LLC |
| // |
| //======================================================================== |
| |
| //======================================================================== |
| // |
| // Modified under the Poppler project - http://poppler.freedesktop.org |
| // |
| // All changes made under the Poppler project to this file are licensed |
| // under GPL version 2 or later |
| // |
| // Copyright (C) 2005 Martin Kretzschmar <martink@gnome.org> |
| // Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com> |
| // Copyright (C) 2006-2009, 2011-2013, 2015-2022, 2024 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2006 Jeff Muizelaar <jeff@infidigm.net> |
| // Copyright (C) 2007, 2008 Brad Hards <bradh@kde.org> |
| // Copyright (C) 2008, 2009 Koji Otani <sho@bbr.jp> |
| // Copyright (C) 2008, 2010 Hib Eris <hib@hiberis.nl> |
| // Copyright (C) 2009-2013 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2009 Till Kamppeter <till.kamppeter@gmail.com> |
| // Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org> |
| // Copyright (C) 2009, 2011, 2012, 2014-2017, 2019, 2020 William Bader <williambader@hotmail.com> |
| // Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net> |
| // Copyright (C) 2009-2011, 2013-2015, 2017, 2020 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2012, 2014 Fabio D'Urso <fabiodurso@hotmail.it> |
| // Copyright (C) 2012 Lu Wang <coolwanglu@gmail.com> |
| // Copyright (C) 2014 Till Kamppeter <till.kamppeter@gmail.com> |
| // Copyright (C) 2015 Marek Kasik <mkasik@redhat.com> |
| // Copyright (C) 2016 Caolán McNamara <caolanm@redhat.com> |
| // 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 |
| // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de> |
| // Copyright (C) 2018 Philipp Knechtges <philipp-dev@knechtges.com> |
| // Copyright (C) 2019, 2021 Christian Persch <chpe@src.gnome.org> |
| // Copyright (C) 2019, 2021-2024 Oliver Sander <oliver.sander@tu-dresden.de> |
| // Copyright (C) 2020, 2021 Philipp Knechtges <philipp-dev@knechtges.com> |
| // Copyright (C) 2021 Hubert Figuiere <hub@figuiere.net> |
| // Copyright (C) 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
| // Copyright (C) 2024 Nelson Benítez León <nbenitezl@gmail.com> |
| // |
| // To see a description of the changes please see the Changelog file that |
| // came with your tarball or type make ChangeLog if you are building from git |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #include <cstdio> |
| #include <cstddef> |
| #include <cstdarg> |
| #include <csignal> |
| #include <cmath> |
| #include <climits> |
| #include <algorithm> |
| #include <array> |
| #include "goo/GooString.h" |
| #include "poppler-config.h" |
| #include "GlobalParams.h" |
| #include "Object.h" |
| #include "Error.h" |
| #include "Function.h" |
| #include "Gfx.h" |
| #include "GfxState.h" |
| #include "GfxFont.h" |
| #include "UnicodeMap.h" |
| #include <fofi/FoFiType1C.h> |
| #include <fofi/FoFiTrueType.h> |
| #include "Catalog.h" |
| #include "Page.h" |
| #include "Stream.h" |
| #include "FlateEncoder.h" |
| #ifdef ENABLE_ZLIB_UNCOMPRESS |
| # include "FlateStream.h" |
| #endif |
| #include "Annot.h" |
| #include "XRef.h" |
| #include "PreScanOutputDev.h" |
| #include "FileSpec.h" |
| #include "CharCodeToUnicode.h" |
| #include "splash/Splash.h" |
| #include "splash/SplashBitmap.h" |
| #include "SplashOutputDev.h" |
| #include "PSOutputDev.h" |
| #include "PDFDoc.h" |
| #include "UTF.h" |
| |
| #ifdef USE_CMS |
| # include <lcms2.h> |
| #endif |
| |
| // the MSVC math.h doesn't define this |
| #ifndef M_PI |
| # define M_PI 3.14159265358979323846 |
| #endif |
| |
| //------------------------------------------------------------------------ |
| |
| // Max size of a slice when rasterizing pages, in pixels. |
| #define rasterizationSliceSize 20000000 |
| |
| //------------------------------------------------------------------------ |
| // PostScript prolog and setup |
| //------------------------------------------------------------------------ |
| |
| // The '~' escapes mark prolog code that is emitted only in certain |
| // levels: |
| // |
| // ~[123][sn] |
| // ^ ^----- s=psLevel*Sep, n=psLevel* |
| // +----- 1=psLevel1*, 2=psLevel2*, 3=psLevel3* |
| |
| static const char *prolog[] = { "/xpdf 75 dict def xpdf begin", |
| "% PDF special state", |
| "/pdfDictSize 15 def", |
| "~1sn", |
| "/pdfStates 64 array def", |
| " 0 1 63 {", |
| " pdfStates exch pdfDictSize dict", |
| " dup /pdfStateIdx 3 index put", |
| " put", |
| " } for", |
| "~123sn", |
| "/pdfSetup {", |
| " /setpagedevice where {", |
| " pop 2 dict begin", |
| " /Policies 1 dict dup begin /PageSize 6 def end def", |
| " { /Duplex true def } if", |
| " currentdict end setpagedevice", |
| " } {", |
| " pop", |
| " } ifelse", |
| "} def", |
| "/pdfSetupPaper {", |
| " % Change paper size, but only if different from previous paper size otherwise", |
| " % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size", |
| " % so we use the same when checking if the size changes.", |
| " /setpagedevice where {", |
| " pop currentpagedevice", |
| " /PageSize known {", |
| " 2 copy", |
| " currentpagedevice /PageSize get aload pop", |
| " exch 4 1 roll", |
| " sub abs 5 gt", |
| " 3 1 roll", |
| " sub abs 5 gt", |
| " or", |
| " } {", |
| " true", |
| " } ifelse", |
| " {", |
| " 2 array astore", |
| " 2 dict begin", |
| " /PageSize exch def", |
| " /ImagingBBox null def", |
| " currentdict end", |
| " setpagedevice", |
| " } {", |
| " pop pop", |
| " } ifelse", |
| " } {", |
| " pop", |
| " } ifelse", |
| "} def", |
| "~1sn", |
| "/pdfOpNames [", |
| " /pdfFill /pdfStroke /pdfLastFill /pdfLastStroke", |
| " /pdfTextMat /pdfFontSize /pdfCharSpacing /pdfTextRender /pdfPatternCS", |
| " /pdfTextRise /pdfWordSpacing /pdfHorizScaling /pdfTextClipPath", |
| "] def", |
| "~123sn", |
| "/pdfStartPage {", |
| "~1sn", |
| " pdfStates 0 get begin", |
| "~23sn", |
| " pdfDictSize dict begin", |
| "~23n", |
| " /pdfFillCS [] def", |
| " /pdfFillXform {} def", |
| " /pdfStrokeCS [] def", |
| " /pdfStrokeXform {} def", |
| "~1n", |
| " /pdfFill 0 def", |
| " /pdfStroke 0 def", |
| "~1s", |
| " /pdfFill [0 0 0 1] def", |
| " /pdfStroke [0 0 0 1] def", |
| "~23sn", |
| " /pdfFill [0] def", |
| " /pdfStroke [0] def", |
| " /pdfFillOP false def", |
| " /pdfStrokeOP false def", |
| "~3sn", |
| " /pdfOPM false def", |
| "~123sn", |
| " /pdfLastFill false def", |
| " /pdfLastStroke false def", |
| " /pdfTextMat [1 0 0 1 0 0] def", |
| " /pdfFontSize 0 def", |
| " /pdfCharSpacing 0 def", |
| " /pdfTextRender 0 def", |
| " /pdfPatternCS false def", |
| " /pdfTextRise 0 def", |
| " /pdfWordSpacing 0 def", |
| " /pdfHorizScaling 1 def", |
| " /pdfTextClipPath [] def", |
| "} def", |
| "/pdfEndPage { end } def", |
| "~23s", |
| "% separation convention operators", |
| "/findcmykcustomcolor where {", |
| " pop", |
| "}{", |
| " /findcmykcustomcolor { 5 array astore } def", |
| "} ifelse", |
| "/setcustomcolor where {", |
| " pop", |
| "}{", |
| " /setcustomcolor {", |
| " exch", |
| " [ exch /Separation exch dup 4 get exch /DeviceCMYK exch", |
| " 0 4 getinterval cvx", |
| " [ exch /dup load exch { mul exch dup } /forall load", |
| " /pop load dup ] cvx", |
| " ] setcolorspace setcolor", |
| " } def", |
| "} ifelse", |
| "/customcolorimage where {", |
| " pop", |
| "}{", |
| " /customcolorimage {", |
| " gsave", |
| " [ exch /Separation exch dup 4 get exch /DeviceCMYK exch", |
| " 0 4 getinterval", |
| " [ exch /dup load exch { mul exch dup } /forall load", |
| " /pop load dup ] cvx", |
| " ] setcolorspace", |
| " 10 dict begin", |
| " /ImageType 1 def", |
| " /DataSource exch def", |
| " /ImageMatrix exch def", |
| " /BitsPerComponent exch def", |
| " /Height exch def", |
| " /Width exch def", |
| " /Decode [1 0] def", |
| " currentdict end", |
| " image", |
| " grestore", |
| " } def", |
| "} ifelse", |
| "~123sn", |
| "% PDF color state", |
| "~1n", |
| "/g { dup /pdfFill exch def setgray", |
| " /pdfLastFill true def /pdfLastStroke false def } def", |
| "/G { dup /pdfStroke exch def setgray", |
| " /pdfLastStroke true def /pdfLastFill false def } def", |
| "/fCol {", |
| " pdfLastFill not {", |
| " pdfFill setgray", |
| " /pdfLastFill true def /pdfLastStroke false def", |
| " } if", |
| "} def", |
| "/sCol {", |
| " pdfLastStroke not {", |
| " pdfStroke setgray", |
| " /pdfLastStroke true def /pdfLastFill false def", |
| " } if", |
| "} def", |
| "~1s", |
| "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor", |
| " /pdfLastFill true def /pdfLastStroke false def } def", |
| "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor", |
| " /pdfLastStroke true def /pdfLastFill false def } def", |
| "/fCol {", |
| " pdfLastFill not {", |
| " pdfFill aload pop setcmykcolor", |
| " /pdfLastFill true def /pdfLastStroke false def", |
| " } if", |
| "} def", |
| "/sCol {", |
| " pdfLastStroke not {", |
| " pdfStroke aload pop setcmykcolor", |
| " /pdfLastStroke true def /pdfLastFill false def", |
| " } if", |
| "} def", |
| "~3n", |
| "/opm { dup /pdfOPM exch def", |
| " /setoverprintmode where{pop setoverprintmode}{pop}ifelse } def", |
| "~23n", |
| "/cs { /pdfFillXform exch def dup /pdfFillCS exch def", |
| " setcolorspace } def", |
| "/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def", |
| " setcolorspace } def", |
| "/sc { pdfLastFill not { pdfFillCS setcolorspace } if", |
| " dup /pdfFill exch def aload pop pdfFillXform setcolor", |
| " /pdfLastFill true def /pdfLastStroke false def } def", |
| "/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if", |
| " dup /pdfStroke exch def aload pop pdfStrokeXform setcolor", |
| " /pdfLastStroke true def /pdfLastFill false def } def", |
| "/op { /pdfFillOP exch def", |
| " pdfLastFill { pdfFillOP setoverprint } if } def", |
| "/OP { /pdfStrokeOP exch def", |
| " pdfLastStroke { pdfStrokeOP setoverprint } if } def", |
| "/fCol {", |
| " pdfLastFill not {", |
| " pdfFillCS setcolorspace", |
| " pdfFill aload pop pdfFillXform setcolor", |
| " pdfFillOP setoverprint", |
| " /pdfLastFill true def /pdfLastStroke false def", |
| " } if", |
| "} def", |
| "/sCol {", |
| " pdfLastStroke not {", |
| " pdfStrokeCS setcolorspace", |
| " pdfStroke aload pop pdfStrokeXform setcolor", |
| " pdfStrokeOP setoverprint", |
| " /pdfLastStroke true def /pdfLastFill false def", |
| " } if", |
| "} def", |
| "~3s", |
| "/opm { dup /pdfOPM exch def", |
| " /setoverprintmode where{pop setoverprintmode}{pop}ifelse } def", |
| "~23s", |
| "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor", |
| " /pdfLastFill true def /pdfLastStroke false def } def", |
| "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor", |
| " /pdfLastStroke true def /pdfLastFill false def } def", |
| "/ck { 6 copy 6 array astore /pdfFill exch def", |
| " findcmykcustomcolor exch setcustomcolor", |
| " /pdfLastFill true def /pdfLastStroke false def } def", |
| "/CK { 6 copy 6 array astore /pdfStroke exch def", |
| " findcmykcustomcolor exch setcustomcolor", |
| " /pdfLastStroke true def /pdfLastFill false def } def", |
| "/op { /pdfFillOP exch def", |
| " pdfLastFill { pdfFillOP setoverprint } if } def", |
| "/OP { /pdfStrokeOP exch def", |
| " pdfLastStroke { pdfStrokeOP setoverprint } if } def", |
| "/fCol {", |
| " pdfLastFill not {", |
| " pdfFill aload length 4 eq {", |
| " setcmykcolor", |
| " }{", |
| " findcmykcustomcolor exch setcustomcolor", |
| " } ifelse", |
| " pdfFillOP setoverprint", |
| " /pdfLastFill true def /pdfLastStroke false def", |
| " } if", |
| "} def", |
| "/sCol {", |
| " pdfLastStroke not {", |
| " pdfStroke aload length 4 eq {", |
| " setcmykcolor", |
| " }{", |
| " findcmykcustomcolor exch setcustomcolor", |
| " } ifelse", |
| " pdfStrokeOP setoverprint", |
| " /pdfLastStroke true def /pdfLastFill false def", |
| " } if", |
| "} def", |
| "~123sn", |
| "% build a font", |
| "/pdfMakeFont {", |
| " 4 3 roll findfont", |
| " 4 2 roll matrix scale makefont", |
| " dup length dict begin", |
| " { 1 index /FID ne { def } { pop pop } ifelse } forall", |
| " /Encoding exch def", |
| " currentdict", |
| " end", |
| " definefont pop", |
| "} def", |
| "/pdfMakeFont16 {", |
| " exch findfont", |
| " dup length dict begin", |
| " { 1 index /FID ne { def } { pop pop } ifelse } forall", |
| " /WMode exch def", |
| " currentdict", |
| " end", |
| " definefont pop", |
| "} def", |
| "~3sn", |
| "/pdfMakeFont16L3 {", |
| " 1 index /CIDFont resourcestatus {", |
| " pop pop 1 index /CIDFont findresource /CIDFontType known", |
| " } {", |
| " false", |
| " } ifelse", |
| " {", |
| " 0 eq { /Identity-H } { /Identity-V } ifelse", |
| " exch 1 array astore composefont pop", |
| " } {", |
| " pdfMakeFont16", |
| " } ifelse", |
| "} def", |
| "~123sn", |
| "% graphics state operators", |
| "~1sn", |
| "/q {", |
| " gsave", |
| " pdfOpNames length 1 sub -1 0 { pdfOpNames exch get load } for", |
| " pdfStates pdfStateIdx 1 add get begin", |
| " pdfOpNames { exch def } forall", |
| "} def", |
| "/Q { end grestore } def", |
| "~23sn", |
| "/q { gsave pdfDictSize dict begin } def", |
| "/Q {", |
| " end grestore", |
| " /pdfLastFill where {", |
| " pop", |
| " pdfLastFill {", |
| " pdfFillOP setoverprint", |
| " } {", |
| " pdfStrokeOP setoverprint", |
| " } ifelse", |
| " } if", |
| "~3sn", |
| " /pdfOPM where {", |
| " pop", |
| " pdfOPM /setoverprintmode where{pop setoverprintmode}{pop}ifelse ", |
| " } if", |
| "~23sn", |
| "} def", |
| "~123sn", |
| "/cm { concat } def", |
| "/d { setdash } def", |
| "/i { setflat } def", |
| "/j { setlinejoin } def", |
| "/J { setlinecap } def", |
| "/M { setmiterlimit } def", |
| "/w { setlinewidth } def", |
| "% path segment operators", |
| "/m { moveto } def", |
| "/l { lineto } def", |
| "/c { curveto } def", |
| "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto", |
| " neg 0 rlineto closepath } def", |
| "/h { closepath } def", |
| "% path painting operators", |
| "/S { sCol stroke } def", |
| "/Sf { fCol stroke } def", |
| "/f { fCol fill } def", |
| "/f* { fCol eofill } def", |
| "% clipping operators", |
| "/W { clip newpath } def", |
| "/W* { eoclip newpath } def", |
| "/Ws { strokepath clip newpath } def", |
| "% text state operators", |
| "/Tc { /pdfCharSpacing exch def } def", |
| "/Tf { dup /pdfFontSize exch def", |
| " dup pdfHorizScaling mul exch matrix scale", |
| " pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put", |
| " exch findfont exch makefont setfont } def", |
| "/Tr { /pdfTextRender exch def } def", |
| "/Tp { /pdfPatternCS exch def } def", |
| "/Ts { /pdfTextRise exch def } def", |
| "/Tw { /pdfWordSpacing exch def } def", |
| "/Tz { /pdfHorizScaling exch def } def", |
| "% text positioning operators", |
| "/Td { pdfTextMat transform moveto } def", |
| "/Tm { /pdfTextMat exch def } def", |
| "% text string operators", |
| "/xyshow where {", |
| " pop", |
| " /xyshow2 {", |
| " dup length array", |
| " 0 2 2 index length 1 sub {", |
| " 2 index 1 index 2 copy get 3 1 roll 1 add get", |
| " pdfTextMat dtransform", |
| " 4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put", |
| " } for", |
| " exch pop", |
| " xyshow", |
| " } def", |
| "}{", |
| " /xyshow2 {", |
| " currentfont /FontType get 0 eq {", |
| " 0 2 3 index length 1 sub {", |
| " currentpoint 4 index 3 index 2 getinterval show moveto", |
| " 2 copy get 2 index 3 2 roll 1 add get", |
| " pdfTextMat dtransform rmoveto", |
| " } for", |
| " } {", |
| " 0 1 3 index length 1 sub {", |
| " currentpoint 4 index 3 index 1 getinterval show moveto", |
| " 2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get", |
| " pdfTextMat dtransform rmoveto", |
| " } for", |
| " } ifelse", |
| " pop pop", |
| " } def", |
| "} ifelse", |
| "/cshow where {", |
| " pop", |
| " /xycp {", // xycharpath |
| " 0 3 2 roll", |
| " {", |
| " pop pop currentpoint 3 2 roll", |
| " 1 string dup 0 4 3 roll put false charpath moveto", |
| " 2 copy get 2 index 2 index 1 add get", |
| " pdfTextMat dtransform rmoveto", |
| " 2 add", |
| " } exch cshow", |
| " pop pop", |
| " } def", |
| "}{", |
| " /xycp {", // xycharpath |
| " currentfont /FontType get 0 eq {", |
| " 0 2 3 index length 1 sub {", |
| " currentpoint 4 index 3 index 2 getinterval false charpath moveto", |
| " 2 copy get 2 index 3 2 roll 1 add get", |
| " pdfTextMat dtransform rmoveto", |
| " } for", |
| " } {", |
| " 0 1 3 index length 1 sub {", |
| " currentpoint 4 index 3 index 1 getinterval false charpath moveto", |
| " 2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get", |
| " pdfTextMat dtransform rmoveto", |
| " } for", |
| " } ifelse", |
| " pop pop", |
| " } def", |
| "} ifelse", |
| "/Tj {", |
| " fCol", // because stringwidth has to draw Type 3 chars |
| " 0 pdfTextRise pdfTextMat dtransform rmoveto", |
| " currentpoint 4 2 roll", |
| " pdfTextRender 1 and 0 eq {", |
| " 2 copy xyshow2", |
| " } if", |
| " pdfTextRender 3 and dup 1 eq exch 2 eq or {", |
| " 3 index 3 index moveto", |
| " 2 copy", |
| " currentfont /FontType get 3 eq { fCol } { sCol } ifelse", |
| " xycp currentpoint stroke moveto", |
| " } if", |
| " pdfTextRender 4 and 0 ne {", |
| " 4 2 roll moveto xycp", |
| " /pdfTextClipPath [ pdfTextClipPath aload pop", |
| " {/moveto cvx}", |
| " {/lineto cvx}", |
| " {/curveto cvx}", |
| " {/closepath cvx}", |
| " pathforall ] def", |
| " currentpoint newpath moveto", |
| " } {", |
| " pop pop pop pop", |
| " } ifelse", |
| " 0 pdfTextRise neg pdfTextMat dtransform rmoveto", |
| "} def", |
| "/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0", |
| " pdfTextMat dtransform rmoveto } def", |
| "/TJmV { 0.001 mul pdfFontSize mul neg 0 exch", |
| " pdfTextMat dtransform rmoveto } def", |
| "/Tclip { pdfTextClipPath cvx exec clip newpath", |
| " /pdfTextClipPath [] def } def", |
| "/Tclip* { pdfTextClipPath cvx exec eoclip newpath", |
| " /pdfTextClipPath [] def } def", |
| "~1ns", |
| "% Level 1 image operators", |
| "/pdfIm1 {", |
| " /pdfImBuf1 4 index string def", |
| " { currentfile pdfImBuf1 readhexstring pop } image", |
| "} def", |
| "/pdfIm1Bin {", |
| " /pdfImBuf1 4 index string def", |
| " { currentfile pdfImBuf1 readstring pop } image", |
| "} def", |
| "~1s", |
| "/pdfIm1Sep {", |
| " /pdfImBuf1 4 index string def", |
| " /pdfImBuf2 4 index string def", |
| " /pdfImBuf3 4 index string def", |
| " /pdfImBuf4 4 index string def", |
| " { currentfile pdfImBuf1 readhexstring pop }", |
| " { currentfile pdfImBuf2 readhexstring pop }", |
| " { currentfile pdfImBuf3 readhexstring pop }", |
| " { currentfile pdfImBuf4 readhexstring pop }", |
| " true 4 colorimage", |
| "} def", |
| "/pdfIm1SepBin {", |
| " /pdfImBuf1 4 index string def", |
| " /pdfImBuf2 4 index string def", |
| " /pdfImBuf3 4 index string def", |
| " /pdfImBuf4 4 index string def", |
| " { currentfile pdfImBuf1 readstring pop }", |
| " { currentfile pdfImBuf2 readstring pop }", |
| " { currentfile pdfImBuf3 readstring pop }", |
| " { currentfile pdfImBuf4 readstring pop }", |
| " true 4 colorimage", |
| "} def", |
| "~1ns", |
| "/pdfImM1 {", |
| " fCol /pdfImBuf1 4 index 7 add 8 idiv string def", |
| " { currentfile pdfImBuf1 readhexstring pop } imagemask", |
| "} def", |
| "/pdfImM1Bin {", |
| " fCol /pdfImBuf1 4 index 7 add 8 idiv string def", |
| " { currentfile pdfImBuf1 readstring pop } imagemask", |
| "} def", |
| "/pdfImStr {", |
| " 2 copy exch length lt {", |
| " 2 copy get exch 1 add exch", |
| " } {", |
| " ()", |
| " } ifelse", |
| "} def", |
| "/pdfImM1a {", |
| " { pdfImStr } imagemask", |
| " pop pop", |
| "} def", |
| "~23sn", |
| "% Level 2/3 image operators", |
| "/pdfImBuf 100 string def", |
| "/pdfImStr {", |
| " 2 copy exch length lt {", |
| " 2 copy get exch 1 add exch", |
| " } {", |
| " ()", |
| " } ifelse", |
| "} def", |
| "/skipEOD {", |
| " { currentfile pdfImBuf readline", |
| " not { pop exit } if", |
| " (%-EOD-) eq { exit } if } loop", |
| "} def", |
| "/pdfIm { image skipEOD } def", |
| "~3sn", |
| "/pdfMask {", |
| " /ReusableStreamDecode filter", |
| " skipEOD", |
| " /maskStream exch def", |
| "} def", |
| "/pdfMaskEnd { maskStream closefile } def", |
| "/pdfMaskInit {", |
| " /maskArray exch def", |
| " /maskIdx 0 def", |
| "} def", |
| "/pdfMaskSrc {", |
| " maskIdx maskArray length lt {", |
| " maskArray maskIdx get", |
| " /maskIdx maskIdx 1 add def", |
| " } {", |
| " ()", |
| " } ifelse", |
| "} def", |
| "~23s", |
| "/pdfImSep {", |
| " findcmykcustomcolor exch", |
| " dup /Width get /pdfImBuf1 exch string def", |
| " dup /Decode get aload pop 1 index sub /pdfImDecodeRange exch def", |
| " /pdfImDecodeLow exch def", |
| " begin Width Height BitsPerComponent ImageMatrix DataSource end", |
| " /pdfImData exch def", |
| " { pdfImData pdfImBuf1 readstring pop", |
| " 0 1 2 index length 1 sub {", |
| " 1 index exch 2 copy get", |
| " pdfImDecodeRange mul 255 div pdfImDecodeLow add round cvi", |
| " 255 exch sub put", |
| " } for }", |
| " 6 5 roll customcolorimage", |
| " skipEOD", |
| "} def", |
| "~23sn", |
| "/pdfImM { fCol imagemask skipEOD } def", |
| "~123sn", |
| "/pr { 2 index 2 index 3 2 roll putinterval 4 add } def", |
| "/pdfImClip {", |
| " gsave", |
| " 0 2 4 index length 1 sub {", |
| " dup 4 index exch 2 copy", |
| " get 5 index div put", |
| " 1 add 3 index exch 2 copy", |
| " get 3 index div put", |
| " } for", |
| " pop pop rectclip", |
| "} def", |
| "/pdfImClipEnd { grestore } def", |
| "~23sn", |
| "% shading operators", |
| "/colordelta {", |
| " false 0 1 3 index length 1 sub {", |
| " dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {", |
| " pop true", |
| " } if", |
| " } for", |
| " exch pop exch pop", |
| "} def", |
| "/funcCol { func n array astore } def", |
| "/funcSH {", |
| " dup 0 eq {", |
| " true", |
| " } {", |
| " dup 6 eq {", |
| " false", |
| " } {", |
| " 4 index 4 index funcCol dup", |
| " 6 index 4 index funcCol dup", |
| " 3 1 roll colordelta 3 1 roll", |
| " 5 index 5 index funcCol dup", |
| " 3 1 roll colordelta 3 1 roll", |
| " 6 index 8 index funcCol dup", |
| " 3 1 roll colordelta 3 1 roll", |
| " colordelta or or or", |
| " } ifelse", |
| " } ifelse", |
| " {", |
| " 1 add", |
| " 4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch", |
| " 6 index 6 index 4 index 4 index 4 index funcSH", |
| " 2 index 6 index 6 index 4 index 4 index funcSH", |
| " 6 index 2 index 4 index 6 index 4 index funcSH", |
| " 5 3 roll 3 2 roll funcSH pop pop", |
| " } {", |
| " pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul", |
| "~23n", |
| " funcCol sc", |
| "~23s", |
| " funcCol aload pop k", |
| "~23sn", |
| " dup 4 index exch mat transform m", |
| " 3 index 3 index mat transform l", |
| " 1 index 3 index mat transform l", |
| " mat transform l pop pop h f*", |
| " } ifelse", |
| "} def", |
| "/axialCol {", |
| " dup 0 lt {", |
| " pop t0", |
| " } {", |
| " dup 1 gt {", |
| " pop t1", |
| " } {", |
| " dt mul t0 add", |
| " } ifelse", |
| " } ifelse", |
| " func n array astore", |
| "} def", |
| "/axialSH {", |
| " dup 0 eq {", |
| " true", |
| " } {", |
| " dup 8 eq {", |
| " false", |
| " } {", |
| " 2 index axialCol 2 index axialCol colordelta", |
| " } ifelse", |
| " } ifelse", |
| " {", |
| " 1 add 3 1 roll 2 copy add 0.5 mul", |
| " dup 4 3 roll exch 4 index axialSH", |
| " exch 3 2 roll axialSH", |
| " } {", |
| " pop 2 copy add 0.5 mul", |
| "~23n", |
| " axialCol sc", |
| "~23s", |
| " axialCol aload pop k", |
| "~23sn", |
| " exch dup dx mul x0 add exch dy mul y0 add", |
| " 3 2 roll dup dx mul x0 add exch dy mul y0 add", |
| " dx abs dy abs ge {", |
| " 2 copy yMin sub dy mul dx div add yMin m", |
| " yMax sub dy mul dx div add yMax l", |
| " 2 copy yMax sub dy mul dx div add yMax l", |
| " yMin sub dy mul dx div add yMin l", |
| " h f*", |
| " } {", |
| " exch 2 copy xMin sub dx mul dy div add xMin exch m", |
| " xMax sub dx mul dy div add xMax exch l", |
| " exch 2 copy xMax sub dx mul dy div add xMax exch l", |
| " xMin sub dx mul dy div add xMin exch l", |
| " h f*", |
| " } ifelse", |
| " } ifelse", |
| "} def", |
| "/radialCol {", |
| " dup t0 lt {", |
| " pop t0", |
| " } {", |
| " dup t1 gt {", |
| " pop t1", |
| " } if", |
| " } ifelse", |
| " func n array astore", |
| "} def", |
| "/radialSH {", |
| " dup 0 eq {", |
| " true", |
| " } {", |
| " dup 8 eq {", |
| " false", |
| " } {", |
| " 2 index dt mul t0 add radialCol", |
| " 2 index dt mul t0 add radialCol colordelta", |
| " } ifelse", |
| " } ifelse", |
| " {", |
| " 1 add 3 1 roll 2 copy add 0.5 mul", |
| " dup 4 3 roll exch 4 index radialSH", |
| " exch 3 2 roll radialSH", |
| " } {", |
| " pop 2 copy add 0.5 mul dt mul t0 add", |
| "~23n", |
| " radialCol sc", |
| "~23s", |
| " radialCol aload pop k", |
| "~23sn", |
| " encl {", |
| " exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", |
| " 0 360 arc h", |
| " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", |
| " 360 0 arcn h f", |
| " } {", |
| " 2 copy", |
| " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", |
| " a1 a2 arcn", |
| " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", |
| " a2 a1 arcn h", |
| " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", |
| " a1 a2 arc", |
| " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", |
| " a2 a1 arc h f", |
| " } ifelse", |
| " } ifelse", |
| "} def", |
| "~123sn", |
| "end", |
| nullptr }; |
| |
| static const char *cmapProlog[] = { "/CIDInit /ProcSet findresource begin", |
| "10 dict begin", |
| " begincmap", |
| " /CMapType 1 def", |
| " /CMapName /Identity-H def", |
| " /CIDSystemInfo 3 dict dup begin", |
| " /Registry (Adobe) def", |
| " /Ordering (Identity) def", |
| " /Supplement 0 def", |
| " end def", |
| " 1 begincodespacerange", |
| " <0000> <ffff>", |
| " endcodespacerange", |
| " 0 usefont", |
| " 1 begincidrange", |
| " <0000> <ffff> 0", |
| " endcidrange", |
| " endcmap", |
| " currentdict CMapName exch /CMap defineresource pop", |
| "end", |
| "10 dict begin", |
| " begincmap", |
| " /CMapType 1 def", |
| " /CMapName /Identity-V def", |
| " /CIDSystemInfo 3 dict dup begin", |
| " /Registry (Adobe) def", |
| " /Ordering (Identity) def", |
| " /Supplement 0 def", |
| " end def", |
| " /WMode 1 def", |
| " 1 begincodespacerange", |
| " <0000> <ffff>", |
| " endcodespacerange", |
| " 0 usefont", |
| " 1 begincidrange", |
| " <0000> <ffff> 0", |
| " endcidrange", |
| " endcmap", |
| " currentdict CMapName exch /CMap defineresource pop", |
| "end", |
| "end", |
| nullptr }; |
| |
| //------------------------------------------------------------------------ |
| // Fonts |
| //------------------------------------------------------------------------ |
| |
| struct PSSubstFont |
| { |
| const char *psName; // PostScript name |
| double mWidth; // width of 'm' character |
| }; |
| |
| // NB: must be in same order as base14SubstFonts in GfxFont.cc |
| static const PSSubstFont psBase14SubstFonts[14] = { { "Courier", 0.600 }, |
| { "Courier-Oblique", 0.600 }, |
| { "Courier-Bold", 0.600 }, |
| { "Courier-BoldOblique", 0.600 }, |
| { "Helvetica", 0.833 }, |
| { "Helvetica-Oblique", 0.833 }, |
| { "Helvetica-Bold", 0.889 }, |
| { "Helvetica-BoldOblique", 0.889 }, |
| { "Times-Roman", 0.788 }, |
| { "Times-Italic", 0.722 }, |
| { "Times-Bold", 0.833 }, |
| { "Times-BoldItalic", 0.778 }, |
| // the last two are never used for substitution |
| { "Symbol", 0 }, |
| { "ZapfDingbats", 0 } }; |
| |
| // Mapping from Type 1/1C font file to PS font name. |
| struct PST1FontName |
| { |
| Ref fontFileID; |
| GooString *psName; // PostScript font name used for this |
| // embedded font file |
| }; |
| |
| // Info for 8-bit fonts |
| struct PSFont8Info |
| { |
| Ref fontID; |
| int *codeToGID; // code-to-GID mapping for TrueType fonts |
| }; |
| |
| // Encoding info for substitute 16-bit font |
| struct PSFont16Enc |
| { |
| Ref fontID; |
| GooString *enc; |
| }; |
| |
| //------------------------------------------------------------------------ |
| // process colors |
| //------------------------------------------------------------------------ |
| |
| #define psProcessCyan 1 |
| #define psProcessMagenta 2 |
| #define psProcessYellow 4 |
| #define psProcessBlack 8 |
| #define psProcessCMYK 15 |
| |
| //------------------------------------------------------------------------ |
| // PSOutCustomColor |
| //------------------------------------------------------------------------ |
| |
| class PSOutCustomColor |
| { |
| public: |
| PSOutCustomColor(double cA, double mA, double yA, double kA, GooString *nameA); |
| ~PSOutCustomColor(); |
| |
| PSOutCustomColor(const PSOutCustomColor &) = delete; |
| PSOutCustomColor &operator=(const PSOutCustomColor &) = delete; |
| |
| double c, m, y, k; |
| GooString *name; |
| PSOutCustomColor *next; |
| }; |
| |
| PSOutCustomColor::PSOutCustomColor(double cA, double mA, double yA, double kA, GooString *nameA) |
| { |
| c = cA; |
| m = mA; |
| y = yA; |
| k = kA; |
| name = nameA; |
| next = nullptr; |
| } |
| |
| PSOutCustomColor::~PSOutCustomColor() |
| { |
| delete name; |
| } |
| |
| //------------------------------------------------------------------------ |
| |
| struct PSOutImgClipRect |
| { |
| int x0, x1, y0, y1; |
| }; |
| |
| //------------------------------------------------------------------------ |
| // DeviceNRecoder |
| //------------------------------------------------------------------------ |
| |
| class DeviceNRecoder : public FilterStream |
| { |
| public: |
| DeviceNRecoder(Stream *strA, int widthA, int heightA, GfxImageColorMap *colorMapA); |
| ~DeviceNRecoder() override; |
| StreamKind getKind() const override { return strWeird; } |
| void reset() override; |
| int getChar() override { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx++]; } |
| int lookChar() override { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx]; } |
| GooString *getPSFilter(int psLevel, const char *indent) override { return nullptr; } |
| bool isBinary(bool last = true) const override { return true; } |
| bool isEncoder() const override { return true; } |
| |
| private: |
| bool fillBuf(); |
| |
| int width, height; |
| GfxImageColorMap *colorMap; |
| const Function *func; |
| ImageStream *imgStr; |
| int buf[gfxColorMaxComps]; |
| int pixelIdx; |
| int bufIdx; |
| int bufSize; |
| }; |
| |
| DeviceNRecoder::DeviceNRecoder(Stream *strA, int widthA, int heightA, GfxImageColorMap *colorMapA) : FilterStream(strA) |
| { |
| width = widthA; |
| height = heightA; |
| colorMap = colorMapA; |
| imgStr = nullptr; |
| pixelIdx = 0; |
| bufIdx = gfxColorMaxComps; |
| bufSize = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getAlt()->getNComps(); |
| func = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getTintTransformFunc(); |
| } |
| |
| DeviceNRecoder::~DeviceNRecoder() |
| { |
| if (imgStr) { |
| delete imgStr; |
| } |
| if (str->isEncoder()) { |
| delete str; |
| } |
| } |
| |
| void DeviceNRecoder::reset() |
| { |
| imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); |
| imgStr->reset(); |
| } |
| |
| bool DeviceNRecoder::fillBuf() |
| { |
| unsigned char pixBuf[gfxColorMaxComps]; |
| GfxColor color; |
| double x[gfxColorMaxComps], y[gfxColorMaxComps]; |
| int i; |
| |
| if (pixelIdx >= width * height) { |
| return false; |
| } |
| imgStr->getPixel(pixBuf); |
| colorMap->getColor(pixBuf, &color); |
| for (i = 0; i < ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getNComps(); ++i) { |
| x[i] = colToDbl(color.c[i]); |
| } |
| func->transform(x, y); |
| for (i = 0; i < bufSize; ++i) { |
| buf[i] = (int)(y[i] * 255 + 0.5); |
| } |
| bufIdx = 0; |
| ++pixelIdx; |
| return true; |
| } |
| |
| //------------------------------------------------------------------------ |
| // PSOutputDev |
| //------------------------------------------------------------------------ |
| |
| extern "C" { |
| typedef void (*SignalFunc)(int); |
| } |
| |
| static void outputToFile(void *stream, const char *data, size_t len) |
| { |
| fwrite(data, 1, len, (FILE *)stream); |
| } |
| |
| PSOutputDev::PSOutputDev(const char *fileName, PDFDoc *docA, char *psTitleA, const std::vector<int> &pagesA, PSOutMode modeA, int paperWidthA, int paperHeightA, bool noCropA, bool duplexA, int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, |
| PSForceRasterize forceRasterizeA, bool manualCtrlA, PSOutCustomCodeCbk customCodeCbkA, void *customCodeCbkDataA, PSLevel levelA) |
| { |
| FILE *f; |
| PSFileType fileTypeA; |
| |
| underlayCbk = nullptr; |
| underlayCbkData = nullptr; |
| overlayCbk = nullptr; |
| overlayCbkData = nullptr; |
| customCodeCbk = customCodeCbkA; |
| customCodeCbkData = customCodeCbkDataA; |
| |
| t1FontNames = nullptr; |
| font8Info = nullptr; |
| font16Enc = nullptr; |
| imgIDs = nullptr; |
| formIDs = nullptr; |
| embFontList = nullptr; |
| customColors = nullptr; |
| haveTextClip = false; |
| t3String = nullptr; |
| forceRasterize = forceRasterizeA; |
| psTitle = nullptr; |
| |
| // open file or pipe |
| if (!strcmp(fileName, "-")) { |
| fileTypeA = psStdout; |
| f = stdout; |
| } else if (fileName[0] == '|') { |
| fileTypeA = psPipe; |
| #ifdef HAVE_POPEN |
| # ifndef _WIN32 |
| signal(SIGPIPE, (SignalFunc)SIG_IGN); |
| # endif |
| if (!(f = popen(fileName + 1, "w"))) { |
| error(errIO, -1, "Couldn't run print command '{0:s}'", fileName); |
| ok = false; |
| return; |
| } |
| #else |
| error(errIO, -1, "Print commands are not supported ('{0:s}')", fileName); |
| ok = false; |
| return; |
| #endif |
| } else { |
| fileTypeA = psFile; |
| if (!(f = openFile(fileName, "w"))) { |
| error(errIO, -1, "Couldn't open PostScript file '{0:s}'", fileName); |
| ok = false; |
| return; |
| } |
| } |
| |
| init(outputToFile, f, fileTypeA, psTitleA, docA, pagesA, modeA, imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA, paperWidthA, paperHeightA, noCropA, duplexA, levelA); |
| } |
| |
| PSOutputDev::PSOutputDev(int fdA, PDFDoc *docA, char *psTitleA, const std::vector<int> &pagesA, PSOutMode modeA, int paperWidthA, int paperHeightA, bool noCropA, bool duplexA, int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, |
| PSForceRasterize forceRasterizeA, bool manualCtrlA, PSOutCustomCodeCbk customCodeCbkA, void *customCodeCbkDataA, PSLevel levelA) |
| { |
| FILE *f; |
| PSFileType fileTypeA; |
| |
| underlayCbk = nullptr; |
| underlayCbkData = nullptr; |
| overlayCbk = nullptr; |
| overlayCbkData = nullptr; |
| customCodeCbk = customCodeCbkA; |
| customCodeCbkData = customCodeCbkDataA; |
| |
| t1FontNames = nullptr; |
| font8Info = nullptr; |
| font16Enc = nullptr; |
| imgIDs = nullptr; |
| formIDs = nullptr; |
| embFontList = nullptr; |
| customColors = nullptr; |
| haveTextClip = false; |
| t3String = nullptr; |
| forceRasterize = forceRasterizeA; |
| psTitle = nullptr; |
| |
| // open file or pipe |
| if (fdA == fileno(stdout)) { |
| fileTypeA = psStdout; |
| f = stdout; |
| } else { |
| fileTypeA = psFile; |
| if (!(f = fdopen(fdA, "w"))) { |
| error(errIO, -1, "Couldn't open PostScript file descriptor '{0:d}'", fdA); |
| ok = false; |
| return; |
| } |
| } |
| |
| init(outputToFile, f, fileTypeA, psTitleA, docA, pagesA, modeA, imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA, paperWidthA, paperHeightA, noCropA, duplexA, levelA); |
| } |
| |
| PSOutputDev::PSOutputDev(FoFiOutputFunc outputFuncA, void *outputStreamA, char *psTitleA, PDFDoc *docA, const std::vector<int> &pagesA, PSOutMode modeA, int paperWidthA, int paperHeightA, bool noCropA, bool duplexA, int imgLLXA, |
| int imgLLYA, int imgURXA, int imgURYA, PSForceRasterize forceRasterizeA, bool manualCtrlA, PSOutCustomCodeCbk customCodeCbkA, void *customCodeCbkDataA, PSLevel levelA) |
| { |
| underlayCbk = nullptr; |
| underlayCbkData = nullptr; |
| overlayCbk = nullptr; |
| overlayCbkData = nullptr; |
| customCodeCbk = customCodeCbkA; |
| customCodeCbkData = customCodeCbkDataA; |
| |
| t1FontNames = nullptr; |
| font8Info = nullptr; |
| font16Enc = nullptr; |
| imgIDs = nullptr; |
| formIDs = nullptr; |
| embFontList = nullptr; |
| customColors = nullptr; |
| haveTextClip = false; |
| t3String = nullptr; |
| forceRasterize = forceRasterizeA; |
| psTitle = nullptr; |
| |
| init(outputFuncA, outputStreamA, psGeneric, psTitleA, docA, pagesA, modeA, imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA, paperWidthA, paperHeightA, noCropA, duplexA, levelA); |
| } |
| |
| struct StandardMedia |
| { |
| const char *name; |
| int width; |
| int height; |
| }; |
| |
| static const StandardMedia standardMedia[] = { { "A0", 2384, 3371 }, { "A1", 1685, 2384 }, { "A2", 1190, 1684 }, { "A3", 842, 1190 }, { "A4", 595, 842 }, { "A5", 420, 595 }, |
| { "B4", 729, 1032 }, { "B5", 516, 729 }, { "Letter", 612, 792 }, { "Tabloid", 792, 1224 }, { "Ledger", 1224, 792 }, { "Legal", 612, 1008 }, |
| { "Statement", 396, 612 }, { "Executive", 540, 720 }, { "Folio", 612, 936 }, { "Quarto", 610, 780 }, { "10x14", 720, 1008 }, { nullptr, 0, 0 } }; |
| |
| /* PLRM specifies a tolerance of 5 points when matching page sizes */ |
| static bool pageDimensionEqual(int a, int b) |
| { |
| int aux; |
| if (unlikely(checkedSubtraction(a, b, &aux))) { |
| return false; |
| } |
| return (abs(aux) < 5); |
| } |
| |
| // Shared initialization of PSOutputDev members. |
| // Store the values but do not process them so the function that |
| // created the PSOutputDev can use the various setters to change defaults. |
| |
| void PSOutputDev::init(FoFiOutputFunc outputFuncA, void *outputStreamA, PSFileType fileTypeA, char *psTitleA, PDFDoc *docA, const std::vector<int> &pagesA, PSOutMode modeA, int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, |
| bool manualCtrlA, int paperWidthA, int paperHeightA, bool noCropA, bool duplexA, PSLevel levelA) |
| { |
| |
| if (pagesA.empty()) { |
| ok = false; |
| return; |
| } |
| |
| // initialize |
| postInitDone = false; |
| embedType1 = true; |
| embedTrueType = true; |
| embedCIDPostScript = true; |
| embedCIDTrueType = true; |
| fontPassthrough = false; |
| optimizeColorSpace = false; |
| passLevel1CustomColor = false; |
| preloadImagesForms = false; |
| generateOPI = false; |
| useASCIIHex = false; |
| useBinary = false; |
| enableLZW = true; |
| enableFlate = true; |
| rasterResolution = 300; |
| uncompressPreloadedImages = false; |
| psCenter = true; |
| rasterAntialias = false; |
| displayText = true; |
| ok = true; |
| outputFunc = outputFuncA; |
| outputStream = outputStreamA; |
| fileType = fileTypeA; |
| psTitle = (psTitleA ? strdup(psTitleA) : nullptr); |
| doc = docA; |
| level = levelA; |
| pages = pagesA; |
| mode = modeA; |
| paperWidth = paperWidthA; |
| paperHeight = paperHeightA; |
| noCrop = noCropA; |
| duplex = duplexA; |
| imgLLX = imgLLXA; |
| imgLLY = imgLLYA; |
| imgURX = imgURXA; |
| imgURY = imgURYA; |
| manualCtrl = manualCtrlA; |
| |
| xref = nullptr; |
| |
| processColors = 0; |
| inType3Char = false; |
| inUncoloredPattern = false; |
| t3FillColorOnly = false; |
| |
| #ifdef OPI_SUPPORT |
| // initialize OPI nesting levels |
| opi13Nest = 0; |
| opi20Nest = 0; |
| #endif |
| |
| tx0 = ty0 = -1; |
| xScale0 = yScale0 = 0; |
| rotate0 = -1; |
| clipLLX0 = clipLLY0 = 0; |
| clipURX0 = clipURY0 = -1; |
| |
| processColorFormatSpecified = false; |
| |
| // initialize sequential page number |
| seqPage = 1; |
| } |
| |
| // Complete the initialization after the function that created the PSOutputDev |
| // has had a chance to modify default values with the various setters. |
| |
| void PSOutputDev::postInit() |
| { |
| Catalog *catalog; |
| PDFRectangle *box; |
| int w, h, i; |
| |
| if (postInitDone || !ok) { |
| return; |
| } |
| |
| postInitDone = true; |
| |
| xref = doc->getXRef(); |
| catalog = doc->getCatalog(); |
| |
| if (paperWidth < 0 || paperHeight < 0) { |
| paperMatch = true; |
| } else { |
| paperMatch = false; |
| } |
| |
| paperSizes.clear(); |
| for (const int pg : pages) { |
| Page *page = catalog->getPage(pg); |
| if (page == nullptr) { |
| paperMatch = false; |
| } |
| if (!paperMatch) { |
| w = paperWidth; |
| h = paperHeight; |
| if (w < 0 || h < 0) { |
| // Unable to obtain a paper size from the document and no page size |
| // specified. In this case use A4 as the page size to ensure the PS output is |
| // valid. This will only occur if the PDF is very broken. |
| w = 595; |
| h = 842; |
| } |
| } else if (noCrop) { |
| w = (int)ceil(page->getMediaWidth()); |
| h = (int)ceil(page->getMediaHeight()); |
| } else { |
| w = (int)ceil(page->getCropWidth()); |
| h = (int)ceil(page->getCropHeight()); |
| } |
| if (paperMatch) { |
| const int pageRotate = page->getRotate(); |
| if (pageRotate == 90 || pageRotate == 270) { |
| std::swap(w, h); |
| } |
| } |
| if (w > paperWidth) { |
| paperWidth = w; |
| } |
| if (h > paperHeight) { |
| paperHeight = h; |
| } |
| for (i = 0; i < (int)paperSizes.size(); ++i) { |
| const PSOutPaperSize &size = paperSizes[i]; |
| if (pageDimensionEqual(w, size.w) && pageDimensionEqual(h, size.h)) { |
| break; |
| } |
| } |
| if (i == (int)paperSizes.size()) { |
| const StandardMedia *media = standardMedia; |
| std::string name; |
| while (media->name) { |
| if (pageDimensionEqual(w, media->width) && pageDimensionEqual(h, media->height)) { |
| name = std::string(media->name); |
| w = media->width; |
| h = media->height; |
| break; |
| } |
| media++; |
| } |
| if (name.empty()) { |
| name = GooString::format("{0:d}x{1:d}mm", int(w * 25.4 / 72), int(h * 25.4 / 72)); |
| } |
| paperSizes.emplace_back(std::move(name), w, h); |
| } |
| pagePaperSize.insert(std::pair<int, int>(pg, i)); |
| if (!paperMatch) { |
| break; // we only need one entry when all pages are the same size |
| } |
| } |
| if (imgLLX == 0 && imgURX == 0 && imgLLY == 0 && imgURY == 0) { |
| imgLLX = imgLLY = 0; |
| imgURX = paperWidth; |
| imgURY = paperHeight; |
| } |
| std::vector<int> pageList; |
| if (mode == psModeForm) { |
| pageList.push_back(pages[0]); |
| } else { |
| pageList = pages; |
| } |
| |
| // initialize fontIDs, fontFileIDs, and fontFileNames lists |
| fontIDs.reserve(64); |
| fontIDs.resize(0); |
| for (i = 0; i < 14; ++i) { |
| fontNames.emplace(psBase14SubstFonts[i].psName); |
| } |
| t1FontNameSize = 64; |
| t1FontNameLen = 0; |
| t1FontNames = (PST1FontName *)gmallocn(t1FontNameSize, sizeof(PST1FontName)); |
| font8InfoLen = 0; |
| font8InfoSize = 0; |
| font16EncLen = 0; |
| font16EncSize = 0; |
| imgIDLen = 0; |
| imgIDSize = 0; |
| formIDLen = 0; |
| formIDSize = 0; |
| |
| numSaves = 0; |
| numTilingPatterns = 0; |
| nextFunc = 0; |
| |
| // set some default process color format if none is set |
| if (!processColorFormatSpecified) { |
| if (level == psLevel1) { |
| processColorFormat = splashModeMono8; |
| } else if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep || overprintPreview) { |
| processColorFormat = splashModeCMYK8; |
| } |
| #ifdef USE_CMS |
| else if (getDisplayProfile()) { |
| auto processcolorspace = cmsGetColorSpace(getDisplayProfile().get()); |
| if (processcolorspace == cmsSigCmykData) { |
| processColorFormat = splashModeCMYK8; |
| } else if (processcolorspace == cmsSigGrayData) { |
| processColorFormat = splashModeMono8; |
| } else { |
| processColorFormat = splashModeRGB8; |
| } |
| } |
| #endif |
| else { |
| processColorFormat = splashModeRGB8; |
| } |
| } |
| |
| // check for consistency between the processColorFormat the LanguageLevel and other settings |
| if (level == psLevel1 && processColorFormat != splashModeMono8) { |
| error(errConfig, -1, |
| "Conflicting settings between LanguageLevel=psLevel1 and processColorFormat." |
| " Resetting processColorFormat to MONO8."); |
| processColorFormat = splashModeMono8; |
| } else if ((level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep || overprintPreview) && processColorFormat != splashModeCMYK8) { |
| error(errConfig, -1, |
| "Conflicting settings between LanguageLevel and/or overprint simulation, and processColorFormat." |
| " Resetting processColorFormat to CMYK8."); |
| processColorFormat = splashModeCMYK8; |
| } |
| #ifdef USE_CMS |
| if (getDisplayProfile()) { |
| auto processcolorspace = cmsGetColorSpace(getDisplayProfile().get()); |
| if (processColorFormat == splashModeCMYK8) { |
| if (processcolorspace != cmsSigCmykData) { |
| error(errConfig, -1, "Mismatch between processColorFormat=CMYK8 and ICC profile color format."); |
| } |
| } else if (processColorFormat == splashModeMono8) { |
| if (processcolorspace != cmsSigGrayData) { |
| error(errConfig, -1, "Mismatch between processColorFormat=MONO8 and ICC profile color format."); |
| } |
| } else if (processColorFormat == splashModeRGB8) { |
| if (processcolorspace != cmsSigRgbData) { |
| error(errConfig, -1, "Mismatch between processColorFormat=RGB8 and ICC profile color format."); |
| } |
| } |
| } |
| #endif |
| |
| // initialize embedded font resource comment list |
| embFontList = new GooString(); |
| |
| if (!manualCtrl) { |
| Page *page; |
| // this check is needed in case the document has zero pages |
| if ((page = doc->getPage(pageList[0]))) { |
| writeHeader(pageList.size(), page->getMediaBox(), page->getCropBox(), page->getRotate(), psTitle); |
| } else { |
| error(errSyntaxError, -1, "Invalid page {0:d}", pageList[0]); |
| box = new PDFRectangle(0, 0, 1, 1); |
| writeHeader(pageList.size(), box, box, 0, psTitle); |
| delete box; |
| } |
| if (mode != psModeForm) { |
| writePS("%%BeginProlog\n"); |
| } |
| writeXpdfProcset(); |
| if (mode != psModeForm) { |
| writePS("%%EndProlog\n"); |
| writePS("%%BeginSetup\n"); |
| } |
| writeDocSetup(catalog, pageList, duplex); |
| if (mode != psModeForm) { |
| writePS("%%EndSetup\n"); |
| } |
| } |
| } |
| |
| PSOutputDev::~PSOutputDev() |
| { |
| PSOutCustomColor *cc; |
| int i; |
| |
| if (ok) { |
| if (!postInitDone) { |
| postInit(); |
| } |
| if (!manualCtrl) { |
| writePS("%%Trailer\n"); |
| writeTrailer(); |
| if (mode != psModeForm) { |
| writePS("%%EOF\n"); |
| } |
| } |
| if (fileType == psFile) { |
| fclose((FILE *)outputStream); |
| } |
| #ifdef HAVE_POPEN |
| else if (fileType == psPipe) { |
| pclose((FILE *)outputStream); |
| # ifndef _WIN32 |
| signal(SIGPIPE, (SignalFunc)SIG_DFL); |
| # endif |
| } |
| #endif |
| } |
| if (embFontList) { |
| delete embFontList; |
| } |
| if (t1FontNames) { |
| for (i = 0; i < t1FontNameLen; ++i) { |
| delete t1FontNames[i].psName; |
| } |
| gfree(t1FontNames); |
| } |
| if (font8Info) { |
| for (i = 0; i < font8InfoLen; ++i) { |
| gfree(font8Info[i].codeToGID); |
| } |
| gfree(font8Info); |
| } |
| if (font16Enc) { |
| for (i = 0; i < font16EncLen; ++i) { |
| if (font16Enc[i].enc) { |
| delete font16Enc[i].enc; |
| } |
| } |
| gfree(font16Enc); |
| } |
| gfree(imgIDs); |
| gfree(formIDs); |
| while (customColors) { |
| cc = customColors; |
| customColors = cc->next; |
| delete cc; |
| } |
| gfree(psTitle); |
| delete t3String; |
| } |
| |
| void PSOutputDev::writeHeader(int nPages, const PDFRectangle *mediaBox, const PDFRectangle *cropBox, int pageRotate, const char *title) |
| { |
| double x1, y1, x2, y2; |
| |
| switch (mode) { |
| case psModePS: |
| writePS("%!PS-Adobe-3.0\n"); |
| break; |
| case psModeEPS: |
| writePS("%!PS-Adobe-3.0 EPSF-3.0\n"); |
| break; |
| case psModeForm: |
| writePS("%!PS-Adobe-3.0 Resource-Form\n"); |
| break; |
| } |
| Object info = xref->getDocInfo(); |
| std::string creator = GooString::format("poppler pdftops version: {0:s} (http://poppler.freedesktop.org)", PACKAGE_VERSION); |
| if (info.isDict()) { |
| Object obj1 = info.dictLookup("Creator"); |
| if (obj1.isString()) { |
| const GooString *pdfCreator = obj1.getString(); |
| if (pdfCreator && !pdfCreator->toStr().empty()) { |
| creator.append(". PDF Creator: "); |
| if (hasUnicodeByteOrderMark(pdfCreator->toStr())) { |
| creator.append(TextStringToUtf8(pdfCreator->toStr())); |
| } else { |
| creator.append(pdfCreator->toStr()); |
| } |
| } |
| } |
| } |
| writePS("%%Creator: "); |
| writePSTextLine(creator); |
| if (title) { |
| char *sanitizedTitle = strdup(title); |
| for (size_t i = 0; i < strlen(sanitizedTitle); ++i) { |
| if (sanitizedTitle[i] == '\n' || sanitizedTitle[i] == '\r') { |
| sanitizedTitle[i] = ' '; |
| } |
| } |
| writePSFmt("%%Title: {0:s}\n", sanitizedTitle); |
| free(sanitizedTitle); |
| } |
| writePSFmt("%%LanguageLevel: {0:d}\n", (level == psLevel1 || level == psLevel1Sep) ? 1 : (level == psLevel2 || level == psLevel2Sep) ? 2 : 3); |
| if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) { |
| writePS("%%DocumentProcessColors: (atend)\n"); |
| writePS("%%DocumentCustomColors: (atend)\n"); |
| } |
| writePS("%%DocumentSuppliedResources: (atend)\n"); |
| if ((level == psLevel1 || level == psLevel1Sep) && useBinary) { |
| writePS("%%DocumentData: Binary\n"); |
| } |
| |
| switch (mode) { |
| case psModePS: |
| for (std::size_t i = 0; i < paperSizes.size(); ++i) { |
| const PSOutPaperSize &size = paperSizes[i]; |
| writePSFmt("%%{0:s} {1:s} {2:d} {3:d} 0 () ()\n", i == 0 ? "DocumentMedia:" : "+", size.name.c_str(), size.w, size.h); |
| } |
| writePSFmt("%%BoundingBox: 0 0 {0:d} {1:d}\n", paperWidth, paperHeight); |
| writePSFmt("%%Pages: {0:d}\n", nPages); |
| writePS("%%EndComments\n"); |
| if (!paperMatch) { |
| writePS("%%BeginDefaults\n"); |
| writePSFmt("%%PageMedia: {0:s}\n", paperSizes[0].name.c_str()); |
| writePS("%%EndDefaults\n"); |
| } |
| break; |
| case psModeEPS: |
| epsX1 = cropBox->x1; |
| epsY1 = cropBox->y1; |
| epsX2 = cropBox->x2; |
| epsY2 = cropBox->y2; |
| if (pageRotate == 0 || pageRotate == 180) { |
| x1 = epsX1; |
| y1 = epsY1; |
| x2 = epsX2; |
| y2 = epsY2; |
| } else { // pageRotate == 90 || pageRotate == 270 |
| x1 = 0; |
| y1 = 0; |
| x2 = epsY2 - epsY1; |
| y2 = epsX2 - epsX1; |
| } |
| writePSFmt("%%BoundingBox: {0:d} {1:d} {2:d} {3:d}\n", (int)floor(x1), (int)floor(y1), (int)ceil(x2), (int)ceil(y2)); |
| writePSFmt("%%HiResBoundingBox: {0:.6g} {1:.6g} {2:.6g} {3:.6g}\n", x1, y1, x2, y2); |
| writePS("%%DocumentSuppliedResources: (atend)\n"); |
| writePS("%%EndComments\n"); |
| break; |
| case psModeForm: |
| writePS("%%EndComments\n"); |
| writePS("32 dict dup begin\n"); |
| writePSFmt("/BBox [{0:d} {1:d} {2:d} {3:d}] def\n", (int)floor(mediaBox->x1), (int)floor(mediaBox->y1), (int)ceil(mediaBox->x2), (int)ceil(mediaBox->y2)); |
| writePS("/FormType 1 def\n"); |
| writePS("/Matrix [1 0 0 1 0 0] def\n"); |
| break; |
| } |
| } |
| |
| void PSOutputDev::writeXpdfProcset() |
| { |
| bool lev1, lev2, lev3, sep, nonSep; |
| const char **p; |
| const char *q; |
| |
| writePSFmt("%%BeginResource: procset xpdf {0:s} 0\n", "3.00"); |
| writePSFmt("%%Copyright: {0:s}\n", xpdfCopyright); |
| lev1 = lev2 = lev3 = sep = nonSep = true; |
| for (p = prolog; *p; ++p) { |
| if ((*p)[0] == '~') { |
| lev1 = lev2 = lev3 = sep = nonSep = false; |
| for (q = *p + 1; *q; ++q) { |
| switch (*q) { |
| case '1': |
| lev1 = true; |
| break; |
| case '2': |
| lev2 = true; |
| break; |
| case '3': |
| lev3 = true; |
| break; |
| case 's': |
| sep = true; |
| break; |
| case 'n': |
| nonSep = true; |
| break; |
| } |
| } |
| } else if ((level == psLevel1 && lev1 && nonSep) || (level == psLevel1Sep && lev1 && sep) || (level == psLevel1Sep && lev2 && sep && getPassLevel1CustomColor()) || (level == psLevel2 && lev2 && nonSep) |
| || (level == psLevel2Sep && lev2 && sep) || (level == psLevel3 && lev3 && nonSep) || (level == psLevel3Sep && lev3 && sep)) { |
| writePSFmt("{0:s}\n", *p); |
| } |
| } |
| writePS("%%EndResource\n"); |
| |
| if (level >= psLevel3) { |
| for (p = cmapProlog; *p; ++p) { |
| writePSFmt("{0:s}\n", *p); |
| } |
| } |
| } |
| |
| void PSOutputDev::writeDocSetup(Catalog *catalog, const std::vector<int> &pageList, bool duplexA) |
| { |
| Page *page; |
| Dict *resDict; |
| Annots *annots; |
| Object *acroForm; |
| GooString *s; |
| |
| if (mode == psModeForm) { |
| // swap the form and xpdf dicts |
| writePS("xpdf end begin dup begin\n"); |
| } else { |
| writePS("xpdf begin\n"); |
| } |
| for (const int pg : pageList) { |
| page = doc->getPage(pg); |
| if (!page) { |
| error(errSyntaxError, -1, "Failed writing resources for page {0:d}", pg); |
| continue; |
| } |
| if ((resDict = page->getResourceDict())) { |
| setupResources(resDict); |
| } |
| annots = page->getAnnots(); |
| for (Annot *annot : annots->getAnnots()) { |
| Object obj1 = annot->getAppearanceResDict(); |
| if (obj1.isDict()) { |
| setupResources(obj1.getDict()); |
| } |
| } |
| } |
| if ((acroForm = catalog->getAcroForm()) && acroForm->isDict()) { |
| Object obj1 = acroForm->dictLookup("DR"); |
| if (obj1.isDict()) { |
| setupResources(obj1.getDict()); |
| } |
| obj1 = acroForm->dictLookup("Fields"); |
| if (obj1.isArray()) { |
| for (int i = 0; i < obj1.arrayGetLength(); ++i) { |
| Object obj2 = obj1.arrayGet(i); |
| if (obj2.isDict()) { |
| Object obj3 = obj2.dictLookup("DR"); |
| if (obj3.isDict()) { |
| setupResources(obj3.getDict()); |
| } |
| } |
| } |
| } |
| } |
| if (mode != psModeForm) { |
| if (mode != psModeEPS && !manualCtrl) { |
| writePSFmt("{0:s} pdfSetup\n", duplexA ? "true" : "false"); |
| if (!paperMatch) { |
| writePSFmt("{0:d} {1:d} pdfSetupPaper\n", paperWidth, paperHeight); |
| } |
| } |
| #ifdef OPI_SUPPORT |
| if (generateOPI) { |
| writePS("/opiMatrix matrix currentmatrix def\n"); |
| } |
| #endif |
| } |
| if (customCodeCbk) { |
| if ((s = (*customCodeCbk)(this, psOutCustomDocSetup, 0, customCodeCbkData))) { |
| writePS(s->c_str()); |
| delete s; |
| } |
| } |
| } |
| |
| void PSOutputDev::writePageTrailer() |
| { |
| if (mode != psModeForm) { |
| writePS("pdfEndPage\n"); |
| } |
| } |
| |
| void PSOutputDev::writeTrailer() |
| { |
| PSOutCustomColor *cc; |
| |
| if (mode == psModeForm) { |
| writePS("/Foo exch /Form defineresource pop\n"); |
| } else { |
| writePS("end\n"); |
| writePS("%%DocumentSuppliedResources:\n"); |
| writePS(embFontList->c_str()); |
| if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) { |
| writePS("%%DocumentProcessColors:"); |
| if (processColors & psProcessCyan) { |
| writePS(" Cyan"); |
| } |
| if (processColors & psProcessMagenta) { |
| writePS(" Magenta"); |
| } |
| if (processColors & psProcessYellow) { |
| writePS(" Yellow"); |
| } |
| if (processColors & psProcessBlack) { |
| writePS(" Black"); |
| } |
| writePS("\n"); |
| writePS("%%DocumentCustomColors:"); |
| for (cc = customColors; cc; cc = cc->next) { |
| writePS(" "); |
| writePSString(cc->name->toStr()); |
| } |
| writePS("\n"); |
| writePS("%%CMYKCustomColor:\n"); |
| for (cc = customColors; cc; cc = cc->next) { |
| writePSFmt("%%+ {0:.4g} {1:.4g} {2:.4g} {3:.4g} ", cc->c, cc->m, cc->y, cc->k); |
| writePSString(cc->name->toStr()); |
| writePS("\n"); |
| } |
| } |
| } |
| } |
| |
| void PSOutputDev::setupResources(Dict *resDict) |
| { |
| bool skip; |
| |
| setupFonts(resDict); |
| setupImages(resDict); |
| setupForms(resDict); |
| |
| //----- recursively scan XObjects |
| Object xObjDict = resDict->lookup("XObject"); |
| if (xObjDict.isDict()) { |
| for (int i = 0; i < xObjDict.dictGetLength(); ++i) { |
| |
| // avoid infinite recursion on XObjects |
| skip = false; |
| const Object &xObjRef = xObjDict.dictGetValNF(i); |
| if (xObjRef.isRef()) { |
| Ref ref0 = xObjRef.getRef(); |
| if (resourceIDs.find(ref0.num) != resourceIDs.end()) { |
| skip = true; |
| } else { |
| resourceIDs.insert(ref0.num); |
| } |
| } |
| if (!skip) { |
| |
| // process the XObject's resource dictionary |
| Object xObj = xObjDict.dictGetVal(i); |
| if (xObj.isStream()) { |
| Ref resObjRef; |
| Object resObj = xObj.streamGetDict()->lookup("Resources", &resObjRef); |
| if (resObj.isDict()) { |
| if (resObjRef != Ref::INVALID()) { |
| const int numObj = resObjRef.num; |
| if (resourceIDs.find(numObj) != resourceIDs.end()) { |
| error(errSyntaxError, -1, "loop in Resources (numObj: {0:d})", numObj); |
| continue; |
| } |
| resourceIDs.insert(numObj); |
| } |
| setupResources(resObj.getDict()); |
| } |
| } |
| } |
| } |
| } |
| |
| //----- recursively scan Patterns |
| Object patDict = resDict->lookup("Pattern"); |
| if (patDict.isDict()) { |
| inType3Char = true; |
| for (int i = 0; i < patDict.dictGetLength(); ++i) { |
| |
| // avoid infinite recursion on Patterns |
| skip = false; |
| const Object &patRef = patDict.dictGetValNF(i); |
| if (patRef.isRef()) { |
| Ref ref0 = patRef.getRef(); |
| if (resourceIDs.find(ref0.num) != resourceIDs.end()) { |
| skip = true; |
| } else { |
| resourceIDs.insert(ref0.num); |
| } |
| } |
| if (!skip) { |
| |
| // process the Pattern's resource dictionary |
| Object pat = patDict.dictGetVal(i); |
| if (pat.isStream()) { |
| Ref resObjRef; |
| Object resObj = pat.streamGetDict()->lookup("Resources", &resObjRef); |
| if (resObj.isDict()) { |
| if (resObjRef != Ref::INVALID() && !resourceIDs.insert(resObjRef.num).second) { |
| error(errSyntaxWarning, -1, "PSOutputDev::setupResources: Circular resources found."); |
| continue; |
| } |
| setupResources(resObj.getDict()); |
| } |
| } |
| } |
| } |
| inType3Char = false; |
| } |
| } |
| |
| void PSOutputDev::setupFonts(Dict *resDict) |
| { |
| Ref fontDictRef; |
| const Object &fontDictObj = resDict->lookup("Font", &fontDictRef); |
| if (fontDictObj.isDict()) { |
| GfxFontDict gfxFontDict(xref, fontDictRef, fontDictObj.getDict()); |
| for (int i = 0; i < gfxFontDict.getNumFonts(); ++i) { |
| if (const std::shared_ptr<GfxFont> &font = gfxFontDict.getFont(i)) { |
| setupFont(font.get(), resDict); |
| } |
| } |
| } |
| } |
| |
| void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) |
| { |
| GooString *psName; |
| char buf[16]; |
| bool subst; |
| const char *charName; |
| double xs, ys; |
| int code; |
| double w1, w2; |
| int i, j; |
| |
| // check if font is already set up |
| for (Ref fontID : fontIDs) { |
| if (fontID == *font->getID()) { |
| return; |
| } |
| } |
| |
| fontIDs.push_back(*font->getID()); |
| |
| psName = nullptr; |
| xs = ys = 1; |
| subst = false; |
| |
| if (font->getType() == fontType3) { |
| psName = new GooString(GooString::format("T3_{0:d}_{1:d}", font->getID()->num, font->getID()->gen)); |
| setupType3Font(font, psName, parentResDict); |
| } else { |
| std::optional<GfxFontLoc> fontLoc = font->locateFont(xref, this); |
| if (fontLoc) { |
| switch (fontLoc->locType) { |
| case gfxFontLocEmbedded: |
| switch (fontLoc->fontType) { |
| case fontType1: |
| // this assumes that the PS font name matches the PDF font name |
| psName = font->getEmbeddedFontName() ? font->getEmbeddedFontName()->copy() : new GooString(); |
| setupEmbeddedType1Font(&fontLoc->embFontID, psName); |
| break; |
| case fontType1C: |
| psName = makePSFontName(font, &fontLoc->embFontID); |
| setupEmbeddedType1CFont(font, &fontLoc->embFontID, psName); |
| break; |
| case fontType1COT: |
| psName = makePSFontName(font, &fontLoc->embFontID); |
| setupEmbeddedOpenTypeT1CFont(font, &fontLoc->embFontID, psName, fontLoc->fontNum); |
| break; |
| case fontTrueType: |
| case fontTrueTypeOT: |
| psName = makePSFontName(font, font->getID()); |
| setupEmbeddedTrueTypeFont(font, &fontLoc->embFontID, psName, fontLoc->fontNum); |
| break; |
| case fontCIDType0C: |
| psName = makePSFontName(font, &fontLoc->embFontID); |
| setupEmbeddedCIDType0Font(font, &fontLoc->embFontID, psName); |
| break; |
| case fontCIDType2: |
| case fontCIDType2OT: |
| psName = makePSFontName(font, font->getID()); |
| //~ should check to see if font actually uses vertical mode |
| setupEmbeddedCIDTrueTypeFont(font, &fontLoc->embFontID, psName, true, fontLoc->fontNum); |
| break; |
| case fontCIDType0COT: |
| psName = makePSFontName(font, &fontLoc->embFontID); |
| setupEmbeddedOpenTypeCFFFont(font, &fontLoc->embFontID, psName, fontLoc->fontNum); |
| break; |
| default: |
| break; |
| } |
| break; |
| case gfxFontLocExternal: |
| //~ add cases for external 16-bit fonts |
| switch (fontLoc->fontType) { |
| case fontType1: |
| if (font->getEmbeddedFontName()) { |
| // this assumes that the PS font name matches the PDF font name |
| psName = font->getEmbeddedFontName()->copy(); |
| } else { |
| //~ this won't work -- the PS font name won't match |
| psName = makePSFontName(font, font->getID()); |
| } |
| setupExternalType1Font(fontLoc->path, psName); |
| break; |
| case fontTrueType: |
| case fontTrueTypeOT: |
| psName = makePSFontName(font, font->getID()); |
| setupExternalTrueTypeFont(font, fontLoc->path, psName, fontLoc->fontNum); |
| break; |
| case fontCIDType2: |
| case fontCIDType2OT: |
| psName = makePSFontName(font, font->getID()); |
| //~ should check to see if font actually uses vertical mode |
| setupExternalCIDTrueTypeFont(font, fontLoc->path, psName, true, fontLoc->fontNum); |
| break; |
| default: |
| break; |
| } |
| break; |
| case gfxFontLocResident: |
| psName = new GooString(fontLoc->path); |
| break; |
| } |
| } |
| |
| if (!psName) { |
| if (font->isCIDFont()) { |
| error(errSyntaxError, -1, "Couldn't find a font to substitute for '{0:s}' ('{1:s}' character collection)", font->getName() ? font->getName()->c_str() : "(unnamed)", |
| ((GfxCIDFont *)font)->getCollection() ? ((GfxCIDFont *)font)->getCollection()->c_str() : "(unknown)"); |
| if (font16EncLen >= font16EncSize) { |
| font16EncSize += 16; |
| font16Enc = (PSFont16Enc *)greallocn(font16Enc, font16EncSize, sizeof(PSFont16Enc)); |
| } |
| font16Enc[font16EncLen].fontID = *font->getID(); |
| font16Enc[font16EncLen].enc = nullptr; |
| ++font16EncLen; |
| } else { |
| error(errSyntaxError, -1, "Couldn't find a font to substitute for '{0:s}'", font->getName() ? font->getName()->c_str() : "(unnamed)"); |
| } |
| return; |
| } |
| |
| // scale substituted 8-bit fonts |
| if (fontLoc->locType == gfxFontLocResident && fontLoc->substIdx >= 0) { |
| subst = true; |
| for (code = 0; code < 256; ++code) { |
| if ((charName = ((Gfx8BitFont *)font)->getCharName(code)) && charName[0] == 'm' && charName[1] == '\0') { |
| break; |
| } |
| } |
| if (code < 256) { |
| w1 = ((Gfx8BitFont *)font)->getWidth(code); |
| } else { |
| w1 = 0; |
| } |
| w2 = psBase14SubstFonts[fontLoc->substIdx].mWidth; |
| xs = w1 / w2; |
| if (xs < 0.1) { |
| xs = 1; |
| } |
| } |
| } |
| |
| // generate PostScript code to set up the font |
| if (font->isCIDFont()) { |
| if (level == psLevel3 || level == psLevel3Sep) { |
| writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n", font->getID()->num, font->getID()->gen, psName, font->getWMode()); |
| } else { |
| writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16\n", font->getID()->num, font->getID()->gen, psName, font->getWMode()); |
| } |
| } else { |
| writePSFmt("/F{0:d}_{1:d} /{2:t} {3:.6g} {4:.6g}\n", font->getID()->num, font->getID()->gen, psName, xs, ys); |
| for (i = 0; i < 256; i += 8) { |
| writePS((char *)((i == 0) ? "[ " : " ")); |
| for (j = 0; j < 8; ++j) { |
| if (font->getType() == fontTrueType && !subst && !((Gfx8BitFont *)font)->getHasEncoding()) { |
| sprintf(buf, "c%02x", i + j); |
| charName = buf; |
| } else { |
| charName = ((Gfx8BitFont *)font)->getCharName(i + j); |
| } |
| writePS("/"); |
| writePSName(charName ? charName : (char *)".notdef"); |
| // the empty name is legal in PDF and PostScript, but PostScript |
| // uses a double-slash (//...) for "immediately evaluated names", |
| // so we need to add a space character here |
| if (charName && !charName[0]) { |
| writePS(" "); |
| } |
| } |
| writePS((i == 256 - 8) ? (char *)"]\n" : (char *)"\n"); |
| } |
| writePS("pdfMakeFont\n"); |
| } |
| |
| delete psName; |
| } |
| |
| void PSOutputDev::setupEmbeddedType1Font(Ref *id, GooString *psName) |
| { |
| static const char hexChar[17] = "0123456789abcdef"; |
| Dict *dict; |
| long length1, length2, length3, i; |
| int c; |
| int start[4]; |
| bool binMode; |
| bool writePadding = true; |
| |
| // check if font is already embedded |
| if (!fontNames.emplace(psName->toStr()).second) { |
| return; |
| } |
| |
| // get the font stream and info |
| Object obj1, obj2, obj3; |
| Object refObj(*id); |
| Object strObj = refObj.fetch(xref); |
| if (!strObj.isStream()) { |
| error(errSyntaxError, -1, "Embedded font file object is not a stream"); |
| goto err1; |
| } |
| if (!(dict = strObj.streamGetDict())) { |
| error(errSyntaxError, -1, "Embedded font stream is missing its dictionary"); |
| goto err1; |
| } |
| obj1 = dict->lookup("Length1"); |
| obj2 = dict->lookup("Length2"); |
| obj3 = dict->lookup("Length3"); |
| if (!obj1.isInt() || !obj2.isInt() || !obj3.isInt()) { |
| error(errSyntaxError, -1, "Missing length fields in embedded font stream dictionary"); |
| goto err1; |
| } |
| length1 = obj1.getInt(); |
| length2 = obj2.getInt(); |
| length3 = obj3.getInt(); |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| strObj.streamReset(); |
| if (strObj.streamGetChar() == 0x80 && strObj.streamGetChar() == 1) { |
| // PFB format |
| length1 = strObj.streamGetChar() | (strObj.streamGetChar() << 8) | (strObj.streamGetChar() << 16) | (strObj.streamGetChar() << 24); |
| } else { |
| strObj.streamReset(); |
| } |
| // copy ASCII portion of font |
| for (i = 0; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) { |
| writePSChar(c); |
| } |
| |
| // figure out if encrypted portion is binary or ASCII |
| binMode = false; |
| for (i = 0; i < 4; ++i) { |
| start[i] = strObj.streamGetChar(); |
| if (start[i] == EOF) { |
| error(errSyntaxError, -1, "Unexpected end of file in embedded font stream"); |
| goto err1; |
| } |
| if (!((start[i] >= '0' && start[i] <= '9') || (start[i] >= 'A' && start[i] <= 'F') || (start[i] >= 'a' && start[i] <= 'f'))) { |
| binMode = true; |
| } |
| } |
| |
| if (length2 == 0) { |
| // length2 == 0 is an error |
| // trying to solve it by just piping all |
| // the stream data |
| error(errSyntaxWarning, -1, "Font has length2 as 0, trying to overcome the problem reading the stream until the end"); |
| length2 = INT_MAX; |
| writePadding = false; |
| } |
| |
| // convert binary data to ASCII |
| if (binMode) { |
| if (start[0] == 0x80 && start[1] == 2) { |
| length2 = start[2] | (start[3] << 8) | (strObj.streamGetChar() << 16) | (strObj.streamGetChar() << 24); |
| i = 0; |
| } else { |
| for (i = 0; i < 4; ++i) { |
| writePSChar(hexChar[(start[i] >> 4) & 0x0f]); |
| writePSChar(hexChar[start[i] & 0x0f]); |
| } |
| } |
| #if 0 // this causes trouble for various PostScript printers |
| // if Length2 is incorrect (too small), font data gets chopped, so |
| // we take a few extra characters from the trailer just in case |
| length2 += length3 >= 8 ? 8 : length3; |
| #endif |
| while (i < length2) { |
| if ((c = strObj.streamGetChar()) == EOF) { |
| break; |
| } |
| writePSChar(hexChar[(c >> 4) & 0x0f]); |
| writePSChar(hexChar[c & 0x0f]); |
| if (++i % 32 == 0) { |
| writePSChar('\n'); |
| } |
| } |
| if (i % 32 > 0) { |
| writePSChar('\n'); |
| } |
| |
| // already in ASCII format -- just copy it |
| } else { |
| for (i = 0; i < 4; ++i) { |
| writePSChar(start[i]); |
| } |
| for (i = 4; i < length2; ++i) { |
| if ((c = strObj.streamGetChar()) == EOF) { |
| break; |
| } |
| writePSChar(c); |
| } |
| } |
| |
| if (writePadding) { |
| if (length3 > 0) { |
| // write fixed-content portion |
| c = strObj.streamGetChar(); |
| if (c == 0x80) { |
| c = strObj.streamGetChar(); |
| if (c == 1) { |
| length3 = strObj.streamGetChar() | (strObj.streamGetChar() << 8) | (strObj.streamGetChar() << 16) | (strObj.streamGetChar() << 24); |
| |
| i = 0; |
| while (i < length3) { |
| if ((c = strObj.streamGetChar()) == EOF) { |
| break; |
| } |
| writePSChar(c); |
| ++i; |
| } |
| } |
| } else { |
| if (c != EOF) { |
| writePSChar(c); |
| |
| while ((c = strObj.streamGetChar()) != EOF) { |
| writePSChar(c); |
| } |
| } |
| } |
| } else { |
| // write padding and "cleartomark" |
| for (i = 0; i < 8; ++i) { |
| writePS("00000000000000000000000000000000" |
| "00000000000000000000000000000000\n"); |
| } |
| writePS("cleartomark\n"); |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| |
| err1: |
| if (strObj.isStream()) { |
| strObj.streamClose(); |
| } |
| } |
| |
| void PSOutputDev::setupExternalType1Font(const std::string &fileName, GooString *psName) |
| { |
| static const char hexChar[17] = "0123456789abcdef"; |
| FILE *fontFile; |
| int c; |
| |
| if (!fontNames.emplace(psName->toStr()).second) { |
| return; |
| } |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // copy the font file |
| if (!(fontFile = openFile(fileName.c_str(), "rb"))) { |
| error(errIO, -1, "Couldn't open external font file"); |
| return; |
| } |
| |
| c = fgetc(fontFile); |
| if (c == 0x80) { |
| // PFB file |
| ungetc(c, fontFile); |
| while (!feof(fontFile)) { |
| fgetc(fontFile); // skip start of segment byte (0x80) |
| int segType = fgetc(fontFile); |
| long segLen = fgetc(fontFile) | (fgetc(fontFile) << 8) | (fgetc(fontFile) << 16) | (fgetc(fontFile) << 24); |
| if (feof(fontFile)) { |
| break; |
| } |
| |
| if (segType == 1) { |
| // ASCII segment |
| for (long i = 0; i < segLen; i++) { |
| c = fgetc(fontFile); |
| if (c == EOF) { |
| break; |
| } |
| writePSChar(c); |
| } |
| } else if (segType == 2) { |
| // binary segment |
| for (long i = 0; i < segLen; i++) { |
| c = fgetc(fontFile); |
| if (c == EOF) { |
| break; |
| } |
| writePSChar(hexChar[(c >> 4) & 0x0f]); |
| writePSChar(hexChar[c & 0x0f]); |
| if (i % 36 == 35) { |
| writePSChar('\n'); |
| } |
| } |
| } else { |
| // end of file |
| break; |
| } |
| } |
| } else if (c != EOF) { |
| writePSChar(c); |
| while ((c = fgetc(fontFile)) != EOF) { |
| writePSChar(c); |
| } |
| } |
| fclose(fontFile); |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id, GooString *psName) |
| { |
| FoFiType1C *ffT1C; |
| int i; |
| |
| // check if font is already embedded |
| for (i = 0; i < t1FontNameLen; ++i) { |
| if (t1FontNames[i].fontFileID == *id) { |
| psName->clear(); |
| psName->insert(0, t1FontNames[i].psName); |
| return; |
| } |
| } |
| if (t1FontNameLen == t1FontNameSize) { |
| t1FontNameSize *= 2; |
| t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, sizeof(PST1FontName)); |
| } |
| t1FontNames[t1FontNameLen].fontFileID = *id; |
| t1FontNames[t1FontNameLen].psName = psName->copy(); |
| ++t1FontNameLen; |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // convert it to a Type 1 font |
| const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
| if (fontBuf) { |
| if ((ffT1C = FoFiType1C::make(fontBuf->data(), fontBuf->size()))) { |
| ffT1C->convertToType1(psName->c_str(), nullptr, true, outputFunc, outputStream); |
| delete ffT1C; |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, GooString *psName, int faceIndex) |
| { |
| int i; |
| |
| // check if font is already embedded |
| for (i = 0; i < t1FontNameLen; ++i) { |
| if (t1FontNames[i].fontFileID == *id) { |
| psName->clear(); |
| psName->insert(0, t1FontNames[i].psName); |
| return; |
| } |
| } |
| if (t1FontNameLen == t1FontNameSize) { |
| t1FontNameSize *= 2; |
| t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, sizeof(PST1FontName)); |
| } |
| t1FontNames[t1FontNameLen].fontFileID = *id; |
| t1FontNames[t1FontNameLen].psName = psName->copy(); |
| ++t1FontNameLen; |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // convert it to a Type 1 font |
| const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
| if (fontBuf) { |
| if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::make(fontBuf->data(), fontBuf->size(), faceIndex)) { |
| if (ffTT->isOpenTypeCFF()) { |
| ffTT->convertToType1(psName->c_str(), nullptr, true, outputFunc, outputStream); |
| } |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id, GooString *psName, int faceIndex) |
| { |
| int *codeToGID; |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // convert it to a Type 42 font |
| const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
| if (fontBuf) { |
| if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::make(fontBuf->data(), fontBuf->size(), faceIndex)) { |
| codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT.get()); |
| ffTT->convertToType42(psName->c_str(), ((Gfx8BitFont *)font)->getHasEncoding() ? ((Gfx8BitFont *)font)->getEncoding() : nullptr, codeToGID, outputFunc, outputStream); |
| if (codeToGID) { |
| if (font8InfoLen >= font8InfoSize) { |
| font8InfoSize += 16; |
| font8Info = (PSFont8Info *)greallocn(font8Info, font8InfoSize, sizeof(PSFont8Info)); |
| } |
| font8Info[font8InfoLen].fontID = *font->getID(); |
| font8Info[font8InfoLen].codeToGID = codeToGID; |
| ++font8InfoLen; |
| } |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, const std::string &fileName, GooString *psName, int faceIndex) |
| { |
| int *codeToGID; |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // convert it to a Type 42 font |
| if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::load(fileName.c_str(), faceIndex)) { |
| codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT.get()); |
| ffTT->convertToType42(psName->c_str(), ((Gfx8BitFont *)font)->getHasEncoding() ? ((Gfx8BitFont *)font)->getEncoding() : nullptr, codeToGID, outputFunc, outputStream); |
| if (codeToGID) { |
| if (font8InfoLen >= font8InfoSize) { |
| font8InfoSize += 16; |
| font8Info = (PSFont8Info *)greallocn(font8Info, font8InfoSize, sizeof(PSFont8Info)); |
| } |
| font8Info[font8InfoLen].fontID = *font->getID(); |
| font8Info[font8InfoLen].codeToGID = codeToGID; |
| ++font8InfoLen; |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::updateFontMaxValidGlyph(GfxFont *font, int maxValidGlyph) |
| { |
| if (maxValidGlyph >= 0 && font->getName()) { |
| auto &fontMaxValidGlyph = perFontMaxValidGlyph[*font->getName()]; |
| if (fontMaxValidGlyph < maxValidGlyph) { |
| fontMaxValidGlyph = maxValidGlyph; |
| } |
| } |
| } |
| |
| void PSOutputDev::setupExternalCIDTrueTypeFont(GfxFont *font, const std::string &fileName, GooString *psName, bool needVerticalMetrics, int faceIndex) |
| { |
| int *codeToGID; |
| int codeToGIDLen; |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // convert it to a Type 0 font |
| //~ this should use fontNum to load the correct font |
| if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::load(fileName.c_str(), faceIndex)) { |
| |
| // check for embedding permission |
| if (ffTT->getEmbeddingRights() >= 1) { |
| codeToGID = nullptr; |
| codeToGIDLen = 0; |
| if (((GfxCIDFont *)font)->getCIDToGID()) { |
| codeToGIDLen = ((GfxCIDFont *)font)->getCIDToGIDLen(); |
| if (codeToGIDLen) { |
| codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); |
| memcpy(codeToGID, ((GfxCIDFont *)font)->getCIDToGID(), codeToGIDLen * sizeof(int)); |
| } |
| } else { |
| codeToGID = ((GfxCIDFont *)font)->getCodeToGIDMap(ffTT.get(), &codeToGIDLen); |
| } |
| if (ffTT->isOpenTypeCFF()) { |
| ffTT->convertToCIDType0(psName->c_str(), codeToGID, codeToGIDLen, outputFunc, outputStream); |
| } else if (level >= psLevel3) { |
| // Level 3: use a CID font |
| ffTT->convertToCIDType2(psName->c_str(), codeToGID, codeToGIDLen, needVerticalMetrics, outputFunc, outputStream); |
| } else { |
| // otherwise: use a non-CID composite font |
| int maxValidGlyph = -1; |
| ffTT->convertToType0(psName->c_str(), codeToGID, codeToGIDLen, needVerticalMetrics, &maxValidGlyph, outputFunc, outputStream); |
| updateFontMaxValidGlyph(font, maxValidGlyph); |
| } |
| gfree(codeToGID); |
| } else { |
| error(errSyntaxError, -1, "TrueType font '{0:s}' does not allow embedding", font->getName() ? font->getName()->c_str() : "(unnamed)"); |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id, GooString *psName) |
| { |
| FoFiType1C *ffT1C; |
| int i; |
| |
| // check if font is already embedded |
| for (i = 0; i < t1FontNameLen; ++i) { |
| if (t1FontNames[i].fontFileID == *id) { |
| psName->clear(); |
| psName->insert(0, t1FontNames[i].psName); |
| return; |
| } |
| } |
| if (t1FontNameLen == t1FontNameSize) { |
| t1FontNameSize *= 2; |
| t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, sizeof(PST1FontName)); |
| } |
| t1FontNames[t1FontNameLen].fontFileID = *id; |
| t1FontNames[t1FontNameLen].psName = psName->copy(); |
| ++t1FontNameLen; |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // convert it to a Type 0 font |
| const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
| if (fontBuf) { |
| if ((ffT1C = FoFiType1C::make(fontBuf->data(), fontBuf->size()))) { |
| if (level >= psLevel3) { |
| // Level 3: use a CID font |
| ffT1C->convertToCIDType0(psName->c_str(), nullptr, 0, outputFunc, outputStream); |
| } else { |
| // otherwise: use a non-CID composite font |
| ffT1C->convertToType0(psName->c_str(), nullptr, 0, outputFunc, outputStream); |
| } |
| delete ffT1C; |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, GooString *psName, bool needVerticalMetrics, int faceIndex) |
| { |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // convert it to a Type 0 font |
| const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
| if (fontBuf) { |
| if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::make(fontBuf->data(), fontBuf->size(), faceIndex)) { |
| if (level >= psLevel3) { |
| // Level 3: use a CID font |
| ffTT->convertToCIDType2(psName->c_str(), ((GfxCIDFont *)font)->getCIDToGID(), ((GfxCIDFont *)font)->getCIDToGIDLen(), needVerticalMetrics, outputFunc, outputStream); |
| } else { |
| // otherwise: use a non-CID composite font |
| int maxValidGlyph = -1; |
| ffTT->convertToType0(psName->c_str(), ((GfxCIDFont *)font)->getCIDToGID(), ((GfxCIDFont *)font)->getCIDToGIDLen(), needVerticalMetrics, &maxValidGlyph, outputFunc, outputStream); |
| updateFontMaxValidGlyph(font, maxValidGlyph); |
| } |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, GooString *psName, int faceIndex) |
| { |
| int i; |
| |
| // check if font is already embedded |
| for (i = 0; i < t1FontNameLen; ++i) { |
| if (t1FontNames[i].fontFileID == *id) { |
| psName->clear(); |
| psName->insert(0, t1FontNames[i].psName); |
| return; |
| } |
| } |
| if (t1FontNameLen == t1FontNameSize) { |
| t1FontNameSize *= 2; |
| t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, sizeof(PST1FontName)); |
| } |
| t1FontNames[t1FontNameLen].fontFileID = *id; |
| t1FontNames[t1FontNameLen].psName = psName->copy(); |
| ++t1FontNameLen; |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // convert it to a Type 0 font |
| const std::optional<std::vector<unsigned char>> fontBuf = font->readEmbFontFile(xref); |
| if (fontBuf) { |
| if (std::unique_ptr<FoFiTrueType> ffTT = FoFiTrueType::make(fontBuf->data(), fontBuf->size(), faceIndex)) { |
| if (ffTT->isOpenTypeCFF()) { |
| if (level >= psLevel3) { |
| // Level 3: use a CID font |
| ffTT->convertToCIDType0(psName->c_str(), ((GfxCIDFont *)font)->getCIDToGID(), ((GfxCIDFont *)font)->getCIDToGIDLen(), outputFunc, outputStream); |
| } else { |
| // otherwise: use a non-CID composite font |
| ffTT->convertToType0(psName->c_str(), ((GfxCIDFont *)font)->getCIDToGID(), ((GfxCIDFont *)font)->getCIDToGIDLen(), outputFunc, outputStream); |
| } |
| } |
| } |
| } |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| void PSOutputDev::setupType3Font(GfxFont *font, GooString *psName, Dict *parentResDict) |
| { |
| Dict *resDict; |
| Dict *charProcs; |
| Gfx *gfx; |
| PDFRectangle box; |
| const double *m; |
| int i; |
| |
| // set up resources used by font |
| if ((resDict = ((Gfx8BitFont *)font)->getResources())) { |
| inType3Char = true; |
| setupResources(resDict); |
| inType3Char = false; |
| } else { |
| resDict = parentResDict; |
| } |
| |
| // beginning comment |
| writePSFmt("%%BeginResource: font {0:t}\n", psName); |
| embFontList->append("%%+ font "); |
| embFontList->append(psName->c_str()); |
| embFontList->append("\n"); |
| |
| // font dictionary |
| writePS("8 dict begin\n"); |
| writePS("/FontType 3 def\n"); |
| m = font->getFontMatrix(); |
| writePSFmt("/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]); |
| m = font->getFontBBox(); |
| writePSFmt("/FontBBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}] def\n", m[0], m[1], m[2], m[3]); |
| writePS("/Encoding 256 array def\n"); |
| writePS(" 0 1 255 { Encoding exch /.notdef put } for\n"); |
| writePS("/BuildGlyph {\n"); |
| writePS(" exch /CharProcs get exch\n"); |
| writePS(" 2 copy known not { pop /.notdef } if\n"); |
| writePS(" get exec\n"); |
| writePS("} bind def\n"); |
| writePS("/BuildChar {\n"); |
| writePS(" 1 index /Encoding get exch get\n"); |
| writePS(" 1 index /BuildGlyph get exec\n"); |
| writePS("} bind def\n"); |
| if ((charProcs = ((Gfx8BitFont *)font)->getCharProcs())) { |
| writePSFmt("/CharProcs {0:d} dict def\n", charProcs->getLength()); |
| writePS("CharProcs begin\n"); |
| box.x1 = m[0]; |
| box.y1 = m[1]; |
| box.x2 = m[2]; |
| box.y2 = m[3]; |
| gfx = new Gfx(doc, this, resDict, &box, nullptr); |
| inType3Char = true; |
| for (i = 0; i < charProcs->getLength(); ++i) { |
| t3FillColorOnly = false; |
| t3Cacheable = false; |
| t3NeedsRestore = false; |
| writePS("/"); |
| writePSName(charProcs->getKey(i)); |
| writePS(" {\n"); |
| Object charProc = charProcs->getVal(i); |
| gfx->display(&charProc); |
| if (t3String) { |
| std::string buf; |
| if (t3Cacheable) { |
| buf = GooString::format("{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g} setcachedevice\n", t3WX, t3WY, t3LLX, t3LLY, t3URX, t3URY); |
| } else { |
| buf = GooString::format("{0:.6g} {1:.6g} setcharwidth\n", t3WX, t3WY); |
| } |
| (*outputFunc)(outputStream, buf.c_str(), buf.size()); |
| (*outputFunc)(outputStream, t3String->c_str(), t3String->getLength()); |
| delete t3String; |
| t3String = nullptr; |
| } |
| if (t3NeedsRestore) { |
| (*outputFunc)(outputStream, "Q\n", 2); |
| } |
| writePS("} def\n"); |
| } |
| inType3Char = false; |
| delete gfx; |
| writePS("end\n"); |
| } |
| writePS("currentdict end\n"); |
| writePSFmt("/{0:t} exch definefont pop\n", psName); |
| |
| // ending comment |
| writePS("%%EndResource\n"); |
| } |
| |
| // Make a unique PS font name, based on the names given in the PDF |
| // font object, and an object ID (font file object for |
| GooString *PSOutputDev::makePSFontName(GfxFont *font, const Ref *id) |
| { |
| const GooString *s; |
| |
| if ((s = font->getEmbeddedFontName())) { |
| std::string psName = filterPSName(s->toStr()); |
| if (fontNames.emplace(psName).second) { |
| return new GooString(std::move(psName)); |
| } |
| } |
| if (font->getName()) { |
| std::string psName = filterPSName(*font->getName()); |
| if (fontNames.emplace(psName).second) { |
| return new GooString(std::move(psName)); |
| } |
| } |
| GooString *psName = new GooString(GooString::format("FF{0:d}_{1:d}", id->num, id->gen)); |
| if ((s = font->getEmbeddedFontName())) { |
| std::string filteredName = filterPSName(s->toStr()); |
| psName->append('_')->append(filteredName); |
| } else if (font->getName()) { |
| std::string filteredName = filterPSName(*font->getName()); |
| psName->append('_')->append(filteredName); |
| } |
| fontNames.emplace(psName->toStr()); |
| return psName; |
| } |
| |
| void PSOutputDev::setupImages(Dict *resDict) |
| { |
| Ref imgID; |
| |
| if (!(mode == psModeForm || inType3Char || preloadImagesForms)) { |
| return; |
| } |
| |
| //----- recursively scan XObjects |
| Object xObjDict = resDict->lookup("XObject"); |
| if (xObjDict.isDict()) { |
| for (int i = 0; i < xObjDict.dictGetLength(); ++i) { |
| const Object &xObjRef = xObjDict.dictGetValNF(i); |
| Object xObj = xObjDict.dictGetVal(i); |
| if (xObj.isStream()) { |
| Object subtypeObj = xObj.streamGetDict()->lookup("Subtype"); |
| if (subtypeObj.isName("Image")) { |
| if (xObjRef.isRef()) { |
| imgID = xObjRef.getRef(); |
| int j; |
| for (j = 0; j < imgIDLen; ++j) { |
| if (imgIDs[j] == imgID) { |
| break; |
| } |
| } |
| if (j == imgIDLen) { |
| if (imgIDLen >= imgIDSize) { |
| if (imgIDSize == 0) { |
| imgIDSize = 64; |
| } else { |
|