blob: 9c51b6f004b7a0bac5c6fa953cad40eecab95b69 [file] [log] [blame]
//========================================================================
//
// 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-2019 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 William Bader <williambader@hotmail.com>
// Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net>
// Copyright (C) 2009-2011, 2013-2015, 2017 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 Christian Persch <chpe@src.gnome.org>
//
// 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 <stdio.h>
#include <stddef.h>
#include <stdarg.h>
#include <signal.h>
#include <math.h>
#include <limits.h>
#include <algorithm>
#include <array>
#include "goo/GooString.h"
#include "goo/GooList.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"
#ifdef ENABLE_ZLIB
# include "FlateEncoder.h"
#endif
#ifdef ENABLE_ZLIB_UNCOMPRESS
# include "FlateStream.h"
#endif
#include "Annot.h"
#include "XRef.h"
#include "PreScanOutputDev.h"
#include "FileSpec.h"
#include "CharCodeToUnicode.h"
#ifdef HAVE_SPLASH
# include "splash/Splash.h"
# include "splash/SplashBitmap.h"
# include "SplashOutputDev.h"
#endif
#include "PSOutputDev.h"
#include "PDFDoc.h"
// 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 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;
};
//------------------------------------------------------------------------
struct PSOutPaperSize {
PSOutPaperSize(GooString *nameA, int wA, int hA) { name = nameA; w = wA; h = hA; }
~PSOutPaperSize() { delete name; }
PSOutPaperSize(const PSOutPaperSize &) = delete;
PSOutPaperSize& operator=(const PSOutPaperSize &) = delete;
GooString *name;
int w, h;
};
//------------------------------------------------------------------------
// DeviceNRecoder
//------------------------------------------------------------------------
class DeviceNRecoder: public FilterStream {
public:
DeviceNRecoder(Stream *strA, int widthA, int heightA,
GfxImageColorMap *colorMapA);
~DeviceNRecoder();
StreamKind getKind() 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) override { return true; }
bool isEncoder() override { return true; }
private:
bool fillBuf();
int width, height;
GfxImageColorMap *colorMap;
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, int len) {
fwrite(data, 1, len, (FILE *)stream);
}
PSOutputDev::PSOutputDev(const char *fileName, PDFDoc *doc,
char *psTitleA,
const std::vector<int> &pages, PSOutMode modeA,
int paperWidthA, int paperHeightA,
bool noCropA, bool duplexA,
int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
bool forceRasterizeA,
bool manualCtrlA,
PSOutCustomCodeCbk customCodeCbkA,
void *customCodeCbkDataA) {
FILE *f;
PSFileType fileTypeA;
underlayCbk = nullptr;
underlayCbkData = nullptr;
overlayCbk = nullptr;
overlayCbkData = nullptr;
customCodeCbk = customCodeCbkA;
customCodeCbkData = customCodeCbkDataA;
fontIDs = nullptr;
t1FontNames = nullptr;
font8Info = nullptr;
font16Enc = nullptr;
imgIDs = nullptr;
formIDs = nullptr;
paperSizes = 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,
doc, pages, modeA,
imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA,
paperWidthA, paperHeightA, noCropA, duplexA);
}
PSOutputDev::PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA,
char *psTitleA,
PDFDoc *doc,
const std::vector<int> &pages, PSOutMode modeA,
int paperWidthA, int paperHeightA,
bool noCropA, bool duplexA,
int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
bool forceRasterizeA,
bool manualCtrlA,
PSOutCustomCodeCbk customCodeCbkA,
void *customCodeCbkDataA) {
underlayCbk = nullptr;
underlayCbkData = nullptr;
overlayCbk = nullptr;
overlayCbkData = nullptr;
customCodeCbk = customCodeCbkA;
customCodeCbkData = customCodeCbkDataA;
fontIDs = nullptr;
t1FontNames = nullptr;
font8Info = nullptr;
font16Enc = nullptr;
imgIDs = nullptr;
formIDs = nullptr;
paperSizes = nullptr;
embFontList = nullptr;
customColors = nullptr;
haveTextClip = false;
t3String = nullptr;
forceRasterize = forceRasterizeA;
psTitle = nullptr;
init(outputFuncA, outputStreamA, psGeneric, psTitleA,
doc, pages, modeA,
imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA,
paperWidthA, paperHeightA, noCropA, duplexA);
}
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) {
return (abs (a - b) < 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(PSOutputFunc 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) {
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;
rasterMono = false;
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 = globalParams->getPSLevel();
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;
// 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;
PSOutPaperSize *size;
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;
}
Page *page;
paperSizes = new GooList<PSOutPaperSize*>();
for (size_t pgi = 0; pgi < pages.size(); ++pgi) {
const int pg = pages[pgi];
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) {
int rotate = page->getRotate();
if (rotate == 90 || rotate == 270)
std::swap(w, h);
}
if (w > paperWidth)
paperWidth = w;
if (h > paperHeight)
paperHeight = h;
for (i = 0; i < (int)paperSizes->size(); ++i) {
size = (PSOutPaperSize *)paperSizes->get(i);
if (pageDimensionEqual(w, size->w) && pageDimensionEqual(h, size->h))
break;
}
if (i == (int)paperSizes->size()) {
const StandardMedia *media = standardMedia;
GooString *name = nullptr;
while (media->name) {
if (pageDimensionEqual(w, media->width) && pageDimensionEqual(h, media->height)) {
name = new GooString(media->name);
w = media->width;
h = media->height;
break;
}
media++;
}
if (!name)
name = GooString::format("{0:d}x{1:d}mm", int(w*25.4/72), int(h*25.4/72));
paperSizes->push_back(new PSOutPaperSize(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
fontIDSize = 64;
fontIDLen = 0;
fontIDs = (Ref *)gmallocn(fontIDSize, sizeof(Ref));
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;
// 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,
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, box, box, 0, psTitle);
delete box;
}
if (mode != psModeForm) {
writePS("%%BeginProlog\n");
}
writeXpdfProcset();
if (mode != psModeForm) {
writePS("%%EndProlog\n");
writePS("%%BeginSetup\n");
}
writeDocSetup(doc, 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 (paperSizes) {
deleteGooList<PSOutPaperSize*>(paperSizes);
}
if (embFontList) {
delete embFontList;
}
if (fontIDs) {
gfree(fontIDs);
}
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);
}
void PSOutputDev::writeHeader(const std::vector<int> &pages,
const PDFRectangle *mediaBox, const PDFRectangle *cropBox,
int pageRotate, char *psTitle) {
PSOutPaperSize *size;
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;
}
writePSFmt("%Produced by poppler pdftops version: {0:s} (http://poppler.freedesktop.org)\n", PACKAGE_VERSION);
Object info = xref->getDocInfo();
if (info.isDict()) {
Object obj1 = info.dictLookup("Creator");
if (obj1.isString()) {
writePS("%%Creator: ");
writePSTextLine(obj1.getString());
}
}
if(psTitle) {
char *sanitizedTitle = strdup(psTitle);
for (unsigned int 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) {
size = (PSOutPaperSize *)paperSizes->get(i);
writePSFmt("%%{0:s} {1:t} {2:d} {3:d} 0 () ()\n",
i==0 ? "DocumentMedia:" : "+", size->name, size->w, size->h);
}
writePSFmt("%%BoundingBox: 0 0 {0:d} {1:d}\n", paperWidth, paperHeight);
writePSFmt("%%Pages: {0:d}\n", static_cast<int>(pages.size()));
writePS("%%EndComments\n");
if (!paperMatch) {
size = (PSOutPaperSize *)paperSizes->get(0);
writePS("%%BeginDefaults\n");
writePSFmt("%%PageMedia: {0:t}\n", size->name);
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(PDFDoc *doc, Catalog *catalog,
const std::vector<int> &pages,
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 (size_t pgi = 0; pgi < pages.size(); ++pgi) {
const int pg = pages[pgi];
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 (int i = 0; i < annots->getNumAnnots(); ++i) {
Object obj1 = annots->getAnnot(i)->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);
}
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);
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()) {
Object resObj = xObj.streamGetDict()->lookup("Resources");
if (resObj.isDict()) {
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()) {
Object resObj = pat.streamGetDict()->lookup("Resources");
if (resObj.isDict()) {
setupResources(resObj.getDict());
}
}
}
}
inType3Char = false;
}
}
void PSOutputDev::setupFonts(Dict *resDict) {
Ref r;
GfxFontDict *gfxFontDict;
GfxFont *font;
int i;
gfxFontDict = nullptr;
const Object &obj1 = resDict->lookupNF("Font");
if (obj1.isRef()) {
Object obj2 = obj1.fetch(xref);
if (obj2.isDict()) {
r = obj1.getRef();
gfxFontDict = new GfxFontDict(xref, &r, obj2.getDict());
}
} else if (obj1.isDict()) {
gfxFontDict = new GfxFontDict(xref, nullptr, obj1.getDict());
}
if (gfxFontDict) {
for (i = 0; i < gfxFontDict->getNumFonts(); ++i) {
if ((font = gfxFontDict->getFont(i))) {
setupFont(font, resDict);
}
}
delete gfxFontDict;
}
}
void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) {
GfxFontLoc *fontLoc;
GooString *psName;
char buf[16];
bool subst;
UnicodeMap *uMap;
const char *charName;
double xs, ys;
int code;
double w1, w2;
int i, j;
// check if font is already set up
for (i = 0; i < fontIDLen; ++i) {
if (fontIDs[i].num == font->getID()->num &&
fontIDs[i].gen == font->getID()->gen) {
return;
}
}
// add entry to fontIDs list
if (fontIDLen >= fontIDSize) {
fontIDSize += 64;
fontIDs = (Ref *)greallocn(fontIDs, fontIDSize, sizeof(Ref));
}
fontIDs[fontIDLen++] = *font->getID();
psName = nullptr;
xs = ys = 1;
subst = false;
if (font->getType() == fontType3) {
psName = GooString::format("T3_{0:d}_{1:d}",
font->getID()->num, font->getID()->gen);
setupType3Font(font, psName, parentResDict);
} else {
fontLoc = font->locateFont(xref, this);
if (fontLoc != nullptr) {
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);
break;
case fontTrueType:
case fontTrueTypeOT:
psName = makePSFontName(font, font->getID());
setupEmbeddedTrueTypeFont(font, &fontLoc->embFontID, psName);
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);
break;
case fontCIDType0COT:
psName = makePSFontName(font, &fontLoc->embFontID);
setupEmbeddedOpenTypeCFFFont(font, &fontLoc->embFontID, psName);
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);
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);
break;
default:
break;
}
break;
case gfxFontLocResident:
psName = fontLoc->path->copy();
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)");
}
delete fontLoc;
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;
}
}
// handle encodings for substituted CID fonts
if (fontLoc->locType == gfxFontLocResident &&
fontLoc->fontType >= fontCIDType0) {
subst = true;
if (font16EncLen >= font16EncSize) {
font16EncSize += 16;
font16Enc = (PSFont16Enc *)greallocn(font16Enc,
font16EncSize,
sizeof(PSFont16Enc));
}
font16Enc[font16EncLen].fontID = *font->getID();
if ((uMap = globalParams->getUnicodeMap(fontLoc->encoding))) {
font16Enc[font16EncLen].enc = fontLoc->encoding->copy();
uMap->decRefCnt();
} else {
error(errSyntaxError, -1,
"Couldn't find Unicode map for 16-bit font encoding '{0:t}'",
fontLoc->encoding);
font16Enc[font16EncLen].enc = nullptr;
}
++font16EncLen;
}
delete fontLoc;
}
// 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;
int c, i;
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(GooString *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) {
char *fontBuf;
int fontLen;
FoFiType1C *ffT1C;
int i;
// check if font is already embedded
for (i = 0; i < t1FontNameLen; ++i) {
if (t1FontNames[i].fontFileID.num == id->num &&
t1FontNames[i].fontFileID.gen == id->gen) {
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
if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) {
if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
ffT1C->convertToType1(psName->c_str(), nullptr, true,
outputFunc, outputStream);
delete ffT1C;
}
gfree(fontBuf);
}
// ending comment
writePS("%%EndResource\n");
}
void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id,
GooString *psName) {
char *fontBuf;
int fontLen;
FoFiTrueType *ffTT;
int i;
// check if font is already embedded
for (i = 0; i < t1FontNameLen; ++i) {
if (t1FontNames[i].fontFileID.num == id->num &&
t1FontNames[i].fontFileID.gen == id->gen) {
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
if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) {
if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
if (ffTT->isOpenTypeCFF()) {
ffTT->convertToType1(psName->c_str(), nullptr, true,
outputFunc, outputStream);
}
delete ffTT;
}
gfree(fontBuf);
}
// ending comment
writePS("%%EndResource\n");
}
void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id,
GooString *psName) {
char *fontBuf;
int fontLen;
FoFiTrueType *ffTT;
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 ((fontBuf = font->readEmbFontFile(xref, &fontLen))) {
if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT);
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;
}
delete ffTT;
}
gfree(fontBuf);
}
// ending comment
writePS("%%EndResource\n");
}
void PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, GooString *fileName,
GooString *psName) {
FoFiTrueType *ffTT;
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 ((ffTT = FoFiTrueType::load(fileName->c_str()))) {
codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT);
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;
}
delete ffTT;
}
// ending comment
writePS("%%EndResource\n");
}
void PSOutputDev::updateFontMaxValidGlyph(GfxFont *font, int maxValidGlyph) {
if (maxValidGlyph >= 0 && font->getName()) {
auto& fontMaxValidGlyph = this->fontMaxValidGlyph[font->getName()->toStr()];
if (fontMaxValidGlyph < maxValidGlyph) {
fontMaxValidGlyph = maxValidGlyph;
}
}
}
void PSOutputDev::setupExternalCIDTrueTypeFont(GfxFont *font,
GooString *fileName,
GooString *psName,
bool needVerticalMetrics) {
FoFiTrueType *ffTT;
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 ((ffTT = FoFiTrueType::load(fileName->c_str()))) {
// 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, &codeToGIDLen);
}
if (ffTT->isOpenTypeCFF()) {
ffTT->convertToCIDType0(psName->c_str(),
codeToGID, codeToGIDLen,
outputFunc, outputStream);
} else if (globalParams->getPSLevel() >= 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)");
}
delete ffTT;
}
// ending comment
writePS("%%EndResource\n");
}
void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id,
GooString *psName) {
char *fontBuf;
int fontLen;
FoFiType1C *ffT1C;
int i;
// check if font is already embedded
for (i = 0; i < t1FontNameLen; ++i) {
if (t1FontNames[i].fontFileID.num == id->num &&
t1FontNames[i].fontFileID.gen == id->gen) {
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
if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) {
if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
if (globalParams->getPSLevel() >= 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;
}
gfree(fontBuf);
}
// ending comment
writePS("%%EndResource\n");
}
void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id,
GooString *psName,
bool needVerticalMetrics) {
char *fontBuf;
int fontLen;
FoFiTrueType *ffTT;
// 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
if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) {
if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
if (globalParams->getPSLevel() >= 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);
}
delete ffTT;
}
gfree(fontBuf);
}
// ending comment
writePS("%%EndResource\n");
}
void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id,
GooString *psName) {
char *fontBuf;
int fontLen;
FoFiTrueType *ffTT;
int i;
// check if font is already embedded
for (i = 0; i < t1FontNameLen; ++i) {
if (t1FontNames[i].fontFileID.num == id->num &&
t1FontNames[i].fontFileID.gen == id->gen) {
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
if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) {
if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
if (ffTT->isOpenTypeCFF()) {
if (globalParams->getPSLevel() >= 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);
}
}
delete ffTT;
}
gfree(fontBuf);
}
// 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;
GooString *buf;
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) {
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->getLength());
delete buf;
(*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) {
GooString *psName;
const GooString *s;
if ((s = font->getEmbeddedFontName())) {
psName = filterPSName(s);
if (fontNames.emplace(psName->toStr()).second) {
return psName;
}
delete psName;
}
if ((s = font->getName())) {
psName = filterPSName(s);
if (fontNames.emplace(psName->toStr()).second) {
return psName;
}
delete psName;
}
psName = GooString::format("FF{0:d}_{1:d}", id->num, id->gen);
if ((s = font->getEmbeddedFontName())) {
s = filterPSName(s);
psName->append('_')->append(s);
delete s;
} else if ((s = font->getName())) {
s = filterPSName(s);
psName->append('_')->append(s);
delete s;
}
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].num == imgID.num && imgIDs[j].gen == imgID.gen) {
break;
}
}
if (j == imgIDLen) {
if (imgIDLen >= imgIDSize) {
if (imgIDSize == 0) {
imgIDSize = 64;
} else {
imgIDSize *= 2;
}
imgIDs = (Ref *)greallocn(imgIDs, imgIDSize, sizeof(Ref));
}
imgIDs[imgIDLen++] = imgID;
setupImage(imgID, xObj.getStream(), false);
if (level >= psLevel3) {
Object maskObj = xObj.streamGetDict()->lookup("Mask");
if (maskObj.isStream()) {
setupImage(imgID, maskObj.getStream(), true);
}
}
}
} else {
error(errSyntaxError, -1,
"Image in resource dict is not an indirect reference");
}
}
}
}
}
}
void PSOutputDev::setupImage(Ref id, Stream *str, bool mask) {
bool useFlate, useLZW, useRLE, useCompressed, doUseASCIIHex;
GooString *s;
int c;
int size, line, col, i;
int outerSize, outer;
// filters
//~ this does not correctly handle the DeviceN color space
//~ -- need to use DeviceNRecoder
useFlate = useLZW = useRLE = false;
useCompressed = false;
doUseASCIIHex = false;
if (level < psLevel2) {
doUseASCIIHex = true;
} else {
if (uncompressPreloadedImages) {
/* nothing to do */;
} else {
s = str->getPSFilter(level < psLevel3 ? 2 : 3, "");
if (s) {
useCompressed = true;
delete s;
} else {
if (level >= psLevel3 && getEnableFlate()) {
useFlate = true;
} else if (getEnableLZW()) {
useLZW = true;
} else {
useRLE = true;
}
}
}
doUseASCIIHex = useASCIIHex;
}
if (useCompressed) {
str = str->getUndecodedStream();
}
#ifdef ENABLE_ZLIB
if (useFlate) {
str = new FlateEncoder(str);
} else
#endif
if (useLZW) {
str = new LZWEncoder(str);
} else if (useRLE) {
str = new RunLengthEncoder(str);
}
if (doUseASCIIHex) {
str = new ASCIIHexEncoder(str);
} else {
str = new ASCII85Encoder(str);
}
// compute image data size
str->reset();
col = size = 0;
do {
do {
c = str->getChar();
} while (c == '\n' || c == '\r');
if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) {
break;
}
if (c == 'z') {
++col;
} else {
++col;
for (i = 1; i <= (doUseASCIIHex ? 1 : 4); ++i) {
do {
c = str->getChar();
} while (c == '\n' || c == '\r');
if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) {
break;
}
++col;
}
if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) {
break;
}
}
if (col > 225) {
++size;
col = 0;
}
} while (c != (doUseASCIIHex ? '>' : '~') && c != EOF);
// add one entry for the final line of data; add another entry
// because the LZWDecode/RunLengthDecode filter may read past the end
++size;
if (useLZW || useRLE) {
++size;
}
outerSize = size/65535 + 1;
writePSFmt("{0:d} array dup /{1:s}Data_{2:d}_{3:d} exch def\n",
outerSize, mask ? "Mask" : "Im", id.num, id.gen);
str->close();
// write the data into the array
str->reset();
for (outer = 0;outer < outerSize;outer++) {
int innerSize = size > 65535 ? 65535 : size;
// put the inner array into the outer array
writePSFmt("{0:d} array 1 index {1:d} 2 index put\n",
innerSize, outer);
line = col = 0;
writePS((char *)(doUseASCIIHex ? "dup 0 <" : "dup 0 <~"));
for (;;) {
do {
c = str->getChar();
} while (c == '\n' || c == '\r');
if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) {
break;
}
if (c == 'z') {
writePSChar(c);
++col;
} else {
writePSChar(c);
++col;
for (i = 1; i <= (doUseASCIIHex ? 1 : 4); ++i) {
do {
c = str->getChar();
} while (c == '\n' || c == '\r');
if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) {
break;
}
writePSChar(c);
++col;
}
}
if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) {
break;
}
// each line is: "dup nnnnn <~...data...~> put<eol>"
// so max data length = 255 - 20 = 235
// chunks are 1 or 4 bytes each, so we have to stop at 232
// but make it 225 just to be safe
if (col > 225) {
writePS((char *)(doUseASCIIHex ? "> put\n" : "~> put\n"));
++line;
if (line >= innerSize) break;
writePSFmt((char *)(doUseASCIIHex ? "dup {0:d} <" : "dup {0:d} <~"), line);
col = 0;
}
}
if (c == (doUseASCIIHex ? '>' : '~') || c == EOF) {
writePS((char *)(doUseASCIIHex ? "> put\n" : "~> put\n"));
if (useLZW || useRLE) {
++line;
writePSFmt("{0:d} <> put\n", line);
} else {
writePS("pop\n");
}
break;
}
writePS("pop\n");
size -= innerSize;
}
writePS("pop\n");
str->close();
delete str;
}
void PSOutputDev::setupForms(Dict *resDict) {
if (!preloadImagesForms) {
return;
}
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("Form")) {
if (xObjRef.isRef()) {
setupForm(xObjRef.getRef(), &xObj);
} else {
error(errSyntaxError, -1,
"Form in resource dict is not an indirect reference");
}
}
}
}
}
}
void PSOutputDev::setupForm(Ref id, Object *strObj) {
Dict *dict, *resDict;
double m[6], bbox[4];
PDFRectangle box;
Gfx *gfx;
// check if form is already defined
for (int i = 0; i < formIDLen; ++i) {
if (formIDs[i].num == id.num && formIDs[i].gen == id.gen) {
return;
}
}
// add entry to formIDs list
if (formIDLen >= formIDSize) {
if (formIDSize == 0) {
formIDSize = 64;
} else {
formIDSize *= 2;
}
formIDs = (Ref *)greallocn(formIDs, formIDSize, sizeof(Ref));
}
formIDs[formIDLen++] = id;
dict = strObj->streamGetDict();
// get bounding box
Object bboxObj = dict->lookup("BBox");
if (!bboxObj.isArray()) {
error(errSyntaxError, -1, "Bad form bounding box");
return;
}
for (int i = 0; i < 4; ++i) {
Object obj1 = bboxObj.arrayGet(i);
bbox[i] = obj1.getNum();
}
// get matrix
Object matrixObj = dict->lookup("Matrix");
if (matrixObj.isArray()) {
for (int i = 0; i < 6; ++i) {
Object obj1 = matrixObj.arrayGet(i);
m[i] = obj1.getNum();
}
} else {
m[0] = 1; m[1] = 0;
m[2] = 0; m[3] = 1;
m[4] = 0; m[5] = 0;
}
// get resources
Object resObj = dict->lookup("Resources");
resDict = resObj.isDict() ? resObj.getDict() : nullptr;
writePSFmt("/f_{0:d}_{1:d} {{\n", id.num, id.gen);
writePS("q\n");
writePSFmt("[{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]);
box.x1 = bbox[0];
box.y1 = bbox[1];
box.x2 = bbox[2];
box.y2 = bbox[3];
gfx = new Gfx(doc, this, resDict, &box, &box);
gfx->display(strObj);
delete gfx;
writePS("Q\n");
writePS("} def\n");
}
bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/,
int rotateA, bool useMediaBox, bool crop,
int sliceX, int sliceY,
int sliceW, int sliceH,
bool printing,
bool (*abortCheckCbk)(void *data),
void *abortCheckCbkData,
bool (*annotDisplayDecideCbk)(Annot *annot, void *user_data),
void *annotDisplayDecideCbkData) {
PreScanOutputDev *scan;
bool rasterize;
#ifdef HAVE_SPLASH
bool useFlate, useLZW;
SplashOutputDev *splashOut;
SplashColor paperColor;
PDFRectangle box;
GfxState *state;
SplashBitmap *bitmap;
Stream *str0, *str;
unsigned char *p;
unsigned char col[4];
double hDPI2, vDPI2;
double m0, m1, m2, m3, m4, m5;
int nStripes, stripeH, stripeY;
int c, w, h, x, y, comp, i;
int numComps, initialNumComps;
char hexBuf[32*2 + 2]; // 32 values X 2 chars/value + line ending + null
unsigned char digit;
bool isGray;
#endif
if (!postInitDone) {
postInit();
}
if (forceRasterize) {
rasterize = true;
} else {
scan = new PreScanOutputDev(doc);
page->displaySlice(scan, 72, 72, rotateA, useMediaBox, crop,
sliceX, sliceY, sliceW, sliceH,
printing, abortCheckCbk, abortCheckCbkData,
annotDisplayDecideCbk, annotDisplayDecideCbkData);
rasterize = scan->usesTransparency() || scan->usesPatternImageMask();
delete scan;
}
if (!rasterize) {
return true;
}
#ifdef HAVE_SPLASH
// get the rasterization parameters
useFlate = getEnableFlate() && level >= psLevel3;
useLZW = getEnableLZW();
// start the PS page
page->makeBox(rasterResolution, rasterResolution, rotateA, useMediaBox, false,
sliceX, sliceY, sliceW, sliceH, &box, &crop);
rotateA += page->getRotate();
if (rotateA >= 360) {
rotateA -= 360;
} else if (rotateA < 0) {
rotateA += 360;
}
state = new GfxState(rasterResolution, rasterResolution, &box, rotateA, false);
startPage(page->getNum(), state, xref);
delete state;
// set up the SplashOutputDev
if (rasterMono || level == psLevel1) {
numComps = 1;
paperColor[0] = 0xff;
splashOut = new SplashOutputDev(splashModeMono8, 1, false,
paperColor, false);
#ifdef SPLASH_CMYK
} else if (level == psLevel1Sep || level == psLevel2Sep ||
level == psLevel3Sep || globalParams->getOverprintPreview()) {
numComps = 4;
paperColor[0] = paperColor[1] = paperColor[2] = paperColor[3] = 0;
splashOut = new SplashOutputDev(splashModeCMYK8, 1, false,
paperColor, false);
#endif
} else {
numComps = 3;
paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
splashOut = new SplashOutputDev(splashModeRGB8, 1, false,
paperColor, false);
}
splashOut->setFontAntialias(rasterAntialias);
splashOut->setVectorAntialias(rasterAntialias);
splashOut->startDoc(doc);
// break the page into stripes
hDPI2 = xScale * rasterResolution;
vDPI2 = yScale * rasterResolution;
if (sliceW < 0 || sliceH < 0) {
if (useMediaBox) {
box = *page->getMediaBox();
} else {
box = *page->getCropBox();
}
sliceX = sliceY = 0;
sliceW = (int)((box.x2 - box.x1) * hDPI2 / 72.0);
sliceH = (int)((box.y2 - box.y1) * vDPI2 / 72.0);
}
nStripes = (int)ceil((double)(sliceW * sliceH) /
(double)rasterizationSliceSize);
if (unlikely(nStripes == 0)) {
delete splashOut;
return false;
}
stripeH = (sliceH + nStripes - 1) / nStripes;
// render the stripes
initialNumComps = numComps;
for (stripeY = sliceY; stripeY < sliceH; stripeY += stripeH) {
// rasterize a stripe
page->makeBox(hDPI2, vDPI2, 0, useMediaBox, false,
sliceX, stripeY, sliceW, stripeH, &box, &crop);
m0 = box.x2 - box.x1;
m1 = 0;
m2 = 0;
m3 = box.y2 - box.y1;
m4 = box.x1;
m5 = box.y1;
page->displaySlice(splashOut, hDPI2, vDPI2,
(360 - page->getRotate()) % 360, useMediaBox, crop,
sliceX, stripeY, sliceW, stripeH,
printing, abortCheckCbk, abortCheckCbkData,
annotDisplayDecideCbk, annotDisplayDecideCbkData);
// draw the rasterized image
bitmap = splashOut->getBitmap();
numComps = initialNumComps;
w = bitmap->getWidth();
h = bitmap->getHeight();
writePS("gsave\n");
writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] concat\n",
m0, m1, m2, m3, m4, m5);
switch (level) {
case psLevel1:
writePSFmt("{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" : "");
p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize();
i = 0;
if (useBinary) {
for (y = 0; y < h; ++y) {
for (x = 0; x < w; ++x) {
hexBuf[i++] = *p++;
if (i >= 64) {
writePSBuf(hexBuf, i);
i = 0;
}
}
}
} else {
for (y = 0; y < h; ++y) {
for (x = 0; x < w; ++x) {
digit = *p / 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
digit = *p++ % 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
if (i >= 64) {
hexBuf[i++] = '\n';
writePSBuf(hexBuf, i);
i = 0;
}
}
}
}
if (i != 0) {
if (!useBinary) {
hexBuf[i++] = '\n';
}
writePSBuf(hexBuf, i);
}
break;
case psLevel1Sep:
p = bitmap->getDataPtr();
// Check for an all gray image
if (getOptimizeColorSpace()) {
isGray = true;
for (y = 0; y < h; ++y) {
for (x = 0; x < w; ++x) {
if (p[4*x] != p[4*x + 1] || p[4*x] != p[4*x + 2]) {
isGray = false;
y = h;
break;
}
}
p += bitmap->getRowSize();
}
} else {
isGray = false;
}
writePSFmt("{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,
isGray ? "" : "Sep",
useBinary ? "Bin" : "");
p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize();
i = 0;
col[0] = col[1] = col[2] = col[3] = 0;
if (isGray) {
int g;
if ((psProcessBlack & processColors) == 0) {
// Check if the image uses black
for (y = 0; y < h; ++y) {
for (x = 0; x < w; ++x) {
if (p[4*x] > 0 || p[4*x + 3] > 0) {
col[3] = 1;
y = h;
break;
}
}
p -= bitmap->getRowSize();
}
p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize();
}
for (y = 0; y < h; ++y) {
if (useBinary) {
// Binary gray image
for (x = 0; x < w; ++x) {
g = p[4*x] + p[4*x + 3];
g = 255 - g;
if (g < 0) g = 0;
hexBuf[i++] = (unsigned char) g;
if (i >= 64) {
writePSBuf(hexBuf, i);
i = 0;
}
}
} else {
// Hex gray image
for (x = 0; x < w; ++x) {
g = p[4*x] + p[4*x + 3];
g = 255 - g;
if (g < 0) g = 0;
digit = g / 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
digit = g % 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
if (i >= 64) {
hexBuf[i++] = '\n';
writePSBuf(hexBuf, i);
i = 0;
}
}
}
p -= bitmap->getRowSize();
}
} else if (((psProcessCyan | psProcessMagenta | psProcessYellow | psProcessBlack) & ~processColors) != 0) {
// Color image, need to check color flags for each dot
for (y = 0; y < h; ++y) {
for (comp = 0; comp < 4; ++comp) {
if (useBinary) {
// Binary color image
for (x = 0; x < w; ++x) {
col[comp] |= p[4*x + comp];
hexBuf[i++] = p[4*x + comp];
if (i >= 64) {
writePSBuf(hexBuf, i);
i = 0;
}
}
} else {
// Gray color image
for (x = 0; x < w; ++x) {
col[comp] |= p[4*x + comp];
digit = p[4*x + comp] / 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
digit = p[4*x + comp] % 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
if (i >= 64) {
hexBuf[i++] = '\n';
writePSBuf(hexBuf, i);
i = 0;
}
}
}
}
p -= bitmap->getRowSize();
}
} else {
// Color image, do not need to check color flags
for (y = 0; y < h; ++y) {
for (comp = 0; comp < 4; ++comp) {
if (useBinary) {
// Binary color image
for (x = 0; x < w; ++x) {
hexBuf[i++] = p[4*x + comp];
if (i >= 64) {
writePSBuf(hexBuf, i);
i = 0;
}
}
} else {
// Hex color image
for (x = 0; x < w; ++x) {
digit = p[4*x + comp] / 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
digit = p[4*x + comp] % 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
if (i >= 64) {
hexBuf[i++] = '\n';
writePSBuf(hexBuf, i);
i = 0;
}
}
}
}
p -= bitmap->getRowSize();
}
}
if (i != 0) {
if (!useBinary) {
hexBuf[i++] = '\n';
}
writePSBuf(hexBuf, i);
}
if (col[0]) {
processColors |= psProcessCyan;
}
if (col[1]) {
processColors |= psProcessMagenta;
}
if (col[2]) {
processColors |= psProcessYellow;
}
if (col[3]) {
processColors |= psProcessBlack;
}
break;
case psLevel2:
case psLevel2Sep:
case psLevel3:
case psLevel3Sep:
p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize();
str0 = new MemStream((char *)p, 0, w * h * numComps, Object(objNull));
// Check for a color image that uses only gray
if (!getOptimizeColorSpace()) {
isGray = false;
} else if (numComps == 4) {
int compCyan;
isGray = true;
while ((compCyan = str0->getChar()) != EOF) {
if (str0->getChar() != compCyan ||
str0->getChar() != compCyan) {
isGray = false;
break;
}
str0->getChar();
}
} else if (numComps == 3) {
int compRed;
isGray = true;
while ((compRed = str0->getChar()) != EOF) {
if (str0->getChar() != compRed ||
str0->getChar() != compRed) {
isGray = false;
break;
}
}
} else {
isGray = false;
}
str0->reset();
#ifdef ENABLE_ZLIB
if (useFlate) {
if (isGray && numComps == 4) {
str = new FlateEncoder(new CMYKGrayEncoder(str0));
numComps = 1;
} else if (isGray && numComps == 3) {
str = new FlateEncoder(new RGBGrayEncoder(str0));
numComps = 1;
} else {
str = new FlateEncoder(str0);
}
} else
#endif
if (useLZW) {
if (isGray && numComps == 4) {
str = new LZWEncoder(new CMYKGrayEncoder(str0));
numComps = 1;
} else if (isGray && numComps == 3) {
str = new LZWEncoder(new RGBGrayEncoder(str0));
numComps = 1;
} else {
str = new LZWEncoder(str0);
}
} else {
if (isGray && numComps == 4) {
str = new RunLengthEncoder(new CMYKGrayEncoder(str0));
numComps = 1;
} else if (isGray && numComps == 3) {
str = new RunLengthEncoder(new RGBGrayEncoder(str0));
numComps = 1;
} else {
str = new RunLengthEncoder(str0);
}
}
if (numComps == 1) {
writePS("/DeviceGray setcolorspace\n");
} else if (numComps == 3) {
writePS("/DeviceRGB setcolorspace\n");
} else {
writePS("/DeviceCMYK setcolorspace\n");
}
writePS("<<\n /ImageType 1\n");
writePSFmt(" /Width {0:d}\n", bitmap->getWidth());
writePSFmt(" /Height {0:d}\n", bitmap->getHeight());
writePSFmt(" /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", w, -h, h);
writePS(" /BitsPerComponent 8\n");
if (numComps == 1) {
writePS(" /Decode [1 0]\n");
} else if (numComps == 3) {
writePS(" /Decode [0 1 0 1 0 1]\n");
} else {
writePS(" /Decode [0 1 0 1 0 1 0 1]\n");
}
writePS(" /DataSource currentfile\n");
if (useBinary) {
/* nothing to do */;
} else if (useASCIIHex) {
writePS(" /ASCIIHexDecode filter\n");
} else {
writePS(" /ASCII85Decode filter\n");
}
if (useFlate) {
writePS(" /FlateDecode filter\n");
} else if (useLZW) {
writePS(" /LZWDecode filter\n");
} else {
writePS(" /RunLengthDecode filter\n");
}
writePS(">>\n");
if (useBinary) {
/* nothing to do */;
} else if (useASCIIHex) {
str = new ASCIIHexEncoder(str);
} else {
str = new ASCII85Encoder(str);
}
str->reset();
if (useBinary) {
// Count the bytes to write a document comment
int len = 0;
while (str->getChar() != EOF) {
len++;
}
str->reset();
writePSFmt("%%BeginData: {0:d} Binary Bytes\n", len+6+1);
}
writePS("image\n");
while ((c = str->getChar()) != EOF) {
writePSChar(c);
}
str->close();
delete str;
delete str0;
writePSChar('\n');
if (useBinary) {
writePS("%%EndData\n");
}
processColors |= (numComps == 1) ? psProcessBlack : psProcessCMYK;
break;
}
writePS("grestore\n");
}
delete splashOut;
// finish the PS page
endPage();
return false;
#else // HAVE_SPLASH
error(errSyntaxWarning, -1,
"PDF page uses transparency and PSOutputDev was built without"
" the Splash rasterizer - output may not be correct");
return true;
#endif // HAVE_SPLASH
}
void PSOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA) {
Page *page;
int x1, y1, x2, y2, width, height, t;
int imgWidth, imgHeight, imgWidth2, imgHeight2;
bool landscape;
GooString *s;
PSOutPaperSize *paperSize;
if (!postInitDone) {
postInit();
}
xref = xrefA;
if (mode == psModePS) {
GooString pageLabel;
const bool gotLabel = doc->getCatalog()->indexToLabel(pageNum -1, &pageLabel);
if (gotLabel) {
// See bug13338 for why we try to avoid parentheses...
bool needParens;
GooString *filteredString = filterPSLabel(&pageLabel, &needParens);
if (needParens) {
writePSFmt("%%Page: ({0:t}) {1:d}\n", filteredString, seqPage);
} else {
writePSFmt("%%Page: {0:t} {1:d}\n", filteredString, seqPage);
}
delete filteredString;
} else {
writePSFmt("%%Page: {0:d} {1:d}\n", pageNum, seqPage);
}
if (paperMatch) {
page = doc->getCatalog()->getPage(pageNum);
imgLLX = imgLLY = 0;
if (noCrop) {
imgURX = (int)ceil(page->getMediaWidth());
imgURY = (int)ceil(page->getMediaHeight());
} else {
imgURX = (int)ceil(page->getCropWidth());
imgURY = (int)ceil(page->getCropHeight());
}
if (state->getRotate() == 90 || state->getRotate() == 270) {
t = imgURX;
imgURX = imgURY;
imgURY = t;
}
}
}
// underlays
if (underlayCbk) {
(*underlayCbk)(this, underlayCbkData);
}
if (overlayCbk) {
saveState(nullptr);
}
xScale = yScale = 1;
switch (mode) {
case psModePS:
// rotate, translate, and scale page
imgWidth = imgURX - imgLLX;
imgHeight = imgURY - imgLLY;
x1 = (int)floor(state->getX1());
y1 = (int)floor(state->getY1());
x2 = (int)ceil(state->getX2());
y2 = (int)ceil(state->getY2());
width = x2 - x1;
height = y2 - y1;
tx = ty = 0;
// rotation and portrait/landscape mode
if (paperMatch) {
rotate = (360 - state->getRotate()) % 360;
landscape = false;
} else if (rotate0 >= 0) {
rotate = (360 - rotate0) % 360;
landscape = false;
} else {
rotate = (360 - state->getRotate()) % 360;
if (rotate == 0 || rotate == 180) {
if ((width < height && imgWidth > imgHeight && height > imgHeight) ||
(width > height && imgWidth < imgHeight && width > imgWidth)) {
rotate += 90;
landscape = true;
} else {
landscape = false;
}
} else { // rotate == 90 || rotate == 270
if ((height < width && imgWidth > imgHeight && width > imgHeight) ||
(height > width && imgWidth < imgHeight && height > imgWidth)) {
rotate = 270 - rotate;
landscape = true;
} else {
landscape = false;
}
}
}
if (rotate == 0) {
imgWidth2 = imgWidth;
imgHeight2 = imgHeight;
} else if (rotate == 90) {
ty = -imgWidth;
imgWidth2 = imgHeight;
imgHeight2 = imgWidth;
} else if (rotate == 180) {
imgWidth2 = imgWidth;
imgHeight2 = imgHeight;
tx = -imgWidth;
ty = -imgHeight;
} else { // rotate == 270
tx = -imgHeight;
imgWidth2 = imgHeight;
imgHeight2 = imgWidth;
}
// shrink or expand
if (xScale0 > 0 && yScale0 > 0) {
xScale = xScale0;
yScale = yScale0;
} else if ((globalParams->getPSShrinkLarger() &&
(width > imgWidth2 || height > imgHeight2)) ||
(globalParams->getPSExpandSmaller() &&
(width < imgWidth2 && height < imgHeight2))) {
xScale = (double)imgWidth2 / (double)width;
yScale = (double)imgHeight2 / (double)height;
if (yScale < xScale) {
xScale = yScale;
} else {
yScale = xScale;
}
}
// deal with odd bounding boxes or clipping
if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
tx -= xScale * clipLLX0;
ty -= yScale * clipLLY0;
} else {
tx -= xScale * x1;
ty -= yScale * y1;
}
// center
if (tx0 >= 0 && ty0 >= 0) {
tx += (rotate == 0 || rotate == 180) ? tx0 : ty0;
ty += (rotate == 0 || rotate == 180) ? ty0 : -tx0;
} else if (psCenter) {
if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
tx += (imgWidth2 - xScale * (clipURX0 - clipLLX0)) / 2;
ty += (imgHeight2 - yScale * (clipURY0 - clipLLY0)) / 2;
} else {
tx += (imgWidth2 - xScale * width) / 2;
ty += (imgHeight2 - yScale * height) / 2;
}
}
tx += (rotate == 0 || rotate == 180) ? imgLLX : imgLLY;
ty += (rotate == 0 || rotate == 180) ? imgLLY : -imgLLX;
if (paperMatch) {
paperSize = (PSOutPaperSize *)paperSizes->get(pagePaperSize[pageNum]);
writePSFmt("%%PageMedia: {0:t}\n", paperSize->name);
}
// Create a matrix with the same transform that will be output to PS
Matrix m;
switch (rotate) {
default:
case 0:
m.init(1, 0,
0, 1,
0, 0);
break;
case 90:
m.init(0, 1,
-1, 0,
0, 0);
break;
case 180:
m.init(-1, 0,
0, -1,
0, 0);
break;
case 270:
m.init(0, -1,
1, 0,
0, 0);
break;
}
m.translate(tx, ty);
m.scale(xScale, yScale);
double bboxX1, bboxY1, bboxX2, bboxY2;
m.transform(0, 0, &bboxX1, &bboxY1);
m.transform(width, height, &bboxX2, &bboxY2);
writePSFmt("%%PageBoundingBox: {0:g} {1:g} {2:g} {3:g}\n",
floor(std::min(bboxX1, bboxX2)),
floor(std::min(bboxY1, bboxY2)),
ceil (std::max(bboxX1, bboxX2)),
ceil (std::max(bboxY1, bboxY2)));
writePSFmt("%%PageOrientation: {0:s}\n",
landscape ? "Landscape" : "Portrait");
writePS("%%BeginPageSetup\n");
if (paperMatch) {
writePSFmt("{0:d} {1:d} pdfSetupPaper\n", imgURX, imgURY);
}
writePS("pdfStartPage\n");
if (rotate)
writePSFmt("{0:d} rotate\n", rotate);
if (tx != 0 || ty != 0) {
writePSFmt("{0:.6g} {1:.6g} translate\n", tx, ty);
}
if (xScale != 1 || yScale != 1) {
writePSFmt("{0:.6f} {1:.6f} scale\n", xScale, yScale);
}
if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} re W\n",
clipLLX0, clipLLY0, clipURX0 - clipLLX0, clipURY0 - clipLLY0);
} else {
writePSFmt("{0:d} {1:d} {2:d} {3:d} re W\n", x1, y1, x2 - x1, y2 - y1);
}
++seqPage;
break;
case psModeEPS:
writePS("pdfStartPage\n");
tx = ty = 0;
rotate = (360 - state->getRotate()) % 360;
if (rotate == 0) {
} else if (rotate == 90) {
writePS("90 rotate\n");
tx = -epsX1;
ty = -epsY2;
} else if (rotate == 180) {
writePS("180 rotate\n");
tx = -(epsX1 + epsX2);
ty = -(epsY1 + epsY2);
} else { // rotate == 270
writePS("270 rotate\n");
tx = -epsX2;
ty = -epsY1;
}
if (tx != 0 || ty != 0) {
writePSFmt("{0:.6g} {1:.6g} translate\n", tx, ty);
}
break;
case psModeForm:
writePS("/PaintProc {\n");
writePS("begin xpdf begin\n");
writePS("pdfStartPage\n");
tx = ty = 0;
rotate = 0;
break;
}
if (customCodeCbk) {
if ((s = (*customCodeCbk)(this, psOutCustomPageSetup, pageNum,
customCodeCbkData))) {
writePS(s->c_str());
delete s;
}
}
writePS("%%EndPageSetup\n");
}
void PSOutputDev::endPage() {
if (overlayCbk) {
restoreState(nullptr);
(*overlayCbk)(this, overlayCbkData);
}
if (mode == psModeForm) {
writePS("pdfEndPage\n");
writePS("end end\n");
writePS("} def\n");
writePS("end end\n");
} else {
if (!manualCtrl) {
writePS("showpage\n");
}
writePS("%%PageTrailer\n");
writePageTrailer();
}
}
void PSOutputDev::saveState(GfxState *state) {
writePS("q\n");
++numSaves;
}
void PSOutputDev::restoreState(GfxState *state) {
writePS("Q\n");
--numSaves;
}
void PSOutputDev::updateCTM(GfxState *state, double m11, double m12,
double m21, double m22, double m31, double m32) {
writePSFmt("[{0:.6gs} {1:.6gs} {2:.6gs} {3:.6gs} {4:.6gs} {5:.6gs}] cm\n",
m11, m12, m21, m22, m31, m32);
}
void PSOutputDev::updateLineDash(GfxState *state) {
double *dash;
double start;
int length, i;
state->getLineDash(&dash, &length, &start);
writePS("[");
for (i = 0; i < length; ++i) {
writePSFmt("{0:.6g}{1:w}",
dash[i] < 0 ? 0 : dash[i],
(i == length-1) ? 0 : 1);
}
writePSFmt("] {0:.6g} d\n", start);
}
void PSOutputDev::updateFlatness(GfxState *state) {
writePSFmt("{0:d} i\n", state->getFlatness());
}
void PSOutputDev::updateLineJoin(GfxState *state) {
writePSFmt("{0:d} j\n", state->getLineJoin());
}
void PSOutputDev::updateLineCap(GfxState *state) {
writePSFmt("{0:d} J\n", state->getLineCap());
}
void PSOutputDev::updateMiterLimit(GfxState *state) {
writePSFmt("{0:.6g} M\n", state->getMiterLimit());
}
void PSOutputDev::updateLineWidth(GfxState *state) {
writePSFmt("{0:.6g} w\n", state->getLineWidth());
}
void PSOutputDev::updateFillColorSpace(GfxState *state) {
if (inUncoloredPattern) {
return;
}
switch (level) {
case psLevel1:
case psLevel1Sep:
break;
case psLevel2:
case psLevel3:
if (state->getFillColorSpace()->getMode() != csPattern) {
dumpColorSpaceL2(state->getFillColorSpace(), true, false, false);
writePS(" cs\n");
}
break;
case psLevel2Sep:
case psLevel3Sep:
break;
}
}
void PSOutputDev::updateStrokeColorSpace(GfxState *state) {
if (inUncoloredPattern) {
return;
}
switch (level) {
case psLevel1:
case psLevel1Sep:
break;
case psLevel2:
case psLevel3:
if (state->getStrokeColorSpace()->getMode() != csPattern) {
dumpColorSpaceL2(state->getStrokeColorSpace(), true, false, false);
writePS(" CS\n");
}
break;
case psLevel2Sep:
case psLevel3Sep:
break;
}
}
void PSOutputDev::updateFillColor(GfxState *state) {
GfxColor color;
GfxGray gray;
GfxCMYK cmyk;
GfxSeparationColorSpace *sepCS;
double c, m, y, k;
int i;
if (inUncoloredPattern) {
return;
}
switch (level) {
case psLevel1:
state->getFillGray(&gray);
writePSFmt("{0:.4g} g\n", colToDbl(gray));
break;
case psLevel2:
case psLevel3:
if (state->getFillColorSpace()->getMode() != csPattern) {
const GfxColor *colorPtr = state->getFillColor();
writePS("[");
for (i = 0; i < state->getFillColorSpace()->getNComps(); ++i) {
if (i > 0) {
writePS(" ");
}
writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i]));
}
writePS("] sc\n");
}
break;
case psLevel1Sep:
case psLevel2Sep:
case psLevel3Sep:
if (state->getFillColorSpace()->getMode() == csSeparation && (level > psLevel1Sep || getPassLevel1CustomColor())) {
sepCS = (GfxSeparationColorSpace *)state->getFillColorSpace();
color.c[0] = gfxColorComp1;
sepCS->getCMYK(&color, &cmyk);
writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) ck\n",
colToDbl(state->getFillColor()->c[0]),
colToDbl(cmyk.c), colToDbl(cmyk.m),
colToDbl(cmyk.y), colToDbl(cmyk.k),
sepCS->getName());
addCustomColor(sepCS);
} else {
state->getFillCMYK(&cmyk);
c = colToDbl(cmyk.c);
m = colToDbl(cmyk.m);
y = colToDbl(cmyk.y);
k = colToDbl(cmyk.k);
writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k);
addProcessColor(c, m, y, k);
}
break;
}
t3Cacheable = false;
}
void PSOutputDev::updateStrokeColor(GfxState *state) {
GfxColor color;
GfxGray gray;
GfxCMYK cmyk;
GfxSeparationColorSpace *sepCS;
double c, m, y, k;
int i;
if (inUncoloredPattern) {
return;
}
switch (level) {
case psLevel1:
state->getStrokeGray(&gray);
writePSFmt("{0:.4g} G\n", colToDbl(gray));
break;
case psLevel2:
case psLevel3:
if (state->getStrokeColorSpace()->getMode() != csPattern) {
const GfxColor *colorPtr = state->getStrokeColor();
writePS("[");
for (i = 0; i < state->getStrokeColorSpace()->getNComps(); ++i) {
if (i > 0) {
writePS(" ");
}
writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i]));
}
writePS("] SC\n");
}
break;
case psLevel1Sep:
case psLevel2Sep:
case psLevel3Sep:
if (state->getStrokeColorSpace()->getMode() == csSeparation && (level > psLevel1Sep || getPassLevel1CustomColor())) {
sepCS = (GfxSeparationColorSpace *)state->getStrokeColorSpace();
color.c[0] = gfxColorComp1;
sepCS->getCMYK(&color, &cmyk);
writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) CK\n",
colToDbl(state->getStrokeColor()->c[0]),
colToDbl(cmyk.c), colToDbl(cmyk.m),
colToDbl(cmyk.y), colToDbl(cmyk.k),
sepCS->getName());
addCustomColor(sepCS);
} else {
state->getStrokeCMYK(&cmyk);
c = colToDbl(cmyk.c);
m = colToDbl(cmyk.m);
y = colToDbl(cmyk.y);
k = colToDbl(cmyk.k);
writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k);
addProcessColor(c, m, y, k);
}
break;
}
t3Cacheable = false;
}
void PSOutputDev::addProcessColor(double c, double m, double y, double k) {
if (c > 0) {
processColors |= psProcessCyan;
}
if (m > 0) {
processColors |= psProcessMagenta;
}
if (y > 0) {
processColors |= psProcessYellow;
}
if (k > 0) {
processColors |= psProcessBlack;
}
}
void PSOutputDev::addCustomColor(GfxSeparationColorSpace *sepCS) {
PSOutCustomColor *cc;
GfxColor color;
GfxCMYK cmyk;
if (!sepCS->getName()->cmp("Black")) {
processColors |= psProcessBlack;
return;
}
if (!sepCS->getName()->cmp("Cyan")) {
processColors |= psProcessCyan;
return;
}
if (!sepCS->getName()->cmp("Yellow")) {
processColors |= psProcessYellow;
return;
}
if (!sepCS->getName()->cmp("Magenta")) {
processColors |= psProcessMagenta;
return;
}
if (!sepCS->getName()->cmp("All"))
return;
if (!sepCS->getName()->cmp("None"))
return;
for (cc = customColors; cc; cc = cc->next) {
if (!cc->name->cmp(sepCS->getName())) {
return;
}
}
color.c[0] = gfxColorComp1;
sepCS->getCMYK(&color, &cmyk);
cc = new PSOutCustomColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
colToDbl(cmyk.y), colToDbl(cmyk.k),
sepCS->getName()->copy());
cc->next = customColors;
customColors = cc;
}
void PSOutputDev::updateFillOverprint(GfxState *state) {
if (level >= psLevel2) {
writePSFmt("{0:s} op\n", state->getFillOverprint() ? "true" : "false");
}
}
void PSOutputDev::updateStrokeOverprint(GfxState *state) {
if (level >= psLevel2) {
writePSFmt("{0:s} OP\n", state->getStrokeOverprint() ? "true" : "false");
}
}
void PSOutputDev::updateOverprintMode(GfxState *state) {
if (level >= psLevel3) {
writePSFmt("{0:s} opm\n", state->getOverprintMode() ? "true" : "false");
}
}
void PSOutputDev::updateTransfer(GfxState *state) {
Function **funcs;
int i;
funcs = state->getTransfer();
if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) {
if (level >= psLevel2) {
for (i = 0; i < 4; ++i) {
cvtFunction(funcs[i]);
}
writePS("setcolortransfer\n");
} else {
cvtFunction(funcs[3]);
writePS("settransfer\n");
}
} else if (funcs[0]) {
cvtFunction(funcs[0]);
writePS("settransfer\n");
} else {
writePS("{} settransfer\n");
}
}
void PSOutputDev::updateFont(GfxState *state) {
if (state->getFont()) {
writePSFmt("/F{0:d}_{1:d} {2:.6g} Tf\n",
state->getFont()->getID()->num, state->getFont()->getID()->gen,
fabs(state->getFontSize()) < 0.0001 ? 0.0001
: state->getFontSize());
}
}
void PSOutputDev::updateTextMat(GfxState *state) {
const double *mat = state->getTextMat();
if (fabs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.00001) {
// avoid a singular (or close-to-singular) matrix
writePSFmt("[0.00001 0 0 0.00001 {0:.6g} {1:.6g}] Tm\n", mat[4], mat[5]);
} else {
writePSFmt("[{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]);
}
}
void PSOutputDev::updateCharSpace(GfxState *state) {
writePSFmt("{0:.6g} Tc\n", state->getCharSpace());
}
void PSOutputDev::updateRender(GfxState *state) {
int rm;
rm = state->getRender();
writePSFmt("{0:d} Tr\n", rm);
rm &= 3;
if (rm != 0 && rm != 3) {
t3Cacheable = false;
}
}
void PSOutputDev::updateRise(GfxState *state) {
writePSFmt("{0:.6g} Ts\n", state->getRise());
}
void PSOutputDev::updateWordSpace(GfxState *state) {
writePSFmt("{0:.6g} Tw\n", state->getWordSpace());
}
void PSOutputDev::updateHorizScaling(GfxState *state) {
double h;
h = state->getHorizScaling();
if (fabs(h) < 0.01) {
h = 0.01;
}
writePSFmt("{0:.6g} Tz\n", h);
}
void PSOutputDev::updateTextPos(GfxState *state) {
writePSFmt("{0:.6g} {1:.6g} Td\n", state->getLineX(), state->getLineY());
}
void PSOutputDev::updateTextShift(GfxState *state, double shift) {
if (state->getFont()->getWMode()) {
writePSFmt("{0:.6g} TJmV\n", shift);
} else {
writePSFmt("{0:.6g} TJm\n", shift);
}
}
void PSOutputDev::saveTextPos(GfxState *state) {
writePS("currentpoint\n");
}
void PSOutputDev::restoreTextPos(GfxState *state) {
writePS("m\n");
}
void PSOutputDev::stroke(GfxState *state) {
doPath(state->getPath());
if (inType3Char && t3FillColorOnly) {
// if we're constructing a cacheable Type 3 glyph, we need to do
// everything in the fill color
writePS("Sf\n");
} else {
writePS("S\n");
}
}
void PSOutputDev::fill(GfxState *state) {
doPath(state->getPath());
writePS("f\n");
}
void PSOutputDev::eoFill(GfxState *state) {
doPath(state->getPath());
writePS("f*\n");
}
bool PSOutputDev::tilingPatternFillL1(GfxState *state, Catalog *cat, Object *str,
const double *pmat, int paintType, int tilingType, Dict *resDict,
const double *mat, const double *bbox,
int x0, int y0, int x1, int y1,
double xStep, double yStep) {
PDFRectangle box;
Gfx *gfx;
// define a Type 3 font
writePS("8 dict begin\n");
writePS("/FontType 3 def\n");
writePS("/FontMatrix [1 0 0 1 0 0] def\n");
writePSFmt("/FontBBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}] def\n",
bbox[0], bbox[1], bbox[2], bbox[3]);
writePS("/Encoding 256 array def\n");
writePS(" 0 1 255 { Encoding exch /.notdef put } for\n");
writePS(" Encoding 120 /x put\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");
writePS("/CharProcs 1 dict def\n");
writePS("CharProcs begin\n");
box.x1 = bbox[0];
box.y1 = bbox[1];
box.x2 = bbox[2];
box.y2 = bbox[3];
gfx = new Gfx(doc, this, resDict, &box, nullptr);
writePS("/x {\n");
if (paintType == 2) {
writePSFmt("{0:.6g} 0 {1:.6g} {2:.6g} {3:.6g} {4:.6g} setcachedevice\n",
xStep, bbox[0], bbox[1], bbox[2], bbox[3]);
t3FillColorOnly = true;
} else
{
if (x1 - 1 <= x0) {
writePS("1 0 setcharwidth\n");
} else {
writePSFmt("{0:.6g} 0 setcharwidth\n", xStep);
}
t3FillColorOnly = false;
}
inType3Char = true;
if (paintType == 2) {
inUncoloredPattern = true;
// ensure any PS procedures that contain sCol or fCol do not change the color
writePS("/pdfLastFill true def\n");
writePS("/pdfLastStroke true def\n");
}
++numTilingPatterns;
gfx->display(str);
--numTilingPatterns;
if (paintType == 2) {
inUncoloredPattern = false;
// ensure the next PS procedures that uses sCol or fCol will update the color
writePS("/pdfLastFill false def\n");
writePS("/pdfLastStroke false def\n");
}
inType3Char = false;
writePS("} def\n");
delete gfx;
writePS("end\n");
writePS("currentdict end\n");
writePSFmt("/xpdfTile{0:d} exch definefont pop\n", numTilingPatterns);
// draw the tiles
writePSFmt("/xpdfTile{0:d} findfont setfont\n", numTilingPatterns);
writePS("fCol\n");
writePSFmt("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]);
writePSFmt("{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);
writePS("grestore\n");
return true;
}
bool PSOutputDev::tilingPatternFillL2(GfxState *state, Catalog *cat, Object *str,
const double *pmat, int paintType, int tilingType, Dict *resDict,
const double *mat, const double *bbox,
int x0, int y0, int x1, int y1,
double xStep, double yStep) {
PDFRectangle box;
Gfx *gfx;
if (paintType == 2) {
// setpattern with PaintType 2 needs the paint color
writePS("currentcolor\n");
}
writePS("<<\n /PatternType 1\n");
writePSFmt(" /PaintType {0:d}\n", paintType);
writePSFmt(" /TilingType {0:d}\n", tilingType);
writePSFmt(" /BBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}]\n", bbox[0], bbox[1], bbox[2], bbox[3]);
writePSFmt(" /XStep {0:.6g}\n", xStep);
writePSFmt(" /YStep {0:.6g}\n", yStep);
writePS(" /PaintProc { \n");
box.x1 = bbox[0];
box.y1 = bbox[1];
box.x2 = bbox[2];
box.y2 = bbox[3];
gfx = new Gfx(doc, this, resDict, &box, nullptr);
inType3Char = true;
if (paintType == 2) {
inUncoloredPattern = true;
// ensure any PS procedures that contain sCol or fCol do not change the color
writePS("/pdfLastFill true def\n");
writePS("/pdfLastStroke true def\n");
}
gfx->display(str);
if (paintType == 2) {
inUncoloredPattern = false;
// ensure the next PS procedures that uses sCol or fCol will update the color
writePS("/pdfLastFill false def\n");
writePS("/pdfLastStroke false def\n");
}
inType3Char = false;
delete gfx;
writePS(" }\n");
writePS(">>\n");
writePSFmt("[{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]);
writePS("makepattern setpattern\n");
writePS("clippath fill\n"); // Gfx sets up a clip before calling out->tilingPatternFill()
return true;
}
bool PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, 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, double yStep) {
if (x1 - x0 == 1 && y1 - y0 == 1) {
// Don't need to use patterns if only one instance of the pattern is used
PDFRectangle box;
Gfx *gfx;
double x, y, tx, ty;
x = x0 * xStep;
y = y0 * yStep;
tx = x * mat[0] + y * mat[2] + mat[4];
ty = x * mat[1] + y * mat[3] + mat[5];
box.x1 = bbox[0];
box.y1 = bbox[1];
box.x2 = bbox[2];
box.y2 = bbox[3];
gfx = new Gfx(doc, this, resDict, &box, nullptr, nullptr, nullptr, gfxA);
writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] cm\n", mat[0], mat[1], mat[2], mat[3], tx, ty);
inType3Char = true;
gfx->display(str);
inType3Char = false;
delete gfx;
return true;
}
if (level == psLevel1 || level == psLevel1Sep) {
return tilingPatternFillL1(state, cat, str, pmat, paintType, tilingType, resDict,
mat, bbox, x0, y0, x1, y1, xStep, yStep);
} else {
return tilingPatternFillL2(state, cat, str, pmat, paintType, tilingType, resDict,
mat, bbox, x0, y0, x1, y1, xStep, yStep);
}
}
bool PSOutputDev::functionShadedFill(GfxState *state,
GfxFunctionShading *shading) {
double x0, y0, x1, y1;
int i;
if (level == psLevel2Sep || level == psLevel3Sep) {
if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
return false;
}
processColors |= psProcessCMYK;
}
shading->getDomain(&x0, &y0, &x1, &y1);
const double *mat = shading->getMatrix();
writePSFmt("/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]);
writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
if (shading->getNFuncs() == 1) {
writePS("/func ");
cvtFunction(shading->getFunc(0));
writePS("def\n");
} else {
writePS("/func {\n");
for (i = 0; i < shading->getNFuncs(); ++i) {
if (i < shading->getNFuncs() - 1) {
writePS("2 copy\n");
}
cvtFunction(shading->getFunc(i));
writePS("exec\n");
if (i < shading->getNFuncs() - 1) {
writePS("3 1 roll\n");
}
}
writePS("} def\n");
}
writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} 0 funcSH\n", x0, y0, x1, y1);
return true;
}
bool PSOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double /*tMin*/, double /*tMax*/) {
double xMin, yMin, xMax, yMax;
double x0, y0, x1, y1, dx, dy, mul;
double tMin, tMax, t, t0, t1;
int i;
if (level == psLevel2Sep || level == psLevel3Sep) {
if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
return false;
}
processColors |= psProcessCMYK;
}
// get the clip region bbox
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
// compute min and max t values, based on the four corners of the
// clip region bbox
shading->getCoords(&x0, &y0, &x1, &y1);
dx = x1 - x0;
dy = y1 - y0;
if (fabs(dx) < 0.01 && fabs(dy) < 0.01) {
return true;
} else {
mul = 1 / (dx * dx + dy * dy);
tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
if (t < tMin) {
tMin = t;
} else if (t > tMax) {
tMax = t;
}
t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
if (t < tMin) {
tMin = t;
} else if (t > tMax) {
tMax = t;
}
t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
if (t < tMin) {
tMin = t;
} else if (t > tMax) {
tMax = t;
}
if (tMin < 0 && !shading->getExtend0()) {
tMin = 0;
}
if (tMax > 1 && !shading->getExtend1()) {
tMax = 1;
}
}
// get the function domain
t0 = shading->getDomain0();
t1 = shading->getDomain1();
// generate the PS code
writePSFmt("/t0 {0:.6g} def\n", t0);
writePSFmt("/t1 {0:.6g} def\n", t1);
writePSFmt("/dt {0:.6g} def\n", t1 - t0);
writePSFmt("/x0 {0:.6g} def\n", x0);
writePSFmt("/y0 {0:.6g} def\n", y0);
writePSFmt("/dx {0:.6g} def\n", x1 - x0);
writePSFmt("/x1 {0:.6g} def\n", x1);
writePSFmt("/y1 {0:.6g} def\n", y1);
writePSFmt("/dy {0:.6g} def\n", y1 - y0);
writePSFmt("/xMin {0:.6g} def\n", xMin);
writePSFmt("/yMin {0:.6g} def\n", yMin);
writePSFmt("/xMax {0:.6g} def\n", xMax);
writePSFmt("/yMax {0:.6g} def\n", yMax);
writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
if (shading->getNFuncs() == 1) {
writePS("/func ");
cvtFunction(shading->getFunc(0));
writePS("def\n");
} else {
writePS("/func {\n");
for (i = 0; i < shading->getNFuncs(); ++i) {
if (i < shading->getNFuncs() - 1) {
writePS("dup\n");
}
cvtFunction(shading->getFunc(i));
writePS("exec\n");
if (i < shading->getNFuncs() - 1) {
writePS("exch\n");
}
}
writePS("} def\n");
}
writePSFmt("{0:.6g} {1:.6g} 0 axialSH\n", tMin, tMax);
return true;
}
bool PSOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double /*sMin*/, double /*sMax*/) {
double xMin, yMin, xMax, yMax;
double x0, y0, r0, x1, y1, r1, t0, t1;
double xa, ya, ra;
double sMin, sMax, h, ta;
double sLeft, sRight, sTop, sBottom, sZero, sDiag;
bool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero;
bool haveSMin, haveSMax;
double theta, alpha, a1, a2;
bool enclosed;
int i;
if (level == psLevel2Sep || level == psLevel3Sep) {
if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
return false;
}
processColors |= psProcessCMYK;
}
// get the shading info
shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
t0 = shading->getDomain0();
t1 = shading->getDomain1();
// Compute the point at which r(s) = 0; check for the enclosed
// circles case; and compute the angles for the tangent lines.
// Compute the point at which r(s) = 0; check for the enclosed
// circles case; and compute the angles for the tangent lines.
h = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
if (h == 0) {
enclosed = true;
theta = 0; // make gcc happy
} else if (r1 - r0 == 0) {
enclosed = false;
theta = 0;
} else if (fabs(r1 - r0) >= h) {
enclosed = true;
theta = 0; // make gcc happy
} else {
enclosed = false;
theta = asin((r1 - r0) / h);
}
if (enclosed) {
a1 = 0;
a2 = 360;
} else {
alpha = atan2(y1 - y0, x1 - x0);
a1 = (180 / M_PI) * (alpha + theta) + 90;
a2 = (180 / M_PI) * (alpha - theta) - 90;
while (a2 < a1) {
a2 += 360;
}
}
// compute the (possibly extended) s range
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
if (enclosed) {
sMin = 0;
sMax = 1;
} else {
// solve x(sLeft) + r(sLeft) = xMin
if ((haveSLeft = fabs((x1 + r1) - (x0 + r0)) > 0.000001)) {
sLeft = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
} else {
sLeft = 0; // make gcc happy
}
// solve x(sRight) - r(sRight) = xMax
if ((haveSRight = fabs((x1 - r1) - (x0 - r0)) > 0.000001)) {
sRight = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
} else {
sRight = 0; // make gcc happy
}
// solve y(sBottom) + r(sBottom) = yMin
if ((haveSBottom = fabs((y1 + r1) - (y0 + r0)) > 0.000001)) {
sBottom = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
} else {
sBottom = 0; // make gcc happy
}
// solve y(sTop) - r(sTop) = yMax
if ((haveSTop = fabs((y1 - r1) - (y0 - r0)) > 0.000001)) {
sTop = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
} else {
sTop = 0; // make gcc happy
}
// solve r(sZero) = 0
if ((haveSZero = fabs(r1 - r0) > 0.000001)) {
sZero = -r0 / (r1 - r0);
} else {
sZero = 0; // make gcc happy
}
// solve r(sDiag) = sqrt((xMax-xMin)^2 + (yMax-yMin)^2)
if (haveSZero) {
sDiag = (sqrt((xMax - xMin) * (xMax - xMin) +
(yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0);
} else {
sDiag = 0; // make gcc happy
}
// compute sMin
if (shading->getExtend0()) {
sMin = 0;
haveSMin = false;
if (x0 < x1 && haveSLeft && sLeft < 0) {
sMin = sLeft;
haveSMin = true;
} else if (x0 > x1 && haveSRight && sRight < 0) {
sMin = sRight;
haveSMin = true;
}
if (y0 < y1 && haveSBottom && sBottom < 0) {
if (!haveSMin || sBottom > sMin) {
sMin = sBottom;
haveSMin = true;
}
} else if (y0 > y1 && haveSTop && sTop < 0) {
if (!haveSMin || sTop > sMin) {
sMin = sTop;
haveSMin = true;
}
}
if (haveSZero && sZero < 0) {
if (!haveSMin || sZero > sMin) {
sMin = sZero;
}
}
} else {
sMin = 0;
}
// compute sMax
if (shading->getExtend1()) {
sMax = 1;
haveSMax = false;
if (x1 < x0 && haveSLeft && sLeft > 1) {
sMax = sLeft;
haveSMax = true;
} else if (x1 > x0 && haveSRight && sRight > 1) {
sMax = sRight;
haveSMax = true;
}
if (y1 < y0 && haveSBottom && sBottom > 1) {
if (!haveSMax || sBottom < sMax) {
sMax = sBottom;
haveSMax = true;
}
} else if (y1 > y0 && haveSTop && sTop > 1) {
if (!haveSMax || sTop < sMax) {
sMax = sTop;
haveSMax = true;
}
}
if (haveSZero && sDiag > 1) {
if (!haveSMax || sDiag < sMax) {
sMax = sDiag;
}
}
} else {
sMax = 1;
}
}
// generate the PS code
writePSFmt("/x0 {0:.6g} def\n", x0);
writePSFmt("/x1 {0:.6g} def\n", x1);
writePSFmt("/dx {0:.6g} def\n", x1 - x0);
writePSFmt("/y0 {0:.6g} def\n", y0);
writePSFmt("/y1 {0:.6g} def\n", y1);
writePSFmt("/dy {0:.6g} def\n", y1 - y0);
writePSFmt("/r0 {0:.6g} def\n", r0);
writePSFmt("/r1 {0:.6g} def\n", r1);
writePSFmt("/dr {0:.6g} def\n", r1 - r0);
writePSFmt("/t0 {0:.6g} def\n", t0);
writePSFmt("/t1 {0:.6g} def\n", t1);
writePSFmt("/dt {0:.6g} def\n", t1 - t0);
writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
writePSFmt("/encl {0:s} def\n", enclosed ? "true" : "false");
writePSFmt("/a1 {0:.6g} def\n", a1);
writePSFmt("/a2 {0:.6g} def\n", a2);
if (shading->getNFuncs() == 1) {
writePS("/func ");
cvtFunction(shading->getFunc(0));
writePS("def\n");
} else {
writePS("/func {\n");
for (i = 0; i < shading->getNFuncs(); ++i) {
if (i < shading->getNFuncs() - 1) {
writePS("dup\n");
}
cvtFunction(shading->getFunc(i));
writePS("exec\n");
if (i < shading->getNFuncs() - 1) {
writePS("exch\n");
}
}
writePS("} def\n");
}
writePSFmt("{0:.6g} {1:.6g} 0 radialSH\n", sMin, sMax);
// extend the 'enclosed' case
if (enclosed) {
// extend the smaller circle
if ((shading->getExtend0() && r0 <= r1) ||
(shading->getExtend1() && r1 < r0)) {
if (r0 <= r1) {
ta = t0;
ra = r0;
xa = x0;
ya = y0;
} else {
ta = t1;
ra = r1;
xa = x1;
ya = y1;
}
if (level == psLevel2Sep || level == psLevel3Sep) {
writePSFmt("{0:.6g} radialCol aload pop k\n", ta);
} else {
writePSFmt("{0:.6g} radialCol sc\n", ta);
}
writePSFmt("{0:.6g} {1:.6g} {2:.6g} 0 360 arc h f*\n", xa, ya, ra);
}
// extend the larger circle
if ((shading->getExtend0() && r0 > r1) ||
(shading->getExtend1() && r1 >= r0)) {
if (r0 > r1) {
ta = t0;
ra = r0;
xa = x0;
ya = y0;
} else {
ta = t1;
ra = r1;
xa = x1;
ya = y1;
}
if (level == psLevel2Sep || level == psLevel3Sep) {
writePSFmt("{0:.6g} radialCol aload pop k\n", ta);
} else {
writePSFmt("{0:.6g} radialCol sc\n", ta);
}
writePSFmt("{0:.6g} {1:.6g} {2:.6g} 0 360 arc h\n", xa, ya, ra);
writePSFmt("{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);
}
}
return true;
}
bool PSOutputDev::patchMeshShadedFill(GfxState *state,
GfxPatchMeshShading *shading) {
// TODO: support parametrized shading
if (level < psLevel3 || shading->isParameterized()) {
return false;
}
writePS("%% Begin patchMeshShadedFill\n");
// ShadingType 7 shadings are pretty much the same for pdf and ps.
// As such, we basically just need to invert GfxPatchMeshShading::parse here.
writePS("<<\n");
writePS(" /ShadingType 7\n");
writePS(" /ColorSpace ");
dumpColorSpaceL2(shading->getColorSpace(), false, false, false);
writePS("\n");
writePS(" /DataSource [\n");
const int ncomps = shading->getColorSpace()->getNComps();
for (int i = 0; i < shading->getNPatches(); ++i) {
const auto& patch = *shading->getPatch(i);
// Print Flag, for us always f = 0
writePS(" 0 \n");
// Print coordinates
const std::array<std::pair<int,int>, 16> coordindices = {{ {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} }};
for (const auto& index: coordindices) {
writePSFmt(" {0:.6g} {1:.6g}\n", patch.x[index.first][index.second],
patch.y[index.first][index.second]);
}
// Print colors
const std::array<std::pair<int, int>, 4> colindices = {{ {0,0}, {0,1}, {1,1}, {1,0} }};
for (const auto& index: colindices) {
writePS(" ");
for (int comp = 0; comp < ncomps; ++comp) {
writePSFmt(" {0:.6g}", colToDbl(patch.color[index.first][index.second].c[comp]));
}
writePS("\n");
}
}
writePS(" ]\n");
writePS(">> shfill\n");
writePS("%% End patchMeshShadedFill\n");
return true;
}
void PSOutputDev::clip(GfxState *state) {
doPath(state->getPath());
writePS("W\n");
}
void PSOutputDev::eoClip(GfxState *state) {
doPath(state->getPath());
writePS("W*\n");
}
void PSOutputDev::clipToStrokePath(GfxState *state) {
doPath(state->getPath());
writePS("Ws\n");
}
void PSOutputDev::doPath(GfxPath *path) {
GfxSubpath *subpath;
double x0, y0, x1, y1, x2, y2, x3, y3, x4, y4;
int n, m, i, j;
n = path->getNumSubpaths();
if (n == 1 && path->getSubpath(0)->getNumPoints() == 5) {
subpath = path->getSubpath(0);
x0 = subpath->getX(0);
y0 = subpath->getY(0);
x4 = subpath->getX(4);
y4 = subpath->getY(4);
if (x4 == x0 && y4 == y0) {
x1 = subpath->getX(1);
y1 = subpath->getY(1);
x2 = subpath->getX(2);
y2 = subpath->getY(2);
x3 = subpath->getX(3);
y3 = subpath->getY(3);
if (x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) {
writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n",
x0 < x2 ? x0 : x2, y0 < y1 ? y0 : y1,
fabs(x2 - x0), fabs(y1 - y0));
return;
} else if (x0 == x3 && x1 == x2 && y0 == y1 && y2 == y3) {
writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n",
x0 < x1 ? x0 : x1, y0 < y2 ? y0 : y2,
fabs(x1 - x0), fabs(y2 - y0));
return;
}
}
}
for (i = 0; i < n; ++i) {
subpath = path->getSubpath(i);
m = subpath->getNumPoints();
writePSFmt("{0:.6g} {1:.6g} m\n", subpath->getX(0), subpath->getY(0));
j = 1;
while (j < m) {
if (subpath->getCurve(j)) {
writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g} c\n",
subpath->getX(j), subpath->getY(j),
subpath->getX(j+1), subpath->getY(j+1),
subpath->getX(j+2), subpath->getY(j+2));
j += 3;
} else {
writePSFmt("{0:.6g} {1:.6g} l\n", subpath->getX(j), subpath->getY(j));
++j;
}
}
if (subpath->isClosed()) {
writePS("h\n");
}
}
}
void PSOutputDev::drawString(GfxState *state, const GooString *s) {
GfxFont *font;
int wMode;
int *codeToGID;
GooString *s2;
double dx, dy, originX, originY;
const char *p;
UnicodeMap *uMap;
CharCode code;
Unicode *u;
char buf[8];
double *dxdy;
int dxdySize, len, nChars, uLen, n, m, i, j;
int maxGlyphInt;
CharCode maxGlyph;
// for pdftohtml, output PS without text
if( displayText == false )
return;
// check for invisible text -- this is used by Acrobat Capture
if (state->getRender() == 3) {
return;
}
// ignore empty strings
if (s->getLength() == 0) {
return;
}
// get the font
if (!(font = state->getFont())) {
return;
}
maxGlyphInt = (font->getName() ? fontMaxValidGlyph[font->getName()->toStr()] : 0);
if (maxGlyphInt < 0) maxGlyphInt = 0;
maxGlyph = (CharCode) maxGlyphInt;
wMode = font->getWMode();
// check for a subtitute 16-bit font
uMap = nullptr;
codeToGID = nullptr;
if (font->isCIDFont()) {
for (i = 0; i < font16EncLen; ++i) {
if (font->getID()->num == font16Enc[i].fontID.num &&
font->getID()->gen == font16Enc[i].fontID.gen) {
if (!font16Enc[i].enc) {
// font substitution failed, so don't output any text
return;
}
uMap = globalParams->getUnicodeMap(font16Enc[i].enc);
break;
}
}
// check for a code-to-GID map
} else {
for (i = 0; i < font8InfoLen; ++i) {
if (font->getID()->num == font8Info[i].fontID.num &&
font->getID()->gen == font8Info[i].fontID.gen) {
codeToGID = font8Info[i].codeToGID;
break;
}
}
}
// compute the positioning (dx, dy) for each char in the string
nChars = 0;
p = s->c_str();
len = s->getLength();
s2 = new GooString();
dxdySize = font->isCIDFont() ? 8 : s->getLength();
dxdy = (double *)gmallocn(2 * dxdySize, sizeof(double));
while (len > 0) {
n = font->getNextChar(p, len, &code,
&u, &uLen,
&dx, &dy, &originX, &originY);
dx *= state->getFontSize();
dy *= state->getFontSize();
if (wMode) {
dy += state->getCharSpace();
if (n == 1 && *p == ' ') {
dy += state->getWordSpace();
}
} else {
dx += state->getCharSpace();
if (n == 1 && *p == ' ') {
dx += state->getWordSpace();
}
}
dx *= state->getHorizScaling();
if (font->isCIDFont()) {
if (uMap) {
if (nChars + uLen > dxdySize) {
do {
dxdySize *= 2;
} while (nChars + uLen > dxdySize);
dxdy = (double *)greallocn(dxdy, 2 * dxdySize, sizeof(double));
}
for (i = 0; i < uLen; ++i) {
m = uMap->mapUnicode(u[i], buf, (int)sizeof(buf));
for (j = 0; j < m; ++j) {
s2->append(buf[j]);
}
//~ this really needs to get the number of chars in the target
//~ encoding - which may be more than the number of Unicode
//~ chars
dxdy[2 * nChars] = dx;
dxdy[2 * nChars + 1] = dy;
++nChars;
}
} else if (maxGlyph > 0 && code > maxGlyph) {
// Ignore this code.
// Using it will exceed the number of glyphs in the font and generate
// /rangecheck in --xyshow--
if (nChars > 0) {
dxdy[2 * (nChars-1) ] += dx;
dxdy[2 * (nChars-1) + 1 ] += dy;
}
} else {
if (nChars + 1 > dxdySize) {
dxdySize *= 2;
dxdy = (double *)greallocn(dxdy, 2 * dxdySize, sizeof(double));
}
s2->append((char)((code >> 8) & 0xff));
s2->append((char)(code & 0xff));
dxdy[2 * nChars] = dx;
dxdy[2 * nChars + 1] = dy;
++nChars;
}
} else {
if (!codeToGID || codeToGID[code] >= 0) {
s2->append((char)code);
dxdy[2 * nChars] = dx;
dxdy[2 * nChars + 1] = dy;
++nChars;
}
}
p += n;
len -= n;
}
if (uMap) {
uMap->decRefCnt();
}
if (nChars > 0) {
writePSString(s2);
writePS("\n[");
for (i = 0; i < 2 * nChars; ++i) {
if (i > 0) {
writePS("\n");
}
writePSFmt("{0:.6g}", dxdy[i]);
}
writePS("] Tj\n");
}
gfree(dxdy);
delete s2;
if (state->getRender() & 4) {
haveTextClip = true;
}
}
void PSOutputDev::beginTextObject(GfxState *state) {
}
void PSOutputDev::endTextObject(GfxState *state) {
if (haveTextClip) {
writePS("Tclip\n");
haveTextClip = false;
}
}
void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
int width, int height, bool invert,
bool interpolate, bool inlineImg) {
int len;
len = height * ((width + 7) / 8);
switch (level) {
case psLevel1:
case psLevel1Sep:
doImageL1(ref, nullptr, invert, inlineImg, str, width, height, len,
nullptr, nullptr, 0, 0, false);
break;
case psLevel2:
case psLevel2Sep:
doImageL2(ref, nullptr, invert, inlineImg, str, width, height, len,
nullptr, nullptr, 0, 0, false);
break;
case psLevel3:
case psLevel3Sep:
doImageL3(ref, nullptr, invert, inlineImg, str, width, height, len,
nullptr, nullptr, 0, 0, false);
break;
}
}
void PSOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str,
int width, int height, bool invert,
bool inlineImg, double *baseMatrix) {
if (level != psLevel1 && level != psLevel1Sep) {
maskToClippingPath(str, width, height, invert);
}
}
void PSOutputDev::unsetSoftMaskFromImageMask(GfxState * state, double *baseMatrix) {
if (level != psLevel1 && level != psLevel1Sep) {
writePS("pdfImClipEnd\n");
}
}
void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
int width, int height, GfxImageColorMap *colorMap,
bool interpolate, int *maskColors, bool inlineImg) {
int len;
len = height * ((width * colorMap->getNumPixelComps() *
colorMap->getBits() + 7) / 8);
switch (level) {
case psLevel1:
doImageL1(ref, colorMap, false, inlineImg, str,
width, height, len, maskColors, nullptr, 0, 0, false);
break;
case psLevel1Sep:
//~ handle indexed, separation, ... color spaces
doImageL1Sep(ref, colorMap, false, inlineImg, str,
width, height, len, maskColors, nullptr, 0, 0, false);
break;
case psLevel2:
case psLevel2Sep:
doImageL2(ref, colorMap, false, inlineImg, str,
width, height, len, maskColors, nullptr, 0, 0, false);
break;
case psLevel3:
case psLevel3Sep:
doImageL3(ref, colorMap, false, inlineImg, str,
width, height, len, maskColors, nullptr, 0, 0, false);
break;
}
t3Cacheable = false;
}
void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
int width, int height,
GfxImageColorMap *colorMap,
bool interpolate,
Stream *maskStr,
int maskWidth, int maskHeight,
bool maskInvert, bool maskInterpolate) {
int len;
len = height * ((width * colorMap->getNumPixelComps() *
colorMap->getBits() + 7) / 8);
switch (level) {
case psLevel1:
doImageL1(ref, colorMap, false, false, str, width, height, len,
nullptr, maskStr, maskWidth, maskHeight, maskInvert);
break;
case psLevel1Sep:
//~ handle indexed, separation, ... color spaces
doImageL1Sep(ref, colorMap, false, false, str, width, height, len,
nullptr, maskStr, maskWidth, maskHeight, maskInvert);
break;
case psLevel2:
case psLevel2Sep:
doImageL2(ref, colorMap, false, false, str, width, height, len,
nullptr, maskStr, maskWidth, maskHeight, maskInvert);
break;
case psLevel3:
case psLevel3Sep:
doImageL3(ref, colorMap, false, false, str, width, height, len,
nullptr, maskStr, maskWidth, maskHeight, maskInvert);
break;
}
t3Cacheable = false;
}
void PSOutputDev::doImageL1(Object *ref, GfxImageColorMap *colorMap,
bool invert, bool inlineImg,
Stream *str, int width, int height, int len,
int *maskColors, Stream *maskStr,
int maskWidth, int maskHeight, bool maskInvert) {
ImageStream *imgStr;
unsigned char pixBuf[gfxColorMaxComps];
GfxGray gray;
int col, x, y, c, i;
char hexBuf[32*2 + 2]; // 32 values X 2 chars/value + line ending + null
unsigned char digit, grayValue;
// explicit masking
if (maskStr && !(maskColors && colorMap)) {
maskToClippingPath(maskStr, maskWidth, maskHeight, maskInvert);
}
if ((inType3Char || preloadImagesForms) && !colorMap) {
if (inlineImg) {
// create an array
str = new FixedLengthEncoder(str, len);
str = new ASCIIHexEncoder(str);
str->reset();
col = 0;
writePS("[<");
do {
do {
c = str->getChar();
} while (c == '\n' || c == '\r');
if (c == '>' || c == EOF) {
break;
}
writePSChar(c);
++col;
// each line is: "<...data...><eol>"
// so max data length = 255 - 4 = 251
// but make it 240 just to be safe
// chunks are 2 bytes each, so we need to stop on an even col number
if (col == 240) {
writePS(">\n<");
col = 0;
}
} while (c != '>' && c != EOF);
writePS(">]\n");
writePS("0\n");
str->close();
delete str;
} else {
// make sure the image is setup, it sometimes is not like on bug #17645
setupImage(ref->getRef(), str, false);
// set up to use the array already created by setupImages()
writePSFmt("ImData_{0:d}_{1:d} 0 0\n", ref->getRefNum(), ref->getRefGen());
}
}
// image/imagemask command
if ((inType3Char || preloadImagesForms) && !colorMap) {
writePSFmt("{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);
} else if (colorMap) {
writePSFmt("{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" : "");
} else {
writePSFmt("{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" : "");
}
// image data
if (!((inType3Char || preloadImagesForms) && !colorMap)) {
if (colorMap) {
// set up to process the data stream
imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
colorMap->getBits());
imgStr->reset();
// process the data stream
i = 0;
for (y = 0; y < height; ++y) {
// write the line
for (x = 0; x < width; ++x) {
imgStr->getPixel(pixBuf);
colorMap->getGray(pixBuf, &gray);
grayValue = colToByte(gray);
if (useBinary) {
hexBuf[i++] = grayValue;
} else {
digit = grayValue / 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
digit = grayValue % 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
}
if (i >= 64) {
if (!useBinary) {
hexBuf[i++] = '\n';
}
writePSBuf(hexBuf, i);
i = 0;
}
}
}
if (i != 0) {
if (!useBinary) {
hexBuf[i++] = '\n';
}
writePSBuf(hexBuf, i);
}
str->close();
delete imgStr;
// imagemask
} else {
str->reset();
i = 0;
for (y = 0; y < height; ++y) {
for (x = 0; x < width; x += 8) {
grayValue = str->getChar();
if (useBinary) {
hexBuf[i++] = grayValue;
} else {
digit = grayValue / 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
digit = grayValue % 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a' - 10: '0');
}
if (i >= 64) {
if (!useBinary) {
hexBuf[i++] = '\n';
}
writePSBuf(hexBuf, i);
i = 0;
}
}
}
if (i != 0) {
if (!useBinary) {
hexBuf[i++] = '\n';
}
writePSBuf(hexBuf, i);
}
str->close();
}
}
if (maskStr && !(maskColors && colorMap)) {
writePS("pdfImClipEnd\n");
}
}
void PSOutputDev::doImageL1Sep(Object *ref, GfxImageColorMap *colorMap,
bool invert, bool inlineImg,
Stream *str, int width, int height, int len,
int *maskColors, Stream *maskStr,
int maskWidth, int maskHeight, bool maskInvert) {
ImageStream *imgStr;
unsigned char *lineBuf;
unsigned char pixBuf[gfxColorMaxComps];
GfxCMYK cmyk;
int x, y, i, comp;
bool checkProcessColor;
char hexBuf[32*2 + 2]; // 32 values X 2 chars/value + line ending + null
unsigned char digit;
// explicit masking
if (maskStr && !(maskColors && colorMap)) {
maskToClippingPath(maskStr, maskWidth, maskHeight, maskInvert);
}
// width, height, matrix, bits per component
writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep{5:s}\n",
width, height,
width, -height, height,
useBinary ? "Bin" : "");
// allocate a line buffer
lineBuf = (unsigned char *)gmallocn(width, 4);
// set up to process the data stream
imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
colorMap->getBits());
imgStr->reset();
// process the data stream
checkProcessColor = true;
i = 0;
for (y = 0; y < height; ++y) {
// read the line
if (checkProcessColor) {
checkProcessColor = (((psProcessCyan | psProcessMagenta | psProcessYellow | psProcessBlack) & ~processColors) != 0);
}
if (checkProcessColor) {
for (x = 0; x < width; ++x) {
imgStr->getPixel(pixBuf);
colorMap->getCMYK(pixBuf, &cmyk);
lineBuf[4*x+0] = colToByte(cmyk.c);
lineBuf[4*x+1] = colToByte(cmyk.m);
lineBuf[4*x+2] = colToByte(cmyk.y);
lineBuf[4*x+3] = colToByte(cmyk.k);
addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
colToDbl(cmyk.y), colToDbl(cmyk.k));
}
} else {
for (x = 0; x < width; ++x) {
imgStr->getPixel(pixBuf);
colorMap->getCMYK(pixBuf, &cmyk);
lineBuf[4*x+0] = colToByte(cmyk.c);
lineBuf[4*x+1] = colToByte(cmyk.m);
lineBuf[4*x+2] = colToByte(cmyk.y);
lineBuf[4*x+3] = colToByte(cmyk.k);
}
}
// write one line of each color component
if (useBinary) {
for (comp = 0; comp < 4; ++comp) {
for (x = 0; x < width; ++x) {
hexBuf[i++] = lineBuf[4*x + comp];
if (i >= 64) {
writePSBuf(hexBuf, i);
i = 0;
}
}
}
} else {
for (comp = 0; comp < 4; ++comp) {
for (x = 0; x < width; ++x) {
digit = lineBuf[4*x + comp] / 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a'-10: '0');
digit = lineBuf[4*x + comp] % 16;
hexBuf[i++] = digit + ((digit >= 10)? 'a'-10: '0');
if (i >= 64) {
hexBuf[i++] = '\n';
writePSBuf(hexBuf, i);
i = 0;
}
}
}
}
}
if (i != 0) {
if (!useBinary) {
hexBuf[i++] = '\n';
}
writePSBuf(hexBuf, i);
}
str->close();
delete imgStr;
gfree(lineBuf);
if (maskStr && !(maskColors && colorMap)) {
writePS("pdfImClipEnd\n");
}
}
void PSOutputDev::maskToClippingPath(Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert) {
ImageStream *imgStr;
unsigned char *line;
PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut;
int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize;
bool emitRect, addRect, extendRect;
int i, x0, x1, y, maskXor;
imgStr = new ImageStream(maskStr, maskWidth, 1, 1);
imgStr->reset();
rects0Len = rects1Len = rectsOutLen = 0;
rectsSize = rectsOutSize = 64;
rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize, sizeof(PSOutImgClipRect));
maskXor = maskInvert ? 1 : 0;
for (y = 0; y < maskHeight; ++y) {
if (!(line = imgStr->getLine())) {
break;
}
i = 0;
rects1Len = 0;
for (x0 = 0; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ;
for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ;
while (x0 < maskWidth || i < rects0Len) {
emitRect = addRect = extendRect = false;
if (x0 >= maskWidth) {
emitRect = true;
} else if (i >= rects0Len) {
addRect = true;
} else if (rects0[i].x0 < x0) {
emitRect = true;
} else if (x0 < rects0[i].x0) {
addRect = true;
} else if (rects0[i].x1 == x1) {
extendRect = true;
} else {
emitRect = addRect = true;
}
if (emitRect) {
if (rectsOutLen == rectsOutSize) {
rectsOutSize *= 2;
rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize, sizeof(PSOutImgClipRect));
}
rectsOut[rectsOutLen].x0 = rects0[i].x0;
rectsOut[rectsOutLen].x1 = rects0[i].x1;
rectsOut[rectsOutLen].y0 = maskHeight - y - 1;
rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1;
++rectsOutLen;
++i;
}
if (addRect || extendRect) {
if (rects1Len == rectsSize) {
rectsSize *= 2;
rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize, sizeof(PSOutImgClipRect));
rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize, sizeof(PSOutImgClipRect));
}
rects1[rects1Len].x0 = x0;
rects1[rects1Len].x1 = x1;
if (addRect) {
rects1[rects1Len].y0 = y;
}
if (extendRect) {
rects1[rects1Len].y0 = rects0[i].y0;
++i;
}
++rects1Len;
for (x0 = x1; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ;
for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ;
}
}
rectsTmp = rects0;
rects0 = rects1;
rects1 = rectsTmp;
i = rects0Len;
rects0Len = rects1Len;
rects1Len = i;
}
for (i = 0; i < rects0Len; ++i) {
if (rectsOutLen == rectsOutSize) {
rectsOutSize *= 2;
rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize, sizeof(PSOutImgClipRect));
}
rectsOut[rectsOutLen].x0 = rects0[i].x0;
rectsOut[rectsOutLen].x1 = rects0[i].x1;
rectsOut[rectsOutLen].y0 = maskHeight - y - 1;
rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1;
++rectsOutLen;
}
if (rectsOutLen < 65536/4) {
writePSFmt("{0:d} array 0\n", rectsOutLen * 4);
for (i = 0; i < rectsOutLen; ++i) {
writePSFmt("[{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);
}
writePSFmt("pop {0:d} {1:d} pdfImClip\n", maskWidth, maskHeight);
} else {
// would be over the limit of array size.
// make each rectangle path and clip.
writePS("gsave newpath\n");
for (i = 0; i < rectsOutLen; ++i) {
writePSFmt("{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,
((double)(rectsOut[i].y1 - rectsOut[i].y0))/maskHeight);
}
writePS("clip\n");
}
gfree(rectsOut);
gfree(rects0);
gfree(rects1);
delete imgStr;
maskStr->close();
}
void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap,
bool invert, bool inlineImg,
Stream *str, int width, int height, int len,
int *maskColors, Stream *maskStr,
int maskWidth, int maskHeight, bool maskInvert) {
Stream *str2;
ImageStream *imgStr;
unsigned char *line;
PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut;
int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize;
bool emitRect, addRect, extendRect;
GooString *s;
int n, numComps;
bool useLZW, useRLE, useASCII, useCompressed;
GfxSeparationColorSpace *sepCS;
GfxColor color;
GfxCMYK cmyk;
int c;
int col, i, j, x0, x1, y;
char dataBuf[4096];
rectsOutLen = 0;
// color key masking
if (maskColors && colorMap && !inlineImg) {
// can't read the stream twice for inline images -- but masking
// isn't allowed with inline images anyway
numComps = colorMap->getNumPixelComps();
imgStr = new ImageStream(str, width, numComps, colorMap->getBits());
imgStr->reset();
rects0Len = rects1Len = 0;
rectsSize = rectsOutSize = 64;
rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize,
sizeof(PSOutImgClipRect));
for (y = 0; y < height; ++y) {
if (!(line = imgStr->getLine())) {
break;
}
i = 0;
rects1Len = 0;
for (x0 = 0; x0 < width; ++x0) {
for (j = 0; j < numComps; ++j) {
if (line[x0*numComps+j] < maskColors[2*j] ||
line[x0*numComps+j] > maskColors[2*j+1]) {
break;
}
}
if (j < numComps) {
break;
}
}
for (x1 = x0; x1 < width; ++x1) {
for (j = 0; j < numComps; ++j) {
if (line[x1*numComps+j] < maskColors[2*j] ||
line[x1*numComps+j] > maskColors[2*j+1]) {
break;
}
}
if (j == numComps) {
break;
}
}
while (x0 < width || i < rects0Len) {
emitRect = addRect = extendRect = false;
if (x0 >= width) {
emitRect = true;
} else if (i >= rects0Len) {
addRect = true;
} else if (rects0[i].x0 < x0) {
emitRect = true;
} else if (x0 < rects0[i].x0) {
addRect = true;
} else if (rects0[i].x1 == x1) {
extendRect = true;
} else {
emitRect = addRect = true;
}
if (emitRect) {
if (rectsOutLen == rectsOutSize) {
rectsOutSize *= 2;
rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
sizeof(PSOutImgClipRect));
}
rectsOut[rectsOutLen].x0 = rects0[i].x0;
rectsOut[rectsOutLen].x1 = rects0[i].x1;
rectsOut[rectsOutLen].y0 = height - y - 1;
rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1;
++rectsOutLen;
++i;
}
if (addRect || extendRect) {
if (rects1Len == rectsSize) {
rectsSize *= 2;
rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize,
sizeof(PSOutImgClipRect));
rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize,
sizeof(PSOutImgClipRect));
}
rects1[rects1Len].x0 = x0;
rects1[rects1Len].x1 = x1;
if (addRect) {
rects1[rects1Len].y0 = y;
}
if (extendRect) {
rects1[rects1Len].y0 = rects0[i].y0;
++i;
}
++rects1Len;
for (x0 = x1; x0 < width; ++x0) {
for (j = 0; j < numComps; ++j) {
if (line[x0*numComps+j] < maskColors[2*j] ||
line[x0*numComps+j] > maskColors[2*j+1]) {
break;
}
}
if (j < numComps) {
break;
}
}
for (x1 = x0; x1 < width; ++x1) {
for (j = 0; j < numComps; ++j) {
if (line[x1*numComps+j] < maskColors[2*j] ||
line[x1*numComps+j] > maskColors[2*j+1]) {
break;
}
}
if (j == numComps) {
break;
}
}
}
}
rectsTmp = rects0;
rects0 = rects1;
rects1 = rectsTmp;
i = rects0Len;
rects0Len = rects1Len;
rects1Len = i;
}
for (i = 0; i < rects0Len; ++i) {
if (rectsOutLen == rectsOutSize) {
rectsOutSize *= 2;
rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
sizeof(PSOutImgClipRect));
}
rectsOut[rectsOutLen].x0 = rects0[i].x0;
rectsOut[rectsOutLen].x1 = rects0[i].x1;
rectsOut[rectsOutLen].y0 = height - y - 1;
rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1;
++rectsOutLen;
}
if (rectsOutLen < 65536/4) {
writePSFmt("{0:d} array 0\n", rectsOutLen * 4);
for (i = 0; i < rectsOutLen; ++i) {
writePSFmt("[{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);
}
writePSFmt("pop {0:d} {1:d} pdfImClip\n", width, height);
} else {
// would be over the limit of array size.
// make each rectangle path and clip.
writePS("gsave newpath\n");
for (i = 0; i < rectsOutLen; ++i) {
writePSFmt("{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,
((double)(rectsOut[i].y1 - rectsOut[i].y0))/height);
}
writePS("clip\n");
}
gfree(rectsOut);
gfree(rects0);
gfree(rects1);
delete imgStr;
str->close();
// explicit masking
} else if (maskStr) {
maskToClippingPath(maskStr, maskWidth, maskHeight, maskInvert);
}
// color space
if (colorMap) {
// Do not update the process color list for custom colors
bool isCustomColor =
(level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) &&
colorMap->getColorSpace()->getMode() == csDeviceN;
dumpColorSpaceL2(colorMap->getColorSpace(), false, !isCustomColor, false);
writePS(" setcolorspace\n");
}
// set up the image data
if (mode == psModeForm || inType3Char || preloadImagesForms) {
if (inlineImg) {
// create an array
str2 = new FixedLengthEncoder(str, len);
if (getEnableLZW()) {
str2 = new LZWEncoder(str2);
} else {
str2 = new RunLengthEncoder(str2);
}
if (useASCIIHex) {
str2 = new ASCIIHexEncoder(str2);
} else {
str2 = new ASCII85Encoder(str2);
}
str2->reset();
col = 0;
writePS((char *)(useASCIIHex ? "[<" : "[<~"));
do {
do {
c = str2->getChar();
} while (c == '\n' || c == '\r');
if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
break;
}
if (c == 'z') {
writePSChar(c);
++col;
} else {
writePSChar(c);
++col;
for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
do {
c = str2->getChar();
} while (c == '\n' || c == '\r');
if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
break;
}
writePSChar(c);
++col;
}
}
// each line is: "<~...data...~><eol>"
// so max data length = 255 - 6 = 249
// chunks are 1 or 5 bytes each, so we have to stop at 245
// but make it 240 just to be safe
if (col > 240) {
writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~"));
col = 0;
}
} while (c != (useASCIIHex ? '>' : '~') && c != EOF);
writePS((char *)(useASCIIHex ? ">\n" : "~>\n"));
// add an extra entry because the LZWDecode/RunLengthDecode filter may
// read past the end
writePS("<>]\n");
writePS("0\n");
str2->close();
delete str2;
} else {
// make sure the image is setup, it sometimes is not like on bug #17645
setupImage(ref->getRef(), str, false);
// set up to use the array already created by setupImages()
writePSFmt("ImData_{0:d}_{1:d} 0 0\n",ref->getRefNum(), ref->getRefGen());
}
}
// image dictionary
writePS("<<\n /ImageType 1\n");
// width, height, matrix, bits per component
writePSFmt(" /Width {0:d}\n", width);
writePSFmt(" /Height {0:d}\n", height);
writePSFmt(" /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
width, -height, height);
if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
writePS(" /BitsPerComponent 8\n");
} else {
writePSFmt(" /BitsPerComponent {0:d}\n",
colorMap ? colorMap->getBits() : 1);
}
// decode
if (colorMap) {
writePS(" /Decode [");
if ((level == psLevel2Sep || level == psLevel3Sep) &&
colorMap->getColorSpace()->getMode() == csSeparation) {
// this matches up with the code in the pdfImSep operator
n = (1 << colorMap->getBits()) - 1;
writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n,
colorMap->getDecodeHigh(0) * n);
} else if (colorMap->getColorSpace()->getMode() == csDeviceN) {
numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
getAlt()->getNComps();
for (i = 0; i < numComps; ++i) {
if (i > 0) {
writePS(" ");
}
writePS("0 1");
}
} else {
numComps = colorMap->getNumPixelComps();
for (i = 0; i < numComps; ++i) {
if (i > 0) {
writePS(" ");
}
writePSFmt("{0:.4g} {1:.4g}",
colorMap->getDecodeLow(i), colorMap->getDecodeHigh(i));
}
}
writePS("]\n");
} else {
writePSFmt(" /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1);
}
// data source
if (mode == psModeForm || inType3Char || preloadImagesForms) {
if (inlineImg) {
writePS(" /DataSource { pdfImStr }\n");
} else {
writePS(" /DataSource { dup 65535 ge { pop 1 add 0 } if 2 index 2"
" index get 1 index get exch 1 add exch }\n");
}
} else {
writePS(" /DataSource currentfile\n");
}
// filters
if ((mode == psModeForm || inType3Char || preloadImagesForms) &&
uncompressPreloadedImages) {
s = nullptr;
useLZW = useRLE = false;
useCompressed = false;
useASCII = false;
} else {
s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3,
" ");
if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) ||
inlineImg || !s) {
if (getEnableLZW()) {
useLZW = true;
useRLE = false;
} else {
useRLE = true;
useLZW = false;
}
useASCII = !(mode == psModeForm || inType3Char || preloadImagesForms);
useCompressed = false;
} else {
useLZW = useRLE = false;
useASCII = str->isBinary() &&
!(mode == psModeForm || inType3Char || preloadImagesForms);
useCompressed = true;
}
}
if (useASCII) {
writePSFmt(" /ASCII{0:s}Decode filter\n",
useASCIIHex ? "Hex" : "85");
}
if (useLZW) {
writePS(" /LZWDecode filter\n");
} else if (useRLE) {
writePS(" /RunLengthDecode filter\n");
}
if (useCompressed) {
writePS(s->c_str());
}
if (s) {
delete s;
}
if (mode == psModeForm || inType3Char || preloadImagesForms) {
// end of image dictionary
writePSFmt(">>\n{0:s}\n", colorMap ? "image" : "imagemask");
// get rid of the array and index
if (!inlineImg) writePS("pop ");
writePS("pop pop\n");
} else {
// cut off inline image streams at appropriate length
if (inlineImg) {
str = new FixedLengthEncoder(str, len);
} else if (useCompressed) {
str = str->getUndecodedStream();
}
// recode DeviceN data
if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
str = new DeviceNRecoder(str, width, height, colorMap);
}
// add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters
if (useLZW) {
str = new LZWEncoder(str);
} else if (useRLE) {
str = new RunLengthEncoder(str);
}
if (useASCII) {
if (useASCIIHex) {
str = new ASCIIHexEncoder(str);
} else {
str = new ASCII85Encoder(str);
}
}
// end of image dictionary
writePS(">>\n");
#ifdef OPI_SUPPORT
if (opi13Nest) {
if (inlineImg) {
// this can't happen -- OPI dictionaries are in XObjects
error(errSyntaxError, -1, "OPI in inline image");
n = 0;
} else {
// need to read the stream to count characters -- the length
// is data-dependent (because of ASCII and LZW/RLE filters)
str->reset();
n = 0;
while ((c = str->getChar()) != EOF) {
++n;
}
str->close();
}
// +6/7 for "pdfIm\n" / "pdfImM\n"
// +8 for newline + trailer
n += colorMap ? 14 : 15;
writePSFmt("%%BeginData: {0:d} Hex Bytes\n", n);
}
#endif
if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap &&
colorMap->getColorSpace()->getMode() == csSeparation && colorMap->getBits() == 8) {
color.c[0] = gfxColorComp1;
sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace();
sepCS->getCMYK(&color, &cmyk);
writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n",
colToDbl(cmyk.c), colToDbl(cmyk.m),
colToDbl(cmyk.y), colToDbl(cmyk.k),
sepCS->getName());
} else {
writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM");
}
// copy the stream data
str->reset();
i = 0;
while ((c = str->getChar()) != EOF) {
dataBuf[i++] = c;
if (i >= (int)sizeof(dataBuf)) {
writePSBuf(dataBuf, i);
i = 0;
}
}
if (i > 0) {
writePSBuf(dataBuf, i);
}
str->close();
// add newline and trailer to the end
writePSChar('\n');
writePS("%-EOD-\n");
#ifdef OPI_SUPPORT
if (opi13Nest) {
writePS("%%EndData\n");
}
#endif
// delete encoders
if (useLZW || useRLE || useASCII || inlineImg) {
delete str;
}
}
if ((maskColors && colorMap && !inlineImg) || maskStr) {
if (rectsOutLen < 65536/4) {
writePS("pdfImClipEnd\n");
} else {
writePS("grestore\n");
}
}
}
//~ this doesn't currently support OPI
void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap,
bool invert, bool inlineImg,
Stream *str, int width, int height, int len,
int *maskColors, Stream *maskStr,
int maskWidth, int maskHeight, bool maskInvert) {
Stream *str2;
GooString *s;
int n, numComps;
bool useFlate, useLZW, useRLE, useASCII, useCompressed;
bool maskUseFlate, maskUseLZW, maskUseRLE, maskUseASCII, maskUseCompressed;
GooString *maskFilters;
GfxSeparationColorSpace *sepCS;
GfxColor color;
GfxCMYK cmyk;
int c;
int col, i;
useFlate = useLZW = useRLE = useASCII = useCompressed = false;
maskUseFlate = maskUseLZW = maskUseRLE = maskUseASCII = maskUseCompressed = false;
maskFilters = nullptr; // make gcc happy
// explicit masking
if (maskStr) {
// mask data source
if ((mode == psModeForm || inType3Char || preloadImagesForms) &&
uncompressPreloadedImages) {
s = nullptr;
} else {
s = maskStr->getPSFilter(3, " ");
if (!s) {
if (getEnableFlate()) {
maskUseFlate = true;
} else if (getEnableLZW()) {
maskUseLZW = true;
} else {
maskUseRLE = true;
}
maskUseASCII = !(mode == psModeForm || inType3Char || preloadImagesForms);
} else {
maskUseASCII = maskStr->isBinary() &&
!(mode == psModeForm || inType3Char || preloadImagesForms);
maskUseCompressed = true;
}
}
maskFilters = new GooString();
if (maskUseASCII) {
maskFilters->appendf(" /ASCII{0:s}Decode filter\n",
useASCIIHex ? "Hex" : "85");
}
if (maskUseFlate) {
maskFilters->append(" /FlateDecode filter\n");
} else if (maskUseLZW) {
maskFilters->append(" /LZWDecode filter\n");
} else if (maskUseRLE) {
maskFilters->append(" /RunLengthDecode filter\n");
}
if (maskUseCompressed) {
maskFilters->append(s);
}
if (s) {
delete s;
}
if (mode == psModeForm || inType3Char || preloadImagesForms) {
writePSFmt("MaskData_{0:d}_{1:d} pdfMaskInit\n",
ref->getRefNum(), ref->getRefGen());
} else {
writePS("currentfile\n");
writePS(maskFilters->c_str());
writePS("pdfMask\n");
// add FlateEncode/LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters
if (maskUseCompressed) {
maskStr = maskStr->getUndecodedStream();
}
#ifdef ENABLE_ZLIB
if (maskUseFlate) {
maskStr = new FlateEncoder(maskStr);
} else
#endif
if (maskUseLZW) {
maskStr = new LZWEncoder(maskStr);
} else if (maskUseRLE) {
maskStr = new RunLengthEncoder(maskStr);
}
if (maskUseASCII) {
if (useASCIIHex) {
maskStr = new ASCIIHexEncoder(maskStr);
} else {
maskStr = new ASCII85Encoder(maskStr);
}
}
// copy the stream data
maskStr->reset();
while ((c = maskStr->getChar()) != EOF) {
writePSChar(c);
}
maskStr->close();
writePSChar('\n');
writePS("%-EOD-\n");
// delete encoders
if (maskUseFlate || maskUseLZW || maskUseRLE || maskUseASCII) {
delete maskStr;
}
}
}
// color space
if (colorMap) {
// Do not update the process color list for custom colors
bool isCustomColor =
(level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) &&
colorMap->getColorSpace()->getMode() == csDeviceN;
dumpColorSpaceL2(colorMap->getColorSpace(), false, !isCustomColor, false);
writePS(" setcolorspace\n");
}
// set up the image data
if (mode == psModeForm || inType3Char || preloadImagesForms) {
if (inlineImg) {
// create an array
str2 = new FixedLengthEncoder(str, len);
#ifdef ENABLE_ZLIB
if (getEnableFlate()) {
str2 = new FlateEncoder(str2);
} else
#endif
if (getEnableLZW()) {
str2 = new LZWEncoder(str2);
} else {
str2 = new RunLengthEncoder(str2);
}
if (useASCIIHex) {
str2 = new ASCIIHexEncoder(str2);
} else {
str2 = new ASCII85Encoder(str2);
}
str2->reset();
col = 0;
writePS((char *)(useASCIIHex ? "[<" : "[<~"));
do {
do {
c = str2->getChar();
} while (c == '\n' || c == '\r');
if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
break;
}
if (c == 'z') {
writePSChar(c);
++col;
} else {
writePSChar(c);
++col;
for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
do {
c = str2->getChar();
} while (c == '\n' || c == '\r');
if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
break;
}
writePSChar(c);
++col;
}
}
// each line is: "<~...data...~><eol>"
// so max data length = 255 - 6 = 249
// chunks are 1 or 5 bytes each, so we have to stop at 245
// but make it 240 just to be safe
if (col > 240) {
writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~"));
col = 0;
}
} while (c != (useASCIIHex ? '>' : '~') && c != EOF);
writePS((char *)(useASCIIHex ? ">\n" : "~>\n"));
// add an extra entry because the FlateEncode/LZWDecode/RunLengthDecode filter may
// read past the end
writePS("<>]\n");
writePS("0\n");
str2->close();
delete str2;
} else {
// make sure the image is setup, it sometimes is not like on bug #17645
setupImage(ref->getRef(), str, false);
// set up to use the array already created by setupImages()
writePSFmt("ImData_{0:d}_{1:d} 0 0\n", ref->getRefNum(), ref->getRefGen());
}
}
// explicit masking
if (maskStr) {
writePS("<<\n /ImageType 3\n");
writePS(" /InterleaveType 3\n");
writePS(" /DataDict\n");
}
// image (data) dictionary
writePSFmt("<<\n /ImageType {0:d}\n", (maskColors && colorMap) ? 4 : 1);
// color key masking
if (maskColors && colorMap) {
writePS(" /MaskColor [\n");
numComps = colorMap->getNumPixelComps();
for (i = 0; i < 2 * numComps; i += 2) {
writePSFmt(" {0:d} {1:d}\n", maskColors[i], maskColors[i+1]);
}
writePS(" ]\n");
}
// width, height, matrix, bits per component
writePSFmt(" /Width {0:d}\n", width);
writePSFmt(" /Height {0:d}\n", height);
writePSFmt(" /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
width, -height, height);
if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
writePS(" /BitsPerComponent 8\n");
} else {
writePSFmt(" /BitsPerComponent {0:d}\n",
colorMap ? colorMap->getBits() : 1);
}
// decode
if (colorMap) {
writePS(" /Decode [");
if ((level == psLevel2Sep || level == psLevel3Sep) &&
colorMap->getColorSpace()->getMode() == csSeparation) {
// this matches up with the code in the pdfImSep operator
n = (1 << colorMap->getBits()) - 1;
writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n,
colorMap->getDecodeHigh(0) * n);
} else {
numComps = colorMap->getNumPixelComps();
for (i = 0; i < numComps; ++i) {
if (i > 0) {
writePS(" ");
}
writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(i),
colorMap->getDecodeHigh(i));
}
}
writePS("]\n");
} else {
writePSFmt(" /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1);
}
// data source
if (mode == psModeForm || inType3Char || preloadImagesForms) {
if (inlineImg) {
writePS(" /DataSource { pdfImStr }\n");
} else {
writePS(" /DataSource { dup 65535 ge { pop 1 add 0 } if 2 index 2"
" index get 1 index get exch 1 add exch }\n");
}
} else {
writePS(" /DataSource currentfile\n");
}
// filters
useFlate = useLZW = useRLE = false;
useCompressed = false;
useASCII = false;
if ((mode == psModeForm || inType3Char || preloadImagesForms) &&
uncompressPreloadedImages) {
s = nullptr;
} else {
s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3,
" ");
if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) ||
inlineImg || !s) {
if (getEnableFlate()) {
useFlate = true;
} else if (getEnableLZW()) {
useLZW = true;
} else {
useRLE = true;
}
useASCII = !(mode == psModeForm || inType3Char || preloadImagesForms);
} else {
useASCII = str->isBinary() &&
!(mode == psModeForm || inType3Char || preloadImagesForms);
useCompressed = true;
}
}
if (useASCII) {
writePSFmt(" /ASCII{0:s}Decode filter\n",
useASCIIHex ? "Hex" : "85");
}
if (useFlate) {
writePS(" /FlateDecode filter\n");
} else if (useLZW) {
writePS(" /LZWDecode filter\n");
} else if (useRLE) {
writePS(" /RunLengthDecode filter\n");
}
if (useCompressed) {
writePS(s->c_str());
}
if (s) {
delete s;
}
// end of image (data) dictionary
writePS(">>\n");
// explicit masking
if (maskStr) {
writePS(" /MaskDict\n");
writePS("<<\n");
writePS(" /ImageType 1\n");
writePSFmt(" /Width {0:d}\n", maskWidth);
writePSFmt(" /Height {0:d}\n", maskHeight);
writePSFmt(" /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
maskWidth, -maskHeight, maskHeight);
writePS(" /BitsPerComponent 1\n");
writePSFmt(" /Decode [{0:d} {1:d}]\n",
maskInvert ? 1 : 0, maskInvert ? 0 : 1);
// mask data source
if (mode == psModeForm || inType3Char || preloadImagesForms) {
writePS(" /DataSource {pdfMaskSrc}\n");
writePS(maskFilters->c_str());
} else {
writePS(" /DataSource maskStream\n");
}
delete maskFilters;
writePS(">>\n");
writePS(">>\n");
}
if (mode == psModeForm || inType3Char || preloadImagesForms) {
// image command
writePSFmt("{0:s}\n", colorMap ? "image" : "imagemask");
} else {
if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap &&
colorMap->getColorSpace()->getMode() == csSeparation && colorMap->getBits() == 8) {
color.c[0] = gfxColorComp1;
sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace();
sepCS->getCMYK(&color, &cmyk);
writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n",
colToDbl(cmyk.c), colToDbl(cmyk.m),
colToDbl(cmyk.y), colToDbl(cmyk.k),
sepCS->getName());
} else {
writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM");
}
}
// get rid of the array and index
if (mode == psModeForm || inType3Char || preloadImagesForms) {
if (!inlineImg) writePS("pop ");
writePS("pop pop\n");
// image data
} else {
// cut off inline image streams at appropriate length
if (inlineImg) {
str = new FixedLengthEncoder(str, len);
} else if (useCompressed) {
str = str->getUndecodedStream();
}
// add FlateEncode/LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters
#ifdef ENABLE_ZLIB
if (useFlate) {
str = new FlateEncoder(str);
} else
#endif
if (useLZW) {
str = new LZWEncoder(str);
} else if (useRLE) {
str = new RunLengthEncoder(str);
}
if (useASCII) {
if (useASCIIHex) {
str = new ASCIIHexEncoder(str);
} else {
str = new ASCII85Encoder(str);
}
}
// copy the stream data
str->reset();
while ((c = str->getChar()) != EOF) {
writePSChar(c);
}
str->close();
// add newline and trailer to the end
writePSChar('\n');
writePS("%-EOD-\n");
// delete encoders
if (useFlate || useLZW || useRLE || useASCII || inlineImg) {
delete str;
}
}
// close the mask stream
if (maskStr) {
if (!(mode == psModeForm || inType3Char || preloadImagesForms)) {
writePS("pdfMaskEnd\n");
}
}
}
void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
bool genXform, bool updateColors,
bool map01) {
GfxCalGrayColorSpace *calGrayCS;
GfxCalRGBColorSpace *calRGBCS;
GfxLabColorSpace *labCS;
GfxIndexedColorSpace *indexedCS;
GfxSeparationColorSpace *separationCS;
GfxDeviceNColorSpace *deviceNCS;
GfxColorSpace *baseCS;
unsigned char *lookup, *p;
double x[gfxColorMaxComps], y[gfxColorMaxComps];
double low[gfxColorMaxComps], range[gfxColorMaxComps];
GfxColor color;
GfxCMYK cmyk;
Function *func;
int n, numComps, numAltComps;
int byte;
int i, j, k;
switch (colorSpace->getMode()) {
case csDeviceGray:
writePS("/DeviceGray");
if (genXform) {
writePS(" {}");
}
if (updateColors) {
processColors |= psProcessBlack;
}
break;
case csCalGray:
calGrayCS = (GfxCalGrayColorSpace *)colorSpace;
writePS("[/CIEBasedA <<\n");
writePSFmt(" /DecodeA {{{0:.4g} exp}} bind\n", calGrayCS->getGamma());
writePSFmt(" /MatrixA [{0:.4g} {1:.4g} {2:.4g}]\n",
calGrayCS->getWhiteX(), calGrayCS->getWhiteY(),
calGrayCS->getWhiteZ());
writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
calGrayCS->getWhiteX(), calGrayCS->getWhiteY(),
calGrayCS->getWhiteZ());
writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
calGrayCS->getBlackX(), calGrayCS->getBlackY(),
calGrayCS->getBlackZ());
writePS(">>]");
if (genXform) {
writePS(" {}");
}
if (updateColors) {
processColors |= psProcessBlack;
}
break;
case csDeviceRGB:
writePS("/DeviceRGB");
if (genXform) {
writePS(" {}");
}
if (updateColors) {
processColors |= psProcessCMYK;
}
break;
case csCalRGB:
calRGBCS = (GfxCalRGBColorSpace *)colorSpace;
writePS("[/CIEBasedABC <<\n");
writePSFmt(" /DecodeABC [{{{0:.4g} exp}} bind {{{1:.4g} exp}} bind {{{2:.4g} exp}} bind]\n",
calRGBCS->getGammaR(), calRGBCS->getGammaG(),
calRGBCS->getGammaB());
writePSFmt(" /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], calRGBCS->getMatrix()[5],
calRGBCS->getMatrix()[6], calRGBCS->getMatrix()[7],
calRGBCS->getMatrix()[8]);
writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
calRGBCS->getWhiteX(), calRGBCS->getWhiteY(),
calRGBCS->getWhiteZ());
writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
calRGBCS->getBlackX(), calRGBCS->getBlackY(),
calRGBCS->getBlackZ());
writePS(">>]");
if (genXform) {
writePS(" {}");
}
if (updateColors) {
processColors |= psProcessCMYK;
}
break;
case csDeviceCMYK:
writePS("/DeviceCMYK");
if (genXform) {
writePS(" {}");
}
if (updateColors) {
processColors |= psProcessCMYK;
}
break;
case csLab:
labCS = (GfxLabColorSpace *)colorSpace;
writePS("[/CIEBasedABC <<\n");
if (map01) {
writePS(" /RangeABC [0 1 0 1 0 1]\n");
writePSFmt(" /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,
(labCS->getBMax() - labCS->getBMin()) / 200.0,
labCS->getBMin() / 200.0);
} else {
writePSFmt(" /RangeABC [0 100 {0:.4g} {1:.4g} {2:.4g} {3:.4g}]\n",
labCS->getAMin(), labCS->getAMax(),
labCS->getBMin(), labCS->getBMax());
writePS(" /DecodeABC [{16 add 116 div} bind {500 div} bind {200 div} bind]\n");
}
writePS(" /MatrixABC [1 1 1 1 0 0 0 0 -1]\n");
writePS(" /DecodeLMN\n");
writePS(" [{dup 6 29 div ge {dup dup mul mul}\n");
writePSFmt(" {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n",
labCS->getWhiteX());
writePS(" {dup 6 29 div ge {dup dup mul mul}\n");
writePSFmt(" {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n",
labCS->getWhiteY());
writePS(" {dup 6 29 div ge {dup dup mul mul}\n");
writePSFmt(" {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind]\n",
labCS->getWhiteZ());
writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
labCS->getWhiteX(), labCS->getWhiteY(), labCS->getWhiteZ());
writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
labCS->getBlackX(), labCS->getBlackY(), labCS->getBlackZ());
writePS(">>]");
if (genXform) {
writePS(" {}");
}
if (updateColors) {
processColors |= psProcessCMYK;
}
break;
case csICCBased:
// there is no transform function to the alternate color space, so
// we can use it directly
dumpColorSpaceL2(((GfxICCBasedColorSpace *)colorSpace)->getAlt(),
genXform, updateColors, false);
break;
case csIndexed:
indexedCS = (GfxIndexedColorSpace *)colorSpace;
baseCS = indexedCS->getBase();
writePS("[/Indexed ");
dumpColorSpaceL2(baseCS, false, false, true);
n = indexedCS->getIndexHigh();
numComps = baseCS->getNComps();
lookup = indexedCS->getLookup();
writePSFmt(" {0:d} <\n", n);
if (baseCS->getMode() == csDeviceN && level != psLevel3 && level != psLevel3Sep) {
func = ((GfxDeviceNColorSpace *)baseCS)->getTintTransformFunc();
baseCS->getDefaultRanges(low, range, indexedCS->getIndexHigh());
if (((GfxDeviceNColorSpace *)baseCS)->getAlt()->getMode() == csLab) {
labCS = (GfxLabColorSpace *)((GfxDeviceNColorSpace *)baseCS)->getAlt();
} else {
labCS = nullptr;
}
numAltComps = ((GfxDeviceNColorSpace *)baseCS)->getAlt()->getNComps();
p = lookup;
for (i = 0; i <= n; i += 8) {
writePS(" ");
for (j = i; j < i+8 && j <= n; ++j) {
for (k = 0; k < numComps; ++k) {
x[k] = low[k] + (*p++ / 255.0) * range[k];
}
func->transform(x, y);
if (labCS) {
y[0] /= 100.0;
y[1] = (y[1] - labCS->getAMin()) /
(labCS->getAMax() - labCS->getAMin());
y[2] = (y[2] - labCS->getBMin()) /
(labCS->getBMax() - labCS->getBMin());
}
for (k = 0; k < numAltComps; ++k) {
byte = (int)(y[k] * 255 + 0.5);
if (byte < 0) {
byte = 0;
} else if (byte > 255) {
byte = 255;
}
writePSFmt("{0:02x}", byte);
}
if (updateColors) {
color.c[0] = dblToCol(j);
indexedCS->getCMYK(&color, &cmyk);
addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
colToDbl(cmyk.y), colToDbl(cmyk.k));
}
}
writePS("\n");
}
} else {
for (i = 0; i <= n; i += 8) {
writePS(" ");
for (j = i; j < i+8 && j <= n; ++j) {
for (k = 0; k < numComps; ++k) {
writePSFmt("{0:02x}", lookup[j * numComps + k]);
}
if (updateColors) {
color.c[0] = dblToCol(j);
indexedCS->getCMYK(&color, &cmyk);
addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
colToDbl(cmyk.y), colToDbl(cmyk.k));
}
}
writePS("\n");
}
}
writePS(">]");
if (genXform) {
writePS(" {}");
}
break;
case csSeparation:
separationCS = (GfxSeparationColorSpace *)colorSpace;
writePS("[/Separation ");
writePSString(separationCS->getName());
writePS(" ");
dumpColorSpaceL2(separationCS->getAlt(), false, false, false);
writePS("\n");
cvtFunction(separationCS->getFunc());
writePS("]");
if (genXform) {
writePS(" {}");
}
if (updateColors) {
addCustomColor(separationCS);
}
break;
case csDeviceN:
deviceNCS = (GfxDeviceNColorSpace *)colorSpace;
if (level == psLevel3 || level == psLevel3Sep) {
writePS("[/DeviceN\n");
writePS(" [ ");
for (i = 0; i < deviceNCS->getNComps(); i++) {
writePSString(deviceNCS->getColorantName(i));
writePS(" ");
}
writePS("]\n");
dumpColorSpaceL2(deviceNCS->getAlt(), false, updateColors, false);
writePS("\n");
cvtFunction(deviceNCS->getTintTransformFunc(), map01 && deviceNCS->getAlt()->getMode() == csLab);
writePS("]\n");
if (genXform) {
writePS(" {}");
}
} else {
// DeviceN color spaces are a Level 3 PostScript feature.
dumpColorSpaceL2(deviceNCS->getAlt(), false, updateColors, map01);
if (genXform) {
writePS(" ");
cvtFunction(deviceNCS->getTintTransformFunc());
}
}
break;
case csPattern:
//~ unimplemented
break;
}
}
#ifdef OPI_SUPPORT
void PSOutputDev::opiBegin(GfxState *state, Dict *opiDict) {
if (generateOPI) {
Object dict = opiDict->lookup("2.0");
if (dict.isDict()) {
opiBegin20(state, dict.getDict());
} else {
dict = opiDict->lookup("1.3");
if (dict.isDict()) {
opiBegin13(state, dict.getDict());
}
}
}
}
void PSOutputDev::opiBegin20(GfxState *state, Dict *dict) {
double width, height, left, right, top, bottom;
int w, h;
writePS("%%BeginOPI: 2.0\n");
writePS("%%Distilled\n");
Object obj1 = dict->lookup("F");
Object obj2 = getFileSpecName(&obj1);
if (obj2.isString()) {
writePSFmt("%%ImageFileName: {0:t}\n", obj2.getString());
}
obj1 = dict->lookup("MainImage");
if (obj1.isString()) {
writePSFmt("%%MainImage: {0:t}\n", obj1.getString());
}
//~ ignoring 'Tags' entry
//~ need to use writePSString() and deal with >255-char lines
obj1 = dict->lookup("Size");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
obj2 = obj1.arrayGet(0);
width = obj2.getNum();
obj2 = obj1.arrayGet(1);
height = obj2.getNum();
writePSFmt("%%ImageDimensions: {0:.6g} {1:.6g}\n", width, height);
}
obj1 = dict->lookup("CropRect");
if (obj1.isArray() && obj1.arrayGetLength() == 4) {
obj2 = obj1.arrayGet(0);
left = obj2.getNum();
obj2 = obj1.arrayGet(1);
top = obj2.getNum();
obj2 = obj1.arrayGet(2);
right = obj2.getNum();
obj2 = obj1.arrayGet(3);
bottom = obj2.getNum();
writePSFmt("%%ImageCropRect: {0:.6g} {1:.6g} {2:.6g} {3:.6g}\n",
left, top, right, bottom);
}
obj1 = dict->lookup("Overprint");
if (obj1.isBool()) {
writePSFmt("%%ImageOverprint: {0:s}\n", obj1.getBool() ? "true" : "false");
}
obj1 = dict->lookup("Inks");
if (obj1.isName()) {
writePSFmt("%%ImageInks: {0:s}\n", obj1.getName());
} else if (obj1.isArray() && obj1.arrayGetLength() >= 1) {
obj2 = obj1.arrayGet(0);
if (obj2.isName()) {
writePSFmt("%%ImageInks: {0:s} {1:d}",
obj2.getName(), (obj1.arrayGetLength() - 1) / 2);
for (int i = 1; i+1 < obj1.arrayGetLength(); i += 2) {
Object obj3 = obj1.arrayGet(i);
Object obj4 = obj1.arrayGet(i+1);
if (obj3.isString() && obj4.isNum()) {
writePS(" ");
writePSString(obj3.getString());
writePSFmt(" {0:.6g}", obj4.getNum());
}
}
writePS("\n");
}
}
writePS("gsave\n");
writePS("%%BeginIncludedImage\n");
obj1 = dict->lookup("IncludedImageDimensions");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
obj2 = obj1.arrayGet(0);
w = obj2.getInt();
obj2 = obj1.arrayGet(1);
h = obj2.getInt();
writePSFmt("%%IncludedImageDimensions: {0:d} {1:d}\n", w, h);
}
obj1 = dict->lookup("IncludedImageQuality");
if (obj1.isNum()) {
writePSFmt("%%IncludedImageQuality: {0:.6g}\n", obj1.getNum());
}
++opi20Nest;
}
void PSOutputDev::opiBegin13(GfxState *state, Dict *dict) {
int left, right, top, bottom, samples, bits, width, height;
double c, m, y, k;
double llx, lly, ulx, uly, urx, ury, lrx, lry;
double tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry;
double horiz, vert;
int i, j;
writePS("save\n");
writePS("/opiMatrix2 matrix currentmatrix def\n");
writePS("opiMatrix setmatrix\n");
Object obj1 = dict->lookup("F");
Object obj2 = getFileSpecName(&obj1);
if (obj2.isString()) {
writePSFmt("%ALDImageFileName: {0:t}\n", obj2.getString());
}
obj1 = dict->lookup("CropRect");
if (obj1.isArray() && obj1.arrayGetLength() == 4) {
obj2 = obj1.arrayGet(0);
left = obj2.getInt();
obj2 = obj1.arrayGet(1);
top = obj2.getInt();
obj2 = obj1.arrayGet(2);
right = obj2.getInt();
obj2 = obj1.arrayGet(3);
bottom = obj2.getInt();
writePSFmt("%ALDImageCropRect: {0:d} {1:d} {2:d} {3:d}\n",
left, top, right, bottom);
}
obj1 = dict->lookup("Color");
if (obj1.isArray() && obj1.arrayGetLength() == 5) {
obj2 = obj1.arrayGet(0);
c = obj2.getNum();
obj2 = obj1.arrayGet(1);
m = obj2.getNum();
obj2 = obj1.arrayGet(2);
y = obj2.getNum();
obj2 = obj1.arrayGet(3);
k = obj2.getNum();
obj2 = obj1.arrayGet(4);
if (obj2.isString()) {
writePSFmt("%ALDImageColor: {0:.4g} {1:.4g} {2:.4g} {3:.4g} ",
c, m, y, k);
writePSString(obj2.getString());
writePS("\n");
}
}
obj1 = dict->lookup("ColorType");
if (obj1.isName()) {
writePSFmt("%ALDImageColorType: {0:s}\n", obj1.getName());
}
//~ ignores 'Comments' entry
//~ need to handle multiple lines
obj1 = dict->lookup("CropFixed");
if (obj1.isArray()) {
obj2 = obj1.arrayGet(0);
ulx = obj2.getNum();
obj2 = obj1.arrayGet(1);
uly = obj2.getNum();
obj2 = obj1.arrayGet(2);
lrx = obj2.getNum();
obj2 = obj1.arrayGet(3);
lry = obj2.getNum();
writePSFmt("%ALDImageCropFixed: {0:.6g} {1:.6g} {2:.6g} {3:.6g}\n",
ulx, uly, lrx, lry);
}
obj1 = dict->lookup("GrayMap");
if (obj1.isArray()) {
writePS("%ALDImageGrayMap:");
for (i = 0; i < obj1.arrayGetLength(); i += 16) {
if (i > 0) {
writePS("\n%%+");
}
for (j = 0; j < 16 && i+j < obj1.arrayGetLength(); ++j) {
obj2 = obj1.arrayGet(i+j);
writePSFmt(" {0:d}", obj2.getInt());
}
}
writePS("\n");
}
obj1 = dict->lookup("ID");
if (obj1.isString()) {
writePSFmt("%ALDImageID: {0:t}\n", obj1.getString());
}
obj1 = dict->lookup("ImageType");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
obj2 = obj1.arrayGet(0);
samples = obj2.getInt();
obj2 = obj1.arrayGet(1);
bits = obj2.getInt();
writePSFmt("%ALDImageType: {0:d} {1:d}\n", samples, bits);
}
dict->lookup("Overprint");
if (obj1.isBool()) {
writePSFmt("%ALDImageOverprint: {0:s}\n",
obj1.getBool() ? "true" : "false");
}
obj1 = dict->lookup("Position");
if (obj1.isArray() && obj1.arrayGetLength() == 8) {
obj2 = obj1.arrayGet(0);
llx = obj2.getNum();
obj2 = obj1.arrayGet(1);
lly = obj2.getNum();
obj2 = obj1.arrayGet(2);
ulx = obj2.getNum();
obj2 = obj1.arrayGet(3);
uly = obj2.getNum();
obj2 = obj1.arrayGet(4);
urx = obj2.getNum();
obj2 = obj1.arrayGet(5);
ury = obj2.getNum();
obj2 = obj1.arrayGet(6);
lrx = obj2.getNum();
obj2 = obj1.arrayGet(7);
lry = obj2.getNum();
opiTransform(state, llx, lly, &tllx, &tlly);
opiTransform(state, ulx, uly, &tulx, &tuly);
opiTransform(state, urx, ury, &turx, &tury);
opiTransform(state, lrx, lry, &tlrx, &tlry);
writePSFmt("%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);
}
obj1 = dict->lookup("Resolution");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
obj2 = obj1.arrayGet(0);
horiz = obj2.getNum();
obj2 = obj1.arrayGet(1);
vert = obj2.getNum();
writePSFmt("%ALDImageResoution: {0:.6g} {1:.6g}\n", horiz, vert);
}
obj1 = dict->lookup("Size");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
obj2 = obj1.arrayGet(0);
width = obj2.getInt();
obj2 = obj1.arrayGet(1);
height = obj2.getInt();
writePSFmt("%ALDImageDimensions: {0:d} {1:d}\n", width, height);
}
//~ ignoring 'Tags' entry
//~ need to use writePSString() and deal with >255-char lines
obj1 = dict->lookup("Tint");
if (obj1.isNum()) {
writePSFmt("%ALDImageTint: {0:.6g}\n", obj1.getNum());
}
obj1 = dict->lookup("Transparency");
if (obj1.isBool()) {
writePSFmt("%ALDImageTransparency: {0:s}\n",
obj1.getBool() ? "true" : "false");
}
writePS("%%BeginObject: image\n");
writePS("opiMatrix2 setmatrix\n");
++opi13Nest;
}
// Convert PDF user space coordinates to PostScript default user space
// coordinates. This has to account for both the PDF CTM and the
// PSOutputDev page-fitting transform.
void PSOutputDev::opiTransform(GfxState *state, double x0, double y0,
double *x1, double *y1) {
double t;
state->transform(x0, y0, x1, y1);
*x1 += tx;
*y1 += ty;
if (rotate == 90) {
t = *x1;
*x1 = -*y1;
*y1 = t;
} else if (rotate == 180) {
*x1 = -*x1;
*y1 = -*y1;
} else if (rotate == 270) {
t = *x1;
*x1 = *y1;
*y1 = -t;
}
*x1 *= xScale;
*y1 *= yScale;
}
void PSOutputDev::opiEnd(GfxState *state, Dict *opiDict) {
if (generateOPI) {
Object dict = opiDict->lookup("2.0");
if (dict.isDict()) {
writePS("%%EndIncludedImage\n");
writePS("%%EndOPI\n");
writePS("grestore\n");
--opi20Nest;
} else {
dict = opiDict->lookup("1.3");
if (dict.isDict()) {
writePS("%%EndObject\n");
writePS("restore\n");
--opi13Nest;
}
}
}
}
#endif // OPI_SUPPORT
void PSOutputDev::type3D0(GfxState *state, double wx, double wy) {
writePSFmt("{0:.6g} {1:.6g} setcharwidth\n", wx, wy);
writePS("q\n");
t3NeedsRestore = true;
}
void PSOutputDev::type3D1(GfxState *state, double wx, double wy,
double llx, double lly, double urx, double ury) {
t3WX = wx;
t3WY = wy;
t3LLX = llx;
t3LLY = lly;
t3URX = urx;
t3URY = ury;
t3String = new GooString();
writePS("q\n");
t3FillColorOnly = true;
t3Cacheable = true;
t3NeedsRestore = true;
}
void PSOutputDev::drawForm(Ref id) {
writePSFmt("f_{0:d}_{1:d}\n", id.num, id.gen);
}
void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) {
Stream *str;
int c;
if ((level == psLevel1 || level == psLevel1Sep) && level1Stream) {
str = level1Stream;
} else {
str = psStream;
}
str->reset();
while ((c = str->getChar()) != EOF) {
writePSChar(c);
}
str->close();
}
//~ can nextFunc be reset to 0 -- maybe at the start of each page?
//~ or maybe at the start of each color space / pattern?
void PSOutputDev::cvtFunction(const Function *func, bool invertPSFunction) {
const SampledFunction *func0;
const ExponentialFunction *func2;
const StitchingFunction *func3;
const PostScriptFunction *func4;
int thisFunc, m, n, nSamples, i, j, k;
switch (func->getType()) {
case -1: // identity
writePS("{}\n");
break;
case 0: // sampled
func0 = (const SampledFunction *)func;
thisFunc = nextFunc++;
m = func0->getInputSize();
n = func0->getOutputSize();
nSamples = n;
for (i = 0; i < m; ++i) {
nSamples *= func0->getSampleSize(i);
}
writePSFmt("/xpdfSamples{0:d} [\n", thisFunc);
for (i = 0; i < nSamples; ++i) {
writePSFmt("{0:.6g}\n", func0->getSamples()[i]);
}
writePS("] def\n");
writePSFmt("{{ {0:d} array {1:d} array {2:d} 2 roll\n", 2*m, m, m+2);
// [e01] [efrac] x0 x1 ... xm-1
for (i = m-1; i >= 0; --i) {
// [e01] [efrac] x0 x1 ... xi
writePSFmt("{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));
// [e01] [efrac] x0 x1 ... xi-1 xi'
writePSFmt("dup 0 lt {{ pop 0 }} {{ dup {0:d} gt {{ pop {1:d} }} if }} ifelse\n",
func0->getSampleSize(i) - 1, func0->getSampleSize(i) - 1);
// [e01] [efrac] x0 x1 ... xi-1 xi'
writePS("dup floor cvi exch dup ceiling cvi exch 2 index sub\n");
// [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi') xi'-floor(xi')
writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, i);
// [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi')
writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, 2*i+1);
// [e01] [efrac] x0 x1 ... xi-1 floor(xi')
writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+2, 2*i);
// [e01] [efrac] x0 x1 ... xi-1
}
// [e01] [efrac]
for (i = 0; i < n; ++i) {
// [e01] [efrac] y(0) ... y(i-1)
for (j = 0; j < (1<<m); ++j) {
// [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(j-1)
writePSFmt("xpdfSamples{0:d}\n", thisFunc);
k = m - 1;
writePSFmt("{0:d} index {1:d} get\n", i+j+2, 2 * k + ((j >> k) & 1));
for (k = m - 2; k >= 0; --k) {
writePSFmt("{0:d} mul {1:d} index {2:d} get add\n",
func0->getSampleSize(k),
i + j + 3,
2 * k + ((j >> k) & 1));
}
if (n > 1) {
writePSFmt("{0:d} mul {1:d} add ", n, i);
}
writePS("get\n");
}
// [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^m-1)
for (j = 0; j < m; ++j) {
// [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^(m-j)-1)
for (k = 0; k < (1 << (m - j)); k += 2) {
// [e01] [efrac] y(0) ... y(i-1) <k/2 s' values> <2^(m-j)-k s values>
writePSFmt("{0:d} index {1:d} get dup\n",
i + k/2 + (1 << (m-j)) - k, j);
writePS("3 2 roll mul exch 1 exch sub 3 2 roll mul add\n");
writePSFmt("{0:d} 1 roll\n", k/2 + (1 << (m-j)) - k - 1);
}
// [e01] [efrac] s'(0) s'(1) ... s(2^(m-j-1)-1)
}
// [e01] [efrac] y(0) ... y(i-1) s
writePSFmt("{0:.6g} mul {1:.6g} add\n",
func0->getDecodeMax(i) - func0->getDecodeMin(i),
func0->getDecodeMin(i));
writePSFmt("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));
// [e01] [efrac] y(0) ... y(i-1) y(i)
}
// [e01] [efrac] y(0) ... y(n-1)
writePSFmt("{0:d} {1:d} roll pop pop \n", n+2, n);
if (invertPSFunction) {
for (i = 0; i < n; ++i) {
writePSFmt("{0:d} -1 roll ", n);
writePSFmt("{0:.6g} sub {1:.6g} div ", func0->getRangeMin(i), func0->getRangeMax(i) - func0->getRangeMin(i));
}
}
writePS("}\n");
break;
case 2: // exponential
func2 = (const ExponentialFunction *)func;
n = func2->getOutputSize();
writePSFmt("{{ dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n",
func2->getDomainMin(0), func2->getDomainMin(0),
func2->getDomainMax(0), func2->getDomainMax(0));
// x
for (i = 0; i < n; ++i) {
// x y(0) .. y(i-1)
writePSFmt("{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]);
if (func2->getHasRange()) {
writePSFmt("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));
}
}
// x y(0) .. y(n-1)
writePSFmt("{0:d} {1:d} roll pop \n", n+1, n);
if (invertPSFunction && func2->getHasRange()) {
for (i = 0; i < n; ++i) {
writePSFmt("{0:d} -1 roll ", n);
writePSFmt("{0:.6g} sub {1:.6g} div ", func2->getRangeMin(i), func2->getRangeMax(i) - func2->getRangeMin(i));
}
}
writePS("}\n");
break;
case 3: // stitching
func3 = (const StitchingFunction *)func;
thisFunc = nextFunc++;
for (i = 0; i < func3->getNumFuncs(); ++i) {
cvtFunction(func3->getFunc(i));
writePSFmt("/xpdfFunc{0:d}_{1:d} exch def\n", thisFunc, i);
}
writePSFmt("{{ dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n",
func3->getDomainMin(0), func3->getDomainMin(0),
func3->getDomainMax(0), func3->getDomainMax(0));
for (i = 0; i < func3->getNumFuncs() - 1; ++i) {
writePSFmt("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);
}
writePSFmt("{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);
for (i = 0; i < func3->getNumFuncs() - 1; ++i) {
writePS("} ifelse\n");
}
if (invertPSFunction && func3->getHasRange()) {
n = func3->getOutputSize();
for (i = 0; i < n; ++i) {
writePSFmt("{0:d} -1 roll ", n);
writePSFmt("{0:.6g} sub {1:.6g} div ", func3->getRangeMin(i), func3->getRangeMax(i) - func3->getRangeMin(i));
}
}
writePS("}\n");
break;
case 4: // PostScript
func4 = (const PostScriptFunction *)func;
if (invertPSFunction) {
GooString *codeString = new GooString(func4->getCodeString());
for (i = codeString->getLength() -1; i > 0; i--) {
if (codeString->getChar(i) == '}') {
codeString->del(i);
break;
}
}
writePS(codeString->c_str());
writePS("\n");
delete codeString;
n = func4->getOutputSize();
for (i = 0; i < n; ++i) {
writePSFmt("{0:d} -1 roll ", n);
writePSFmt("{0:.6g} sub {1:.6g} div ", func4->getRangeMin(i), func4->getRangeMax(i) - func4->getRangeMin(i));
}
writePS("}\n");
} else {
writePS(func4->getCodeString()->c_str());
writePS("\n");
}
break;
}
}
void PSOutputDev::writePSChar(char c) {
if (t3String) {
t3String->append(c);
} else {
(*outputFunc)(outputStream, &c, 1);
}
}
void PSOutputDev::writePS(const char *s) {
if (t3String) {
t3String->append(s);
} else {
(*outputFunc)(outputStream, s, strlen(s));
}
}
void PSOutputDev::writePSBuf(const char *s, int len) {
if (t3String) {
for (int i = 0; i < len; i++) {
t3String->append(s[i]);
}
} else {
(*outputFunc)(outputStream, s, len);
}
}
void PSOutputDev::writePSFmt(const char *fmt, ...) {
va_list args;
GooString *buf;
va_start(args, fmt);
if (t3String) {
t3String->appendfv((char *)fmt, args);
} else {
buf = GooString::formatv((char *)fmt, args);
(*outputFunc)(outputStream, buf->c_str(), buf->getLength());
delete buf;
}
va_end(args);
}
void PSOutputDev::writePSString(const GooString *s) {
unsigned char *p;
int n, line;
char buf[8];
writePSChar('(');
line = 1;
for (p = (unsigned char *)s->c_str(), n = s->getLength(); n; ++p, --n) {
if (line >= 64) {
writePSChar('\\');
writePSChar('\n');
line = 0;
}
if (*p == '(' || *p == ')' || *p == '\\') {
writePSChar('\\');
writePSChar((char)*p);
line += 2;
} else if (*p < 0x20 || *p >= 0x80) {
sprintf(buf, "\\%03o", *p);
writePS(buf);
line += 4;
} else {
writePSChar((char)*p);
++line;
}
}
writePSChar(')');
}
void PSOutputDev::writePSName(const char *s) {
const char *p;
char c;
p = s;
while ((c = *p++)) {
if (c <= (char)0x20 || c >= (char)0x7f ||
c == '(' || c == ')' || c == '<' || c == '>' ||
c == '[' || c == ']' || c == '{' || c == '}' ||
c == '/' || c == '%' || c == '\\') {
writePSFmt("#{0:02x}", c & 0xff);
} else {
writePSChar(c);
}
}
}
GooString *PSOutputDev::filterPSName(const GooString *name) {
GooString *name2;
char buf[8];
int i;
char c;
name2 = new GooString();
// ghostscript chokes on names that begin with out-of-limits
// numbers, e.g., 1e4foo is handled correctly (as a name), but
// 1e999foo generates a limitcheck error
c = name->getChar(0);
if (c >= '0' && c <= '9') {
name2->append('f');
}
for (i = 0; i < name->getLength(); ++i) {
c = name->getChar(i);
if (c <= (char)0x20 || c >= (char)0x7f ||
c == '(' || c == ')' || c == '<' || c == '>' ||
c == '[' || c == ']' || c == '{' || c == '}' ||
c == '/' || c == '%') {
sprintf(buf, "#%02x", c & 0xff);
name2->append(buf);
} else {
name2->append(c);
}
}
return name2;
}
// Convert GooString to GooString, with appropriate escaping
// of things that can't appear in a label
// This is heavily based on the writePSTextLine() method
GooString* PSOutputDev::filterPSLabel(GooString *label, bool *needParens) {
int i, step;
bool isNumeric;
// - DSC comments must be printable ASCII; control chars and
// backslashes have to be escaped (we do cheap UCS2-to-ASCII
// conversion by simply ignoring the high byte)
// - parentheses are escaped. this isn't strictly necessary for matched
// parentheses, but shouldn't be a problem
// - lines are limited to 255 chars (we limit to 200 here to allow
// for the keyword, which was emitted by the caller)
GooString *label2 = new GooString();
int labelLength = label->getLength();
if (labelLength == 0) {
isNumeric = false;
} else {
// this gets changed later if we find a non-numeric character
isNumeric = true;
}
if ( (labelLength >= 2) &&
( (label->getChar(0) & 0xff) == 0xfe) &&
( (label->getChar(1) & 0xff) == 0xff) ) {
// UCS2 mode
i = 3;
step = 2;
if ( (label->getChar(labelLength-1) == 0) ) {
// prune the trailing null (0x000 for UCS2)
labelLength -= 2;
}
} else {
i = 0;
step = 1;
}
for (int j = 0; i < labelLength && j < 200; i += step) {
char c = label->getChar(i);
if ( (c < '0') || (c > '9') ) {
isNumeric = false;
}
if (c == '\\') {
label2->append("\\\\");
j += 2;
} else if (c == ')') {
label2->append("\\)");
} else if (c == '(') {
label2->append("\\(");
} else if (c < 0x20 || c > 0x7e) {
GooString *aux = GooString::format("\\{0:03o}", c);
label2->append(aux);
j += 4;
delete aux;
} else {
label2->append(c);
++j;
}
}
if (needParens) {
*needParens = !(isNumeric);
}
return label2;
}
// Write a DSC-compliant <textline>.
void PSOutputDev::writePSTextLine(const GooString *s) {
int i, j, step;
int c;
// - DSC comments must be printable ASCII; control chars and
// backslashes have to be escaped (we do cheap Unicode-to-ASCII
// conversion by simply ignoring the high byte)
// - lines are limited to 255 chars (we limit to 200 here to allow
// for the keyword, which was emitted by the caller)
// - lines that start with a left paren are treated as <text>
// instead of <textline>, so we escape a leading paren
if (s->getLength() >= 2 &&
(s->getChar(0) & 0xff) == 0xfe &&
(s->getChar(1) & 0xff) == 0xff) {
i = 3;
step = 2;
} else {
i = 0;
step = 1;
}
for (j = 0; i < s->getLength() && j < 200; i += step) {
c = s->getChar(i) & 0xff;
if (c == '\\') {
writePS("\\\\");
j += 2;
} else if (c < 0x20 || c > 0x7e || (j == 0 && c == '(')) {
writePSFmt("\\{0:03o}", c);
j += 4;
} else {
writePSChar(c);
++j;
}
}
writePS("\n");
}