% Copyright (C) 2000, 2001 Aladdin Enterprises. All rights reserved. % % This program is free software; you can redistribute it and/or modify it % under the terms of the GNU General Public License as published by the % Free Software Foundation; either version 2 of the License, or (at your % option) any later version. % % This program is distributed in the hope that it will be useful, but % WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General % Public License for more details. % % You should have received a copy of the GNU General Public License along % with this program; if not, write to the Free Software Foundation, Inc., % 59 Temple Place, Suite 330, Boston, MA, 02111-1307. % $Id: pdfopt.ps,v 1.10.2.2.2.1 2003/04/12 14:02:39 giles Exp $ % PDF linearizer ("optimizer"). .currentglobal true .setglobal /pdfoptdict 200 dict def pdfoptdict begin % This linearizer is designed for simplicity, not for performance. % See the main program (the last procedure in the file) for comments % describing the main processing sequence. % ---------------- Utilities ---------------- % % ------ Data structures ------ % % Distinguish dictionaries, arrays, and everything else. /ifdaelse { % ifdaelse - 3 index type dup /dicttype eq { pop pop pop } { dup /arraytype ne exch /packedarraytype ne and { exch } if pop exch pop } ifelse exec } bind def % Implement dynamically growable arrays using a dictionary. /darray { % darray dict } bind def /dadd { % dadd - 1 index length exch put } bind def /daforall { % daforall - /exch cvx /get cvx 3 -1 roll /exec cvx 5 packedarray cvx 0 1 2 index 0 get length 1 sub 4 -1 roll for } bind def /dacontents { % dacontents [ exch { } daforall ] } bind def /dacontstring { % dacontstring 0 1 index { exch pop length add } forall string dup /NullEncode filter % Stack: darray str filter 3 -1 roll { 1 index exch writestring } daforall closefile } bind def % Force an object, mapping it if it is a reference. /omforcenew { % omforce dup oforce 2 copy eq { pop true } { exch 0 get omapnew exch pop } ifelse } bind def /omforce { % omforce omforcenew pop } bind def /omget { % omget get omforce } bind def % Visit an entire tree. /omvisit { % omvisit - omforcenew { { { omvisit omvisit } forall } { { omvisit } forall } { pop } ifdaelse } { pop } ifelse } bind def % Visit a tree, stopping at references to Page objects. % (This is only needed for the OpenAction in the Catalog.) /omvisitnopage { % omvisitnopage - dup oforce dup type /dicttype eq { /Type .knownget { /Page eq } { false } ifelse } { pop false } ifelse { pop % Page reference } { omforcenew { { { omvisitnopage omvisitnopage } forall } { { omvisitnopage } forall } { pop } ifdaelse } { pop } ifelse } ifelse } bind def % Collect the list of currently mapped object numbers, in order. /omapped { % - omapped RMap length array RMap { 2 index 3 1 roll 1 sub exch put } forall } bind def % Collect the list of object numbers passed to omap by a procedure. /visited { % visited false currentomap 2 .execn omapped exch setomap } bind def % ------ Output ------ % % Provide a framework for closure-based streams. .currentglobal false .setglobal userdict /clostreams 20 dict put % stream -> [data endproc] .setglobal % Create a closure-based stream. /clostream { % clostream 2 index 3 -1 roll /exec load 3 packedarray cvx /NullEncode filter % Stack: data endproc stream clostreams 1 index 5 -2 roll 2 array astore put } bind def % Close a closure-based stream. /closend { % closend dup closefile clostreams exch 2 copy get 3 1 roll undef aload pop exec } bind def % Implement in-memory output streams. /msproc { % msproc 3 -1 roll dadd { 100 string } { () } ifelse } bind def /mstream { % - mstream 10 darray {msproc} {dacontstring} clostream } bind def /mcontents { % mcontents closend } bind def % Implement a stream that only keeps track of its position. % (All streams should do this, but the PLRM doesn't require it.) /posbuf 100 string def /posproc { % posproc 0 2 copy get 5 -1 roll length add put pop //posbuf } bind def /postream { % - postream [0] {posproc} {0 get} clostream } bind def /poslength { % poslength closend } bind def % Implement streams with variable-bit-width data. % Note that these are dictionary objects, not stream objects. /bitstream { % bitstream 4 dict begin /S exch def /N 8 def /B 0 def currentdict end } bind def /bitwrite { % bitwrite - PDEBUG { ( ) print 1 index =only (:) print dup = } if 3 -1 roll begin N exch sub dup 0 ge { /N exch def N bitshift B add } { 2 copy bitshift B add S exch write % Stack: value -left { 8 add dup 0 ge { exit } if 2 copy bitshift 255 and S exch write } loop /N 1 index def bitshift 255 and } ifelse /B exch def end } bind def /bitflush { % bitflush - begin N 8 ne { S B write /B 0 def /N 8 def } if end } bind def % Capture OFile output on the temporary file, in memory, or just as a length. /totemp { % totemp TFile fileposition OFile /OFile TFile def 3 .execn /OFile exch def TFile fileposition } bind def /tomemory { % tomemory OFile /OFile mstream def 2 .execn OFile mcontents exch /OFile exch def } bind def /tolength { % tolength OFile /OFile postream def 2 .execn OFile poslength exch /OFile exch def } bind def % Copy a range of bytes from TFile to OFile. /copyrange { % copybytes - TFile 2 index setfileposition exch sub 1024 string exch { % Stack: buf left 2 copy 1 index length .min 0 exch getinterval TFile exch readstring pop OFile exch writestring 1 index length sub dup 0 le { exit } if } loop pop pop } bind def % Pad with blanks to a specified position. /padto { % padto - OFile fileposition sub dup 0 lt { (ERROR: file position incorrect by ) print = /padto cvx /rangecheck signalerror } { { ( ) ows } repeat } ifelse } bind def % ---------------- Read objects into memory ---------------- % /touch { % touch - { { touch touch } forall } { dup xcheck { % Executable array, must be an indirect object. dup 0 get resolved? { pop pop } { oforce touch } ifelse } { { touch } forall } ifelse } { pop } ifdaelse } bind def % ---------------- Replace references with referents ---------------- % /replaceable? { % replaceable? dup type /integertype eq exch xcheck not and } bind def /replacement { % replacement dup oforce dup replaceable? { exch } if pop } bind def /replacerefs { % replacerefs { dup { 2 index 2 index undef exch replacement exch replacement 2 index 3 1 roll put } forall } { 0 1 2 index length 1 sub { 1 index exch 2 copy get replacement put } for } { } ifdaelse } bind def /replaceReferences { % - replaceReferences - Objects { replacerefs pop } lforall % Delete replaced objects. 0 1 Objects llength 1 sub { Objects 1 index lget replaceable? { PDEBUG { (Deleting ) print dup = } if Generations 1 index 0 lput } if pop } for } bind def % ---------------- Create new objects ---------------- % /createObjects { % [...] createObjects Objects llength dup dup 3 index length add growPDFobjects % Stack: objects objn objn 3 1 roll exch { Objects 2 index 3 -1 roll lput Generations 1 index 1 lput 1 add } forall pop } bind def % ---------------- Propagate attributes ---------------- % /nopropattrs << % Never propagate these. /Type dup /Kids dup /Count dup /Parent dup % Handle Resources specially. /Resources dup >> def % Merge Resources. /mergeres { % mergeres - % Values in todict take priority over fromdict. 1 index /Resources .knownget { 1 index /Resources .knownget { % Stack: fromdict todict fromres tores exch oforce exch oforce % todict's Resources may be shared, so make a copy. dup length dict .copydict exch { % Stack: fromdict todict tores' fromkey fromvalue 2 index 2 index knownoget { % Stack: fromdict todict tores' fromkey fromvalue tovalue exch oforce exch % ProcSet is an array, other types are dictionaries. dup type /dicttype eq { % Dictionary, not ProcSet. exch dup length 2 index length add dict .copydict .copydict } { % Array or packed array, ProcSet. % Use dictionaries to do the merge. dup length 2 index length add dict begin exch { dup def } forall { dup def } forall mark currentdict end { pop } forall .packtomark } ifelse } if 2 index 3 1 roll put } forall } if /Resources exch put pop } { pop pop } ifelse } bind def % Merge attributes other than Resources. /mergeattrs { % mergeattrs % Values in todict take priority over fromdict. 1 index { % Stack: fromdict todict fromkey fromvalue //nopropattrs 2 index known { pop pop } { 2 index 2 index known { pop pop } { 2 index 3 1 roll put } ifelse } ifelse } forall } bind def % Propagate attributes to a subtree. /proppage { % proppage - % We should be able to tell when we reach a leaf % by finding a Type unequal to /Pages. Unfortunately, % some files distributed by Adobe lack the Type key % in some of the Pages nodes! Instead, we check for Kids. dup /Kids knownoget { % Accumulate inherited values. 3 1 roll % Stack: kids attrs pagesnode dup length dict .copydict mergeattrs dup 3 1 roll mergeres exch { oforce 1 index exch proppage } forall pop } { % Merge inherited values into the leaf. mergeattrs mergeres } ifelse } bind def % Propagate attributes to all pages. /propagateAttributes { % - propagateAttributes - 0 dict Trailer /Root oget /Pages oget proppage } bind def % ---------------- Identify document-level objects ---------------- % /identifyDocumentObjects { % - identifyDocumentObjects { Trailer /Root omget dup /PageMode .knownget { omvisit } if % Don't allow omvisit to trace references to Page objects. dup /OpenAction .knownget { omvisitnopage } if Trailer /Encrypt .knownget { omvisit } if dup /Threads .knownget { omforce { omforce } forall } if dup /AcroForm .knownget { omvisit } if pop } visited } bind def % ---------------- Identify the objects of each page ---------------- % /identifyfont { % identifyfont - omforce { exch /FontDescriptor eq { omforce dup /Flags .knownget { 32 and 0 ne } { false } ifelse exch { exch dup dup /FontFile eq exch /FontFile2 eq or exch /FontFile3 eq or 2 index and { fontfiles exch dadd } { omvisit } ifelse } forall pop } { omvisit } ifelse } forall } bind def /identifyPageObjects { % identifyPageObjects pdffindpageref 4 dict begin /images 10 darray def /fontfiles 10 darray def { omforce % Stack: extra page % Visit any extra objects if applicable. exch omvisit % Visit Annots, if any. % We don't try to defer the drawing information. dup /Annots .knownget { omvisit } if % Visit beads. dup /B .knownget { omvisit } if % Visit resources dictionaries. dup /Resources .knownget { omforce dup { % Visit the first-level Resource dictionaries. omforce pop pop } forall { % Visit the resources themselves. % Skip Image XObjects, and FontFile streams if the % FontDescriptor Flags have bit 6 set. % We don't try to visit the resources in the order in which % the Contents stream(s) reference(s) them. exch dup /XObject eq { pop oforce { dup oforce /Subtype get /Image eq { images exch dadd } { omvisit } ifelse pop } forall } { /Font eq { oforce { identifyfont pop } forall } { oforce omvisit } ifelse } ifelse } forall } if % Visit the Contents stream(s). dup /Contents .knownget { omvisit } if % Visit Image XObjects. We don't try to visit them in % reference order. images { omvisit } daforall % Visit FontFile streams. We don't try to visit them in % reference order. fontfiles { omvisit } daforall pop } visited end } bind def % Identify the objects of the first page. /identifyFirstPageObjects { % -identifyFirstPageObjects Trailer /Root oget null 1 index /PageMode knownoget { /UseOutlines eq { 1 index /Outlines knownoget { exch pop } if } if } if exch pop 1 identifyPageObjects } bind def % Identify the non-shared objects of the other pages, and the shared objects. /identifyOtherPageObjects { % - identifyOtherPageObjects [ ...] % 4 dict begin /marks lstring Objects llength lgrowto def % Mark document-level and first page objectsw. [CatalogNs FirstPageNs] { { marks exch 255 lput } forall } forall % Collect objects of other pages and identify sharing. [ 2 1 pdfpagecount { null exch identifyPageObjects } for ] dup { { marks exch 2 copy lget 1 add 254 .min lput } forall } forall [ exch { [ exch { marks 1 index lget 1 ne { pop } if } forall ] } forall ] [ 1 1 marks llength 1 sub { marks 1 index lget dup 1 le exch 255 eq or { pop } if } for ] end } bind def % Identify objects not associated with any page. /identifyNonPageObjects { % - identifyNonPageObjects 4 dict begin /marks lstring Objects llength lgrowto def [[[LPDictN PHSN] CatalogNs FirstPageNs SharedNs] OtherPageNs] { { { marks exch 1 lput } forall } forall } forall %****** PUT THESE IN A REASONABLE ORDER ****** [ 1 1 Objects llength 1 sub { marks 1 index lget 0 eq { Generations 1 index lget 0 eq { pop } if } { pop } ifelse } for ] } bind def % ---------------- Assign object numbers ---------------- % % Assign object numbers to all objects that will be copied. % Return the first (translated) object number in the First Page xref table. /assignObjectNumbers { % - assignObjectNumbers - OtherPageNs { { omap pop } forall } forall SharedNs { omap pop } forall NonPageNs { omap pop } forall % Assign object numbers for the First Page xref table last. LPDictN omap % don't pop, this is the return value CatalogNs { omap pop } forall FirstPageNs { omap pop } forall PHSN omap pop } bind def % ---------------- Create the LPDict ---------------- % % Create the contents of the LPDict. /createLPDict { % % createLPDict - LPDict dup /Linearized 1 put dup /L 4 -1 roll put % filelength dup /T 4 -1 roll put % xref0start dup /E 4 -1 roll put % firstpageend dup /H 5 -2 roll 1 index sub 2 array astore put % phsstart, end-start dup /O 1 pdffindpageref 0 get omap put /N pdfpagecount put } bind def % ---------------- Adjust object positions ---------------- % /adjustObjectPositions { % % adjustObjectPositions - % Objects fall into 4 categories: LPDict, PHS, Catalog, and others. % We handle the first two as special cases. XRef { % Stack: bdy below above key loc dup 5 index ge { 2 } { 3 } ifelse index add XRef 3 1 roll put } forall pop pop pop XRef LPDictN omap HeaderLength put XRef PHSN omap PHSStart put } bind def % ---------------- Write the output file ---------------- % % Write objects identified by object number. /writeobjn { % writeobjn - Generations 1 index lget pdfwriteobj } bind def /writeobjns { % writeobjns - { writeobjn } forall } bind def % Write a part of the output file. /writePart { %