1260 lines
35 KiB
PostScript
1260 lines
35 KiB
PostScript
% Copyright (C) 2000, 2001 Aladdin Enterprises. All rights reserved.
|
|
%
|
|
% This software is provided AS-IS with no warranty, either express or
|
|
% implied.
|
|
%
|
|
% This software is distributed under license and may not be copied,
|
|
% modified or distributed except as expressly authorized under the terms
|
|
% of the license contained in the file LICENSE in this distribution.
|
|
%
|
|
% For more information about licensing, please refer to
|
|
% http://www.ghostscript.com/licensing/. For information on
|
|
% commercial licensing, go to http://www.artifex.com/licensing/ or
|
|
% contact Artifex Software, Inc., 101 Lucas Valley Road #110,
|
|
% San Rafael, CA 94903, U.S.A., +1(415)492-9861.
|
|
|
|
% $Id: pdfopt.ps,v 1.20 2003/06/02 19:52:58 alexcher 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 { % <obj> <dictproc> <arrayproc> <otherproc> 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 { % <size> darray <darray>
|
|
dict
|
|
} bind def
|
|
/dadd { % <darray> <value> dadd -
|
|
1 index length exch put
|
|
} bind def
|
|
/daforall { % <darray> <proc> 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 { % <darray> dacontents <array>
|
|
[ exch { } daforall ]
|
|
} bind def
|
|
/dacontstring { % <darray> dacontstring <string>
|
|
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 { % <obj> omforce <obj'> <notseen>
|
|
dup oforce 2 copy eq { pop true } { exch 0 get omapnew exch pop } ifelse
|
|
} bind def
|
|
/omforce { % <obj> omforce <obj'>
|
|
omforcenew pop
|
|
} bind def
|
|
/omget { % <dict|array> <key> omget <obj>
|
|
get omforce
|
|
} bind def
|
|
% Visit an entire tree.
|
|
/omvisit { % <obj> 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 { % <obj> 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 <obj#s>
|
|
RMap ld_length larray exch lgrowto
|
|
RMap {
|
|
2 index 3 1 roll 1 sub exch lput
|
|
} ld_forall
|
|
} bind def
|
|
|
|
% Collect the list of object numbers passed to omap by a procedure.
|
|
/visited { % <proc> visited <obj#s>
|
|
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 { % <data> <proc> <endproc> clostream <stream>
|
|
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 { % <stream> closend <result>
|
|
dup closefile clostreams exch
|
|
2 copy get 3 1 roll undef aload pop exec
|
|
} bind def
|
|
|
|
% Implement in-memory output streams.
|
|
/msproc { % <data> <more> <accum> msproc <scratch>
|
|
3 -1 roll dadd { 100 string } { () } ifelse
|
|
} bind def
|
|
/mstream { % - mstream <mstream>
|
|
10 darray {msproc} {dacontstring} clostream
|
|
} bind def
|
|
/mcontents { % <mstream> mcontents <string>
|
|
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 { % <data> <more> <accum> posproc <scratch>
|
|
0 2 copy get 5 -1 roll length add put
|
|
pop //posbuf
|
|
} bind def
|
|
/postream { % - postream <postream>
|
|
[0] {posproc} {0 get} clostream
|
|
} bind def
|
|
/poslength { % <postream> poslength <pos>
|
|
closend
|
|
} bind def
|
|
|
|
% Implement streams with variable-bit-width data.
|
|
% Note that these are dictionary objects, not stream objects.
|
|
/bitstream { % <stream> bitstream <bstream>
|
|
4 dict begin /S exch def /N 8 def /B 0 def
|
|
currentdict end
|
|
} bind def
|
|
/bitwrite { % <bstream> <value> <width> bitwrite -
|
|
PDFOPTDEBUG { ( ) 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 { % <bstream> bitflush -
|
|
begin N 8 ne { S B write /B 0 def /N 8 def } if end
|
|
} bind def
|
|
|
|
/bwn { % <value> <width> bwn -
|
|
2 copy % v w v w
|
|
2 exch exp ge { % v w v>=2**w
|
|
/bwn cvx /rangecheck signalerror
|
|
} if
|
|
bits 3 1 roll bitwrite
|
|
} def
|
|
|
|
% Capture OFile output on the temporary file, in memory, or just as a length.
|
|
/totemp { % <proc> totemp <start> <end>
|
|
TFile fileposition OFile
|
|
/OFile TFile def 3 .execn
|
|
/OFile exch def
|
|
TFile fileposition
|
|
} bind def
|
|
/tomemory { % <proc> tomemory <string>
|
|
OFile /OFile mstream def 2 .execn
|
|
OFile mcontents exch /OFile exch def
|
|
} bind def
|
|
/tolength { % <proc> tolength <string>
|
|
OFile /OFile postream def 2 .execn
|
|
OFile poslength exch /OFile exch def
|
|
} bind def
|
|
|
|
% Copy a range of bytes from TFile to OFile.
|
|
/copyrange { % <start> <end> 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 { % <pos> 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 { % <object> 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? { % <value> replaceable? <bool>
|
|
dup type /integertype eq exch xcheck not and
|
|
} bind def
|
|
/replacement { % <obj|ref> replacement <obj'>
|
|
dup oforce dup replaceable? { exch } if pop
|
|
} bind def
|
|
|
|
/replacerefs { % <object> replacerefs <object>
|
|
{
|
|
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? {
|
|
PDFOPTDEBUG { (Deleting ) print dup = } if
|
|
Generations 1 index 0 lput
|
|
} if pop
|
|
} for
|
|
} bind def
|
|
|
|
% ---------------- Create new objects ---------------- %
|
|
|
|
/createObjects { % [<obj>...] createObjects <firstobj#>
|
|
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 { % <fromdict> <todict> 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 { % <fromdict> <todict> mergeattrs <fromdict> <todict>
|
|
% 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 { % <attrs> <subtree> 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 <obj#s>
|
|
{
|
|
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 { omvisit } forall
|
|
} if
|
|
dup /AcroForm .knownget { omvisit } if
|
|
pop
|
|
} visited
|
|
} bind def
|
|
|
|
% ---------------- Identify the objects of each page ---------------- %
|
|
|
|
/identifyfont { % <fontref> 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
|
|
|
|
% Collect all the objects referenced from a page. The first object number
|
|
% (which may not be the smallest one) is that of the page object itself.
|
|
/identifyPageObjects { % <extra> <page#> identifyPageObjects <obj#s>
|
|
PDFOPTDEBUG {
|
|
(%Objects for page: ) print dup =
|
|
} if
|
|
pdffindpageref
|
|
dup 0 get 3 1 roll
|
|
4 dict begin
|
|
/images 10 darray def
|
|
/fontfiles 10 darray def
|
|
{
|
|
omforce
|
|
% Stack: pageobj# 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
|
|
% Stack: pageobj# obj#s_larray
|
|
[ 3 1 roll {
|
|
2 copy eq { pop } { exch } ifelse
|
|
} lforall counttomark 1 roll ]
|
|
PDFOPTDEBUG {
|
|
(%Objects = ) print dup === flush
|
|
} if
|
|
} bind def
|
|
|
|
% Identify the objects of the first page.
|
|
/identifyFirstPageObjects { % - identifyFirstPageObjects <obj#s>
|
|
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.
|
|
% Note that the page objects themselves may appear to be shared, because of
|
|
% references from Dest entries in annotations, but they must be treated as
|
|
% non-shared. Note also that some objects referenced on the first page may
|
|
% also be referenced from other pages.
|
|
/identifyOtherPageObjects { % - identifyOtherPageObjects [<pageobj#s> ...]
|
|
% <sharedobj#s>
|
|
4 dict begin
|
|
/marks lstring Objects llength lgrowto def
|
|
% 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
|
|
% Mark document-level and first page objects.
|
|
CatalogNs { marks exch 255 lput } lforall
|
|
FirstPageNs { marks exch 255 lput } forall
|
|
% Mark the page objects themselves as non-shared.
|
|
dup {
|
|
0 get marks exch 1 lput
|
|
} forall
|
|
% Collect the non-shared objects of each page.
|
|
dup
|
|
[ exch {
|
|
[ exch {
|
|
marks 1 index lget 1 ne { pop } if
|
|
} forall ]
|
|
} forall ]
|
|
% Collect the shared objects of each page.
|
|
exch
|
|
[ exch {
|
|
[ exch {
|
|
marks 1 index lget dup 1 le exch 255 eq or { pop } if
|
|
} forall ]
|
|
} forall ]
|
|
|
|
% Collect the shared objects.
|
|
[ 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 <obj#s>
|
|
4 dict begin
|
|
/marks lstring Objects llength lgrowto def
|
|
|
|
LPDictN marks exch 1 lput
|
|
PHSN marks exch 1 lput
|
|
CatalogNs { marks exch 1 lput } lforall
|
|
FirstPageNs { marks exch 1 lput } forall
|
|
SharedNs { marks exch 1 lput } forall
|
|
OtherPageNs { { marks exch 1 lput } forall } forall
|
|
|
|
%****** PUT THESE IN A REASONABLE ORDER ******
|
|
/npobj larray
|
|
0
|
|
1 1 Objects llength 1 sub {
|
|
marks 1 index lget 0 eq {
|
|
Generations exch lget 0 ne { 1 add } if
|
|
} {
|
|
pop
|
|
} ifelse
|
|
} for
|
|
lgrowto def
|
|
|
|
0
|
|
1 1 Objects llength 1 sub {
|
|
marks 1 index lget 0 eq {
|
|
% i
|
|
Generations 1 index lget 0 ne {
|
|
% i
|
|
npobj 2 index % i nobj 0
|
|
3 -1 roll % nobj 0 i
|
|
lput 1 add
|
|
} {
|
|
pop
|
|
} ifelse
|
|
} {
|
|
pop
|
|
} ifelse
|
|
} for
|
|
pop
|
|
|
|
npobj
|
|
end
|
|
} 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 } lforall
|
|
% Assign object numbers for the First Page xref table last.
|
|
LPDictN omap % don't pop, this is the return value
|
|
CatalogNs { omap pop } lforall
|
|
FirstPageNs { omap pop } forall
|
|
PHSN omap pop
|
|
} bind def
|
|
|
|
% ---------------- Create the LPDict ---------------- %
|
|
|
|
% Create the contents of the LPDict.
|
|
/createLPDict { % <phsstart> <phsend> <firstpageend>
|
|
% <xref0start> <filelength> 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 { % <boundary> <deltabelow> <deltaabove>
|
|
% 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 ld_put
|
|
} ld_forall pop pop pop
|
|
XRef LPDictN omap HeaderLength ld_put
|
|
XRef PHSN omap PHSStart ld_put
|
|
} bind def
|
|
|
|
% ---------------- Write the output file ---------------- %
|
|
|
|
% Write objects identified by object number.
|
|
/writeobjn { % <obj#> writeobjn -
|
|
Generations 1 index lget pdfwriteobj
|
|
} bind def
|
|
/writeobjns { % <obj#s> writeobjns -
|
|
{ writeobjn } forall
|
|
} bind def
|
|
/lwriteobjns { % <obj#s> writeobjns -
|
|
{ writeobjn } lforall
|
|
} bind def
|
|
|
|
% Write a part of the output file.
|
|
/writePart { % <proc> <label> writePart -
|
|
PDFOPTDEBUG {
|
|
dup print ( count=) print count =only ( start=) print
|
|
OFile { .fileposition } stopped { pop (???) } if =
|
|
2 .execn
|
|
print ( end=) print
|
|
OFile { .fileposition } stopped { pop (???) } if =
|
|
} {
|
|
pop exec
|
|
} ifelse
|
|
} bind def
|
|
|
|
% Write the header.
|
|
/writePart1 { % - writePart1 -
|
|
{
|
|
pdfwriteheader
|
|
} (part1) writePart
|
|
} bind def
|
|
|
|
% Write the linearization parameters dictionary.
|
|
/writePart2 { % - writePart2 -
|
|
{
|
|
LPDictN writeobjn
|
|
} (part2) writePart
|
|
} bind def
|
|
|
|
% Write the First Page xref table and trailer.
|
|
% Free variables: FirstPageXN.
|
|
/writePart3 { % <xrefstart> writePart3 -
|
|
{
|
|
FirstPageXN NObjects 1 add 1 index sub pdfwritexref
|
|
Trailer dup length 1 add dict copy
|
|
dup /Size NObjects 1 add put
|
|
dup /Prev 4 -1 roll put
|
|
pdfwritetrailer
|
|
0 pdfwritestartxref
|
|
} (part3) writePart
|
|
} bind def
|
|
|
|
% Write the Catalog and other required document-level objects.
|
|
% Free variables: CatalogNs.
|
|
/writePart4 { % - writePart4 -
|
|
{
|
|
CatalogNs lwriteobjns
|
|
} (part4) writePart
|
|
} bind def
|
|
|
|
% Write the Primary Hint Stream.
|
|
/writePart5 { % - writePart5 -
|
|
{
|
|
PHSN writeobjn
|
|
} (part5) writePart
|
|
} bind def
|
|
|
|
% Write the First Page's objects.
|
|
% Free variables: FirstPageNs.
|
|
/writePart6 { % - writePart6 -
|
|
{
|
|
FirstPageNs writeobjns
|
|
} (part6) writePart
|
|
} bind def
|
|
|
|
% Write the objects of other pages (Page + non-shared objects).
|
|
% Free variables: OtherPageNs.
|
|
/writePart7 { % - writePart7 <lengths>
|
|
{
|
|
[ OtherPageNs {
|
|
OFile fileposition exch
|
|
writeobjns OFile fileposition exch sub
|
|
} forall ]
|
|
} (part7) writePart
|
|
} bind def
|
|
|
|
% Write the shared objects of other pages.
|
|
% Free variables: SharedNs.
|
|
/writePart8 { % - writePart8 -
|
|
{
|
|
SharedNs writeobjns
|
|
} (part8) writePart
|
|
} bind def
|
|
|
|
% Write the other objects not associated with pages.
|
|
% Free variables: NonPageNs.
|
|
/writePart9 { % - writePart9 -
|
|
{
|
|
NonPageNs { writeobjn } lforall
|
|
} (part9) writePart
|
|
} bind def
|
|
|
|
% Write the main xref table and trailer.
|
|
% Free variables: FirstPageXN.
|
|
/writePart11xref { % writePart11 -
|
|
{
|
|
0 FirstPageXN pdfwritexref
|
|
} (part11xref) writePart
|
|
} bind def
|
|
/writePart11rest { % <part3start> writePart11rest -
|
|
{
|
|
<< /Size FirstPageXN >> pdfwritetrailer
|
|
pdfwritestartxref
|
|
} (part11rest) writePart
|
|
} bind def
|
|
|
|
% ---------------- Write hint tables ---------------- %
|
|
|
|
/bitsneeded { % <maxvalue> bitsneeded <#bits>
|
|
0 exch { dup 0 eq { pop exit } if exch 1 add exch 2 idiv } loop
|
|
} bind def
|
|
|
|
% Find the start and end of objects in the output.
|
|
/omstart { % <obj#> omstart <pos>
|
|
PDFOPTDEBUG { (start\() print dup =only } if
|
|
omap
|
|
PDFOPTDEBUG { (=>) print dup =only } if
|
|
XRef exch ld_get
|
|
PDFOPTDEBUG { (\) = ) print dup = } if
|
|
} bind def
|
|
/omend { % <obj#> omend <pos>
|
|
% The end of an object is the start of the next object.
|
|
% The caller must be sure that this object is not the last one
|
|
% in part 9.
|
|
PDFOPTDEBUG { (end\() print dup =only } if
|
|
omap
|
|
PDFOPTDEBUG { (=>) print dup =only } if
|
|
1 add
|
|
% Check that the requested object wasn't the last one in part 6:
|
|
% the next object in the output file is the first in part 7.
|
|
PHSN omap 1 index eq { pop 1 } if
|
|
XRef exch ld_get
|
|
PDFOPTDEBUG { (\) = ) print dup = } if
|
|
} bind def
|
|
/omlength { % <obj#> omlength <length>
|
|
dup omend exch omstart sub
|
|
} bind def
|
|
|
|
% Find the Contents of a page.
|
|
/contentsobjects { % <pagedict> contentsobjects <firstobj#> <lastobj#> true
|
|
% <pagedict> contentsobjects false
|
|
/Contents .knownget {
|
|
dup oforce % ref []
|
|
dup type /dicttype eq {
|
|
pop 0 get dup true % ref ref
|
|
} {
|
|
exch pop % []
|
|
dup length 0 ne {
|
|
dup 0 get 0 get % [] 1st
|
|
exch dup % 1st [] []
|
|
length 1 sub get 0 get % 1st last
|
|
true
|
|
} {
|
|
pop false
|
|
} ifelse
|
|
} ifelse
|
|
} {
|
|
false
|
|
} ifelse
|
|
} bind def
|
|
|
|
/contentsstart { % <pagedict> contentsstart <pos> true
|
|
% <pagedict> contentsstart false
|
|
contentsobjects { pop omstart true } { false } ifelse
|
|
} bind def
|
|
|
|
/contentslength { % <pagedict> contentslength <length>
|
|
contentsobjects { omend exch omstart sub } { 0 } ifelse
|
|
} bind def
|
|
|
|
|
|
/writePageOffsetHints {
|
|
PDFOPTDEBUG { /writePageOffsetHints == } if
|
|
20 dict begin
|
|
/bits OFile bitstream def
|
|
|
|
% Calculate least length of a page.
|
|
FirstPageLength OtherPageLengths { .min } forall
|
|
/minpl exch def
|
|
|
|
% Calculate least contents length.
|
|
FirstPageNs 0 get Objects exch lget contentslength
|
|
OtherPageNs { 0 get Objects exch lget contentslength .min } forall
|
|
/mincl exch def
|
|
|
|
% The Adobe documentation says that all versions of Acrobat
|
|
% require item 8 (mincl) to be zero. Patch this here.
|
|
/mincl 0 def
|
|
|
|
% Calculate bits needed to represent greatest page length.
|
|
FirstPageLength OtherPageLengths { .max } forall
|
|
minpl sub bitsneeded /maxplbits exch def
|
|
% Calculate bits needed to represent the greatest Contents length.
|
|
FirstPageNs 0 get Objects exch lget contentslength
|
|
OtherPageNs { 0 get Objects exch lget contentslength .max } forall
|
|
mincl sub bitsneeded /maxclbits exch def
|
|
|
|
% Per Adobe documentation, Acrobat requires that item 5 (maxplbits)
|
|
% be equal to item 9 (maxclbits). Set both to the max of the two.
|
|
maxplbits maxclbits .max /maxplbits 1 index def /maxclbits exch def
|
|
|
|
% Mapping from object number to shared object reference
|
|
/shared_id_dict FirstPageNs length SharedNs length add dict begin
|
|
0 FirstPageNs { 1 index def 1 add } forall
|
|
SharedNs { 1 index def 1 add } forall
|
|
pop
|
|
currentdict end def
|
|
|
|
% Table F.3 Page offset hint table, header section
|
|
|
|
% 1: Least number of objects in a page:
|
|
FirstPageNs length OtherPageNs { length .min } forall
|
|
/minnop 1 index def 32 bwn
|
|
% 2: Location of first page's Page object:
|
|
FirstPageNs 0 get omap XRef exch ld_get 32 bwn
|
|
% 3: Bits needed to represent greatest # of objects in a page:
|
|
FirstPageNs length OtherPageNs { length .max } forall
|
|
minnop sub bitsneeded /maxnopbits 1 index def 16 bwn
|
|
% 4: Least length of a page:
|
|
minpl 32 bwn
|
|
% 5: Bits needed to represent the greatest page length:
|
|
maxplbits 16 bwn
|
|
% 6: Least start of Contents offset:
|
|
0 % (Acrobat requires that this be 0.)
|
|
/minsco 1 index def 32 bwn
|
|
% 7: Bits needed to represent the greatest start of Contents
|
|
% offset:
|
|
0 % (Acrobat ignores this.)
|
|
/maxscobits 1 index def 16 bwn
|
|
% 8: Least contents length:
|
|
mincl 32 bwn
|
|
% 9: Bits needed to represent the greatest Contents length:
|
|
maxclbits 16 bwn
|
|
% 10: Bits needed to represent the greatest number of Shared
|
|
% Object references:
|
|
FirstPageNs length SharedPageNs { length .max } forall bitsneeded
|
|
/maxsorbits 1 index def 16 bwn
|
|
% 11: Bits needed to identify a Shared Object:
|
|
FirstPageNs length SharedNs length add bitsneeded
|
|
/sobits 1 index def 16 bwn
|
|
% 12: Bits needed to represent numerator of fraction:
|
|
2
|
|
/numfbits 1 index def 16 bwn
|
|
% 13: Denominator of fraction:
|
|
1
|
|
/denf 1 index def 16 bwn
|
|
|
|
% Table F.4 Page offset hint table, per-page entry
|
|
|
|
% 1: Number of objects in pages:
|
|
FirstPageNs length minnop sub maxnopbits bwn
|
|
OtherPageNs {
|
|
length minnop sub maxnopbits bwn
|
|
} forall
|
|
bits bitflush
|
|
|
|
% 2: Total length of pages in bytes;
|
|
FirstPageLength minpl sub maxplbits bwn
|
|
OtherPageLengths {
|
|
minpl sub maxplbits bwn
|
|
} forall
|
|
bits bitflush
|
|
|
|
% 3: Number of shared objects referenced from page:
|
|
FirstPageNs length maxsorbits bwn
|
|
SharedPageNs { length maxsorbits bwn } forall
|
|
bits bitflush
|
|
% 4: A shared object identifier:
|
|
FirstPageNs { shared_id_dict exch get sobits bwn } forall
|
|
SharedPageNs {
|
|
{ shared_id_dict exch get sobits bwn
|
|
} forall
|
|
} forall
|
|
bits bitflush
|
|
|
|
% 5: Numerator of fractional position for each shared object:
|
|
FirstPageNs { pop 0 numfbits bwn } forall
|
|
SharedPageNs {
|
|
{ pop 0 numfbits bwn
|
|
} forall
|
|
} forall
|
|
bits bitflush
|
|
|
|
% 6: Contents offsets:
|
|
% Following Implementation Note 133 section 6 is empty.
|
|
maxscobits 0 gt {
|
|
[FirstPageNs OtherPageNs aload pop] {
|
|
0 get Objects exch lget contentsstart { minsco sub } { 0 } ifelse
|
|
maxscobits bwn
|
|
} forall
|
|
bits bitflush
|
|
} if
|
|
|
|
% 7: Contents lengths:
|
|
[FirstPageNs OtherPageNs aload pop] {
|
|
0 get Objects exch lget contentslength mincl sub maxclbits bwn
|
|
} forall
|
|
bits bitflush
|
|
|
|
end
|
|
|
|
} bind def
|
|
|
|
/writeSharedObjectHints {
|
|
PDFOPTDEBUG { /writeSharedObjectHints == } if
|
|
20 dict begin
|
|
/bits OFile bitstream def
|
|
/obj_count SharedNs length FirstPageNs length add def
|
|
|
|
% Table F.5 Shared object hint table, header section
|
|
|
|
% 1: Object number of first object in Shared Objects section
|
|
0 32 bwn
|
|
% 2: Location of first object in Shared Objects section:
|
|
% If there are no shared objects,
|
|
% Acrobat sets this to the location of linearization
|
|
% parameters object (the very first object).
|
|
{ pdfwriteheader } tomemory length 32 bwn
|
|
% 3: Number of Shared Object entries for first page:
|
|
FirstPageNs length 32 bwn
|
|
% 4: Number of Shared Object entries for Shared Objects
|
|
% section
|
|
obj_count 32 bwn
|
|
% 5: Bits needed to represent the greatest number of objects
|
|
% in a shared object group (always 0, because all groups
|
|
% have only 1 object):
|
|
0 16 bwn
|
|
% 6: Least length of a Shared Object Group in bytes:
|
|
16#7fffffff FirstPageNs { omlength .min } forall
|
|
SharedNs { omlength .min } forall
|
|
/minsol 1 index def 32 bwn
|
|
% 7: Bits needed to represent the greatest length of a
|
|
% Shared Object Group:
|
|
0 FirstPageNs { omlength .max } forall
|
|
SharedNs { omlength .max } forall
|
|
minsol sub bitsneeded
|
|
/maxsolbits 1 index def 16 bwn
|
|
|
|
% Table F.6 Shared object hint table, shared object group entry
|
|
|
|
% 1: Lengths of shared object groups:
|
|
FirstPageNs { omlength minsol sub maxsolbits bwn } forall
|
|
SharedNs { omlength minsol sub maxsolbits bwn } forall
|
|
bits bitflush
|
|
% 2: MD5 flag:
|
|
obj_count { 0 1 bwn } repeat
|
|
bits bitflush
|
|
% 3: No MD5 shared object signatures.
|
|
|
|
% 4: No number_number_of_objects_in_the_group - 1
|
|
end
|
|
} bind def
|
|
|
|
% ---------------- Main program ---------------- %
|
|
|
|
/pdfOptimize { % <infile> <outfile> pdfOptimize -
|
|
realtime 3 1 roll
|
|
exch pdfdict begin pdfopenfile dup begin
|
|
40 dict begin
|
|
/IDict exch def
|
|
/OFile exch def
|
|
/starttime exch def
|
|
/now {
|
|
QUIET { pop } { print (, t = ) print realtime starttime sub = flush } ifelse
|
|
} def
|
|
omapinit
|
|
|
|
% Create and open a temporary file.
|
|
% Because of .setsafe, we have to open it as read-write, rather than
|
|
% opening for writing, then closing it and reopening it for reading.
|
|
|
|
null (w+) .tempfile /TFile exch def /TFileName exch def
|
|
.setsafe
|
|
|
|
% Read all objects into memory.
|
|
|
|
Trailer touch
|
|
(Read objects) now
|
|
|
|
% Encrypted files are not yet supported.
|
|
Trailer /Encrypt known {
|
|
(ERROR: Encrypted files are not yet supported.) = flush
|
|
/pdfOptimize cvx /limitcheck signalerror
|
|
} if
|
|
|
|
% Replace indirect references to numbers. This is needed
|
|
% for the Length of streams, and doesn't hurt anything else.
|
|
|
|
replaceReferences
|
|
(Replaced references) now
|
|
|
|
% Create the two new objects: the linearization parameter
|
|
% dictionary, and the Primary Hint Stream.
|
|
|
|
/LPDict 10 dict def
|
|
/PHS 10 dict cvx def % executable = stream
|
|
[LPDict PHS] createObjects
|
|
/LPDictN 1 index def 1 add
|
|
/PHSN exch def
|
|
PDFOPTDEBUG { << /LPDictN LPDictN /PHSN PHSN >> === } if
|
|
|
|
% Count the number of objects in the output.
|
|
|
|
0 0 1 Objects llength 1 sub {
|
|
Generations exch lget 0 ne { 1 add } if
|
|
} for
|
|
/NObjects exch def
|
|
QUIET not { NObjects =only ( objects total) = flush } if
|
|
|
|
% Propagate inherited attributes down the page tree.
|
|
|
|
propagateAttributes
|
|
(Propagated attributes) now
|
|
|
|
% Identify the document-level objects (part 4).
|
|
|
|
identifyDocumentObjects /CatalogNs exch def
|
|
QUIET not { CatalogNs === flush } if
|
|
(Identified Catalog) now
|
|
|
|
% Identify the first page's objects (part 6),
|
|
% including the Outlines tree if appropriate.
|
|
|
|
pdfopencache
|
|
/FirstPageNs identifyFirstPageObjects def
|
|
QUIET not { FirstPageNs === flush } if
|
|
(Identified first page) now
|
|
|
|
% Identify shared vs. non-shared objects for remaining pages
|
|
% (parts 7 and 8).
|
|
|
|
identifyOtherPageObjects
|
|
/SharedNs exch def
|
|
/SharedPageNs exch def
|
|
/OtherPageNs exch def
|
|
QUIET not { OtherPageNs === flush SharedNs === flush } if
|
|
(Identified other pages) now
|
|
|
|
% Identify objects not associated with any page (part 9).
|
|
|
|
/NonPageNs identifyNonPageObjects def
|
|
QUIET not { NonPageNs { === } forall flush } if
|
|
(Identified non-pages) now
|
|
|
|
% Assign final object numbers to all the objects.
|
|
% (The omap is currently empty.)
|
|
|
|
/FirstPageXN assignObjectNumbers def
|
|
(Assigned objects #s) now
|
|
|
|
% Write the document-level objects (part 4).
|
|
|
|
{ writePart4 } totemp
|
|
/CatalogTempEnd exch def /CatalogTempStart exch def
|
|
(Wrote Catalog) now
|
|
|
|
% Write the first page's objects (part 6).
|
|
|
|
{ writePart6 } totemp
|
|
/FirstPageTempEnd exch def /FirstPageTempStart exch def
|
|
(Wrote first page) now
|
|
|
|
% Write the non-shared objects for other pages (part 7).
|
|
|
|
{ writePart7 /OtherPageLengths exch def } totemp
|
|
/OtherPageTempEnd exch def /OtherPageTempStart exch def
|
|
(Wrote other pages) now
|
|
|
|
% Write the shared objects for other pages (part 8).
|
|
|
|
{ writePart8 } totemp
|
|
/SharedTempEnd exch def /SharedTempStart exch def
|
|
(Wrote shared objects) now
|
|
|
|
% Write the objects not associated with pages (part 9).
|
|
|
|
{ writePart9 } totemp
|
|
/NonPageTempEnd exch def /NonPageTempStart exch def
|
|
|
|
% Compute conservative lengths of parts 2,3,5,11 of the output.
|
|
% It's OK for these to be too large, but not too small.
|
|
|
|
% Make dummy XRef entres for LPDict and PHS.
|
|
XRef LPDictN omap 0 ld_put
|
|
XRef PHSN omap 0 ld_put
|
|
|
|
/HeaderLength { % this is exact
|
|
writePart1 % part 1
|
|
} tolength def
|
|
/CatalogLength % this is exact
|
|
CatalogTempEnd CatalogTempStart sub def % part 4
|
|
/FirstPageLength % this is exact
|
|
FirstPageTempEnd FirstPageTempStart sub def % part 6
|
|
/OtherObjectsLength % this is exact
|
|
NonPageTempEnd OtherPageTempStart sub def % parts 7,8,9
|
|
/ObjectsLength % this is exact
|
|
CatalogLength FirstPageLength add OtherObjectsLength add def
|
|
/XrefLength { % part 11
|
|
% The LPDict must end within the first 1024 bytes,
|
|
% so the start of the FirstPage xref table can't exceed 1024.
|
|
writePart11xref 1024 writePart11rest
|
|
} tolength def
|
|
/NominalFileLength % Make a generous allowance for parts 2,3,5.
|
|
HeaderLength ObjectsLength 3 mul add 10000 add 99999 .max def
|
|
/FirstPageXrefLength { % part 3
|
|
NominalFileLength writePart3
|
|
} tolength def
|
|
/LPDictLength { % part 2
|
|
NominalFileLength dup 2 mul 2 copy add 1 index dup createLPDict writePart2
|
|
} tolength def
|
|
|
|
% Compute a few additional values from the above.
|
|
|
|
/XrefBeginLength {
|
|
(xref\n0 ) ows
|
|
OFile FirstPageXN write=
|
|
} tolength def
|
|
HeaderLength LPDictLength add
|
|
/FirstPageXrefStart 1 index def
|
|
FirstPageXrefLength add
|
|
/CatalogStart 1 index def
|
|
CatalogLength add % phsstart
|
|
/PHSStart exch def
|
|
|
|
% Adjust the object positions ignoring PHS.
|
|
% (Writing the PHS needs these.)
|
|
|
|
0 0 CatalogStart CatalogTempStart sub adjustObjectPositions
|
|
% Make a temporary XRef entry for the PHS, for the benefit of omend.
|
|
XRef PHSN omap CatalogStart ld_put
|
|
(Adjusted positions) now
|
|
|
|
% Construct the hint tables (part 5).
|
|
|
|
{ writePageOffsetHints } totemp
|
|
pop /PHSTempStart exch def
|
|
{ writeSharedObjectHints } totemp
|
|
exch PHSTempStart sub PHS /S 3 -1 roll put
|
|
PHSTempStart sub /PHSTempLength exch def
|
|
(Wrote hints) now
|
|
|
|
% Prepare to read TFile.
|
|
% TFile closefile
|
|
% /TFile TFileName (r) file def
|
|
|
|
PHS
|
|
dup /File TFile put
|
|
dup /FilePosition PHSTempStart put
|
|
dup /Length PHSTempLength put
|
|
pop
|
|
/PHSLength { writePart5 } tolength def
|
|
|
|
% Construct the linearization parameter dictionary (part 2).
|
|
|
|
PHSStart
|
|
dup PHSLength add % phsend
|
|
/FirstPageStart 1 index def
|
|
dup FirstPageLength add % firstpageend
|
|
dup OtherObjectsLength add
|
|
/XrefStart 1 index def
|
|
XrefBeginLength add % xref0start
|
|
dup XrefBeginLength sub XrefLength add % fileend
|
|
% Because of a bug, Acrobat Reader doesn't recognize any file
|
|
% shorter than 1K as linearized. Patch this here.
|
|
1024 .max
|
|
/FileLength 1 index def
|
|
createLPDict
|
|
|
|
% Adjust the object positions again, taking the PHS into account.
|
|
|
|
PHSStart 0 PHSLength adjustObjectPositions
|
|
(Readjusted positions) now
|
|
|
|
% Finally, write the output file.
|
|
|
|
writePart1
|
|
writePart2
|
|
FirstPageXrefStart padto
|
|
XrefStart writePart3
|
|
CatalogStart padto
|
|
CatalogTempStart CatalogTempEnd copyrange % part 4
|
|
writePart5
|
|
FirstPageStart padto
|
|
FirstPageTempStart NonPageTempEnd copyrange % parts 6,7,8,9
|
|
% No Overflow Hint Stream (part 10).
|
|
XrefStart padto
|
|
writePart11xref
|
|
{ FirstPageXrefStart writePart11rest } tomemory
|
|
FileLength 1 index length sub padto ows
|
|
(Wrote output file) now
|
|
|
|
% Wrap up.
|
|
|
|
TFile closefile TFileName deletefile
|
|
end % temporary dict
|
|
end % IDict
|
|
} bind def
|
|
|
|
end % pdfoptdict
|
|
.setglobal
|
|
|
|
% Check for command line arguments.
|
|
[ shellarguments {
|
|
] dup length 2 eq {
|
|
% Load the pdfwrite utilities if necessary.
|
|
/omapinit where { pop } { (pdfwrite.ps) runlibfile } ifelse
|
|
save exch
|
|
aload pop exch (r) file exch (w) file
|
|
3000000 setvmthreshold
|
|
0 setobjectformat
|
|
pdfoptdict begin pdfOptimize end
|
|
restore
|
|
} {
|
|
(Usage: gs -dNODISPLAY -- pdfopt.ps input.pdf output.pdf) = flush quit
|
|
} ifelse
|
|
} {
|
|
pop
|
|
} ifelse
|