1 % Copyright (C) 2000, 2001 Aladdin Enterprises. All rights reserved.
3 % This software is provided AS-IS with no warranty, either express or
6 % This software is distributed under license and may not be copied,
7 % modified or distributed except as expressly authorized under the terms
8 % of the license contained in the file LICENSE in this distribution.
10 % For more information about licensing, please refer to
11 % http://www.ghostscript.com/licensing/. For information on
12 % commercial licensing, go to http://www.artifex.com/licensing/ or
13 % contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14 % San Rafael, CA 94903, U.S.A., +1(415)492-9861.
16 % $Id: pdfopt.ps,v 1.20 2003/06/02 19:52:58 alexcher Exp $
17 % PDF linearizer ("optimizer").
19 .currentglobal true .setglobal
20 /pdfoptdict 200 dict def
23 % This linearizer is designed for simplicity, not for performance.
24 % See the main program (the last procedure in the file) for comments
25 % describing the main processing sequence.
27 % ---------------- Utilities ---------------- %
29 % ------ Data structures ------ %
31 % Distinguish dictionaries, arrays, and everything else.
32 /ifdaelse { % <obj> <dictproc> <arrayproc> <otherproc> ifdaelse -
33 3 index type dup /dicttype eq {
36 dup /arraytype ne exch /packedarraytype ne and {
42 % Implement dynamically growable arrays using a dictionary.
43 /darray { % <size> darray <darray>
46 /dadd { % <darray> <value> dadd -
47 1 index length exch put
49 /daforall { % <darray> <proc> daforall -
50 /exch cvx /get cvx 3 -1 roll /exec cvx 5 packedarray cvx
51 0 1 2 index 0 get length 1 sub 4 -1 roll for
53 /dacontents { % <darray> dacontents <array>
56 /dacontstring { % <darray> dacontstring <string>
57 0 1 index { exch pop length add } forall string
58 dup /NullEncode filter
59 % Stack: darray str filter
60 3 -1 roll { 1 index exch writestring } daforall
64 % Force an object, mapping it if it is a reference.
65 /omforcenew { % <obj> omforce <obj'> <notseen>
66 dup oforce 2 copy eq { pop true } { exch 0 get omapnew exch pop } ifelse
68 /omforce { % <obj> omforce <obj'>
71 /omget { % <dict|array> <key> omget <obj>
74 % Visit an entire tree.
75 /omvisit { % <obj> omvisit -
77 { { omvisit omvisit } forall }
78 { { omvisit } forall }
85 % Visit a tree, stopping at references to Page objects.
86 % (This is only needed for the OpenAction in the Catalog.)
87 /omvisitnopage { % <obj> omvisitnopage -
88 dup oforce dup type /dicttype eq {
89 /Type .knownget { /Page eq } { false } ifelse
96 { { omvisitnopage omvisitnopage } forall }
97 { { omvisitnopage } forall }
106 % Collect the list of currently mapped object numbers, in order.
107 /omapped { % - omapped <obj#s>
108 RMap ld_length larray exch lgrowto
110 2 index 3 1 roll 1 sub exch lput
114 % Collect the list of object numbers passed to omap by a procedure.
115 /visited { % <proc> visited <obj#s>
116 false currentomap 2 .execn
120 % ------ Output ------ %
122 % Provide a framework for closure-based streams.
123 .currentglobal false .setglobal
124 userdict /clostreams 20 dict put % stream -> [data endproc]
126 % Create a closure-based stream.
127 /clostream { % <data> <proc> <endproc> clostream <stream>
128 2 index 3 -1 roll /exec load 3 packedarray cvx
130 % Stack: data endproc stream
131 clostreams 1 index 5 -2 roll 2 array astore put
133 % Close a closure-based stream.
134 /closend { % <stream> closend <result>
135 dup closefile clostreams exch
136 2 copy get 3 1 roll undef aload pop exec
139 % Implement in-memory output streams.
140 /msproc { % <data> <more> <accum> msproc <scratch>
141 3 -1 roll dadd { 100 string } { () } ifelse
143 /mstream { % - mstream <mstream>
144 10 darray {msproc} {dacontstring} clostream
146 /mcontents { % <mstream> mcontents <string>
150 % Implement a stream that only keeps track of its position.
151 % (All streams should do this, but the PLRM doesn't require it.)
152 /posbuf 100 string def
153 /posproc { % <data> <more> <accum> posproc <scratch>
154 0 2 copy get 5 -1 roll length add put
157 /postream { % - postream <postream>
158 [0] {posproc} {0 get} clostream
160 /poslength { % <postream> poslength <pos>
164 % Implement streams with variable-bit-width data.
165 % Note that these are dictionary objects, not stream objects.
166 /bitstream { % <stream> bitstream <bstream>
167 4 dict begin /S exch def /N 8 def /B 0 def
170 /bitwrite { % <bstream> <value> <width> bitwrite -
171 PDFOPTDEBUG { ( ) print 1 index =only (:) print dup = } if
173 N exch sub dup 0 ge {
174 /N exch def N bitshift B add
176 2 copy bitshift B add S exch write
178 { 8 add dup 0 ge { exit } if
179 2 copy bitshift 255 and S exch write
181 /N 1 index def bitshift 255 and
185 /bitflush { % <bstream> bitflush -
186 begin N 8 ne { S B write /B 0 def /N 8 def } if end
189 /bwn { % <value> <width> bwn -
191 2 exch exp ge { % v w v>=2**w
192 /bwn cvx /rangecheck signalerror
194 bits 3 1 roll bitwrite
197 % Capture OFile output on the temporary file, in memory, or just as a length.
198 /totemp { % <proc> totemp <start> <end>
199 TFile fileposition OFile
200 /OFile TFile def 3 .execn
204 /tomemory { % <proc> tomemory <string>
205 OFile /OFile mstream def 2 .execn
206 OFile mcontents exch /OFile exch def
208 /tolength { % <proc> tolength <string>
209 OFile /OFile postream def 2 .execn
210 OFile poslength exch /OFile exch def
213 % Copy a range of bytes from TFile to OFile.
214 /copyrange { % <start> <end> copybytes -
215 TFile 2 index setfileposition
216 exch sub 1024 string exch {
218 2 copy 1 index length .min 0 exch getinterval
219 TFile exch readstring pop OFile exch writestring
220 1 index length sub dup 0 le { exit } if
224 % Pad with blanks to a specified position.
225 /padto { % <pos> padto -
226 OFile fileposition sub
228 (ERROR: file position incorrect by ) print =
229 /padto cvx /rangecheck signalerror
235 % ---------------- Read objects into memory ---------------- %
237 /touch { % <object> touch -
239 { touch touch } forall
242 % Executable array, must be an indirect object.
243 dup 0 get resolved? { pop pop } { oforce touch } ifelse
252 % ---------------- Replace references with referents ---------------- %
254 /replaceable? { % <value> replaceable? <bool>
255 dup type /integertype eq exch xcheck not and
257 /replacement { % <obj|ref> replacement <obj'>
258 dup oforce dup replaceable? { exch } if pop
261 /replacerefs { % <object> replacerefs <object>
264 2 index 2 index undef
265 exch replacement exch replacement
269 0 1 2 index length 1 sub {
270 1 index exch 2 copy get replacement put
276 /replaceReferences { % - replaceReferences -
277 Objects { replacerefs pop } lforall
278 % Delete replaced objects.
279 0 1 Objects llength 1 sub {
280 Objects 1 index lget replaceable? {
281 PDFOPTDEBUG { (Deleting ) print dup = } if
282 Generations 1 index 0 lput
287 % ---------------- Create new objects ---------------- %
289 /createObjects { % [<obj>...] createObjects <firstobj#>
291 dup 3 index length add growPDFobjects
292 % Stack: objects objn objn
294 Objects 2 index 3 -1 roll lput
295 Generations 1 index 1 lput
300 % ---------------- Propagate attributes ---------------- %
303 % Never propagate these.
304 /Type dup /Kids dup /Count dup /Parent dup
305 % Handle Resources specially.
310 /mergeres { % <fromdict> <todict> mergeres -
311 % Values in todict take priority over fromdict.
312 1 index /Resources .knownget {
313 1 index /Resources .knownget {
314 % Stack: fromdict todict fromres tores
315 exch oforce exch oforce
316 % todict's Resources may be shared, so make a copy.
317 dup length dict .copydict
319 % Stack: fromdict todict tores' fromkey fromvalue
320 2 index 2 index knownoget {
321 % Stack: fromdict todict tores' fromkey fromvalue tovalue
323 % ProcSet is an array, other types are dictionaries.
324 dup type /dicttype eq {
325 % Dictionary, not ProcSet.
326 exch dup length 2 index length add dict .copydict .copydict
328 % Array or packed array, ProcSet.
329 % Use dictionaries to do the merge.
330 dup length 2 index length add dict begin
331 exch { dup def } forall { dup def } forall
332 mark currentdict end { pop } forall .packtomark
337 } if /Resources exch put pop
343 % Merge attributes other than Resources.
344 /mergeattrs { % <fromdict> <todict> mergeattrs <fromdict> <todict>
345 % Values in todict take priority over fromdict.
347 % Stack: fromdict todict fromkey fromvalue
348 //nopropattrs 2 index known {
351 2 index 2 index known { pop pop } { 2 index 3 1 roll put } ifelse
356 % Propagate attributes to a subtree.
357 /proppage { % <attrs> <subtree> proppage -
358 % We should be able to tell when we reach a leaf
359 % by finding a Type unequal to /Pages. Unfortunately,
360 % some files distributed by Adobe lack the Type key
361 % in some of the Pages nodes! Instead, we check for Kids.
362 dup /Kids knownoget {
363 % Accumulate inherited values.
365 % Stack: kids attrs pagesnode
366 dup length dict .copydict mergeattrs
367 dup 3 1 roll mergeres
368 exch { oforce 1 index exch proppage } forall pop
370 % Merge inherited values into the leaf.
375 % Propagate attributes to all pages.
376 /propagateAttributes { % - propagateAttributes -
377 0 dict Trailer /Root oget /Pages oget proppage
380 % ---------------- Identify document-level objects ---------------- %
382 /identifyDocumentObjects { % - identifyDocumentObjects <obj#s>
385 dup /PageMode .knownget { omvisit } if
386 % Don't allow omvisit to trace references to Page objects.
387 dup /OpenAction .knownget { omvisitnopage } if
388 Trailer /Encrypt .knownget { omvisit } if
389 dup /Threads .knownget {
390 omforce { omvisit } forall
392 dup /AcroForm .knownget { omvisit } if
397 % ---------------- Identify the objects of each page ---------------- %
399 /identifyfont { % <fontref> identifyfont -
401 exch /FontDescriptor eq {
402 omforce dup /Flags .knownget { 32 and 0 ne } { false } ifelse
404 exch dup dup /FontFile eq exch /FontFile2 eq or
405 exch /FontFile3 eq or 2 index and {
417 % Collect all the objects referenced from a page. The first object number
418 % (which may not be the smallest one) is that of the page object itself.
419 /identifyPageObjects { % <extra> <page#> identifyPageObjects <obj#s>
421 (%Objects for page: ) print dup =
426 /images 10 darray def
427 /fontfiles 10 darray def
430 % Stack: pageobj# extra page
431 % Visit any extra objects if applicable.
433 % Visit Annots, if any.
434 % We don't try to defer the drawing information.
435 dup /Annots .knownget { omvisit } if
437 dup /B .knownget { omvisit } if
438 % Visit resources dictionaries.
439 dup /Resources .knownget {
441 % Visit the first-level Resource dictionaries.
444 % Visit the resources themselves.
445 % Skip Image XObjects, and FontFile streams if the
446 % FontDescriptor Flags have bit 6 set.
447 % We don't try to visit the resources in the order in which
448 % the Contents stream(s) reference(s) them.
449 exch dup /XObject eq {
451 dup oforce /Subtype get /Image eq {
459 oforce { identifyfont pop } forall
466 % Visit the Contents stream(s).
467 dup /Contents .knownget { omvisit } if
468 % Visit Image XObjects. We don't try to visit them in
470 images { omvisit } daforall
471 % Visit FontFile streams. We don't try to visit them in
473 fontfiles { omvisit } daforall
476 % Stack: pageobj# obj#s_larray
478 2 copy eq { pop } { exch } ifelse
479 } lforall counttomark 1 roll ]
481 (%Objects = ) print dup === flush
485 % Identify the objects of the first page.
486 /identifyFirstPageObjects { % - identifyFirstPageObjects <obj#s>
487 Trailer /Root oget null
488 1 index /PageMode knownoget {
490 1 index /Outlines knownoget { exch pop } if
493 1 identifyPageObjects
496 % Identify the non-shared objects of the other pages, and the shared objects.
497 % Note that the page objects themselves may appear to be shared, because of
498 % references from Dest entries in annotations, but they must be treated as
499 % non-shared. Note also that some objects referenced on the first page may
500 % also be referenced from other pages.
501 /identifyOtherPageObjects { % - identifyOtherPageObjects [<pageobj#s> ...]
504 /marks lstring Objects llength lgrowto def
505 % Collect objects of other pages and identify sharing.
506 [ 2 1 pdfpagecount { null exch identifyPageObjects } for ]
508 { marks exch 2 copy lget 1 add 254 .min lput } forall
510 % Mark document-level and first page objects.
511 CatalogNs { marks exch 255 lput } lforall
512 FirstPageNs { marks exch 255 lput } forall
513 % Mark the page objects themselves as non-shared.
515 0 get marks exch 1 lput
517 % Collect the non-shared objects of each page.
521 marks 1 index lget 1 ne { pop } if
524 % Collect the shared objects of each page.
528 marks 1 index lget dup 1 le exch 255 eq or { pop } if
532 % Collect the shared objects.
533 [ 1 1 marks llength 1 sub {
534 marks 1 index lget dup 1 le exch 255 eq or { pop } if
540 % Identify objects not associated with any page.
541 /identifyNonPageObjects { % - identifyNonPageObjects <obj#s>
543 /marks lstring Objects llength lgrowto def
545 LPDictN marks exch 1 lput
546 PHSN marks exch 1 lput
547 CatalogNs { marks exch 1 lput } lforall
548 FirstPageNs { marks exch 1 lput } forall
549 SharedNs { marks exch 1 lput } forall
550 OtherPageNs { { marks exch 1 lput } forall } forall
552 %****** PUT THESE IN A REASONABLE ORDER ******
555 1 1 Objects llength 1 sub {
556 marks 1 index lget 0 eq {
557 Generations exch lget 0 ne { 1 add } if
565 1 1 Objects llength 1 sub {
566 marks 1 index lget 0 eq {
568 Generations 1 index lget 0 ne {
570 npobj 2 index % i nobj 0
586 % ---------------- Assign object numbers ---------------- %
588 % Assign object numbers to all objects that will be copied.
589 % Return the first (translated) object number in the First Page xref table.
590 /assignObjectNumbers { % - assignObjectNumbers -
591 OtherPageNs { { omap pop } forall } forall
592 SharedNs { omap pop } forall
593 NonPageNs { omap pop } lforall
594 % Assign object numbers for the First Page xref table last.
595 LPDictN omap % don't pop, this is the return value
596 CatalogNs { omap pop } lforall
597 FirstPageNs { omap pop } forall
601 % ---------------- Create the LPDict ---------------- %
603 % Create the contents of the LPDict.
604 /createLPDict { % <phsstart> <phsend> <firstpageend>
605 % <xref0start> <filelength> createLPDict -
607 dup /Linearized 1 put
608 dup /L 4 -1 roll put % filelength
609 dup /T 4 -1 roll put % xref0start
610 dup /E 4 -1 roll put % firstpageend
611 dup /H 5 -2 roll 1 index sub 2 array astore put % phsstart, end-start
612 dup /O 1 pdffindpageref 0 get omap put
616 % ---------------- Adjust object positions ---------------- %
618 /adjustObjectPositions { % <boundary> <deltabelow> <deltaabove>
619 % adjustObjectPositions -
620 % Objects fall into 4 categories: LPDict, PHS, Catalog, and others.
621 % We handle the first two as special cases.
623 % Stack: bdy below above key loc
624 dup 5 index ge { 2 } { 3 } ifelse index add
626 } ld_forall pop pop pop
627 XRef LPDictN omap HeaderLength ld_put
628 XRef PHSN omap PHSStart ld_put
631 % ---------------- Write the output file ---------------- %
633 % Write objects identified by object number.
634 /writeobjn { % <obj#> writeobjn -
635 Generations 1 index lget pdfwriteobj
637 /writeobjns { % <obj#s> writeobjns -
640 /lwriteobjns { % <obj#s> writeobjns -
641 { writeobjn } lforall
644 % Write a part of the output file.
645 /writePart { % <proc> <label> writePart -
647 dup print ( count=) print count =only ( start=) print
648 OFile { .fileposition } stopped { pop (???) } if =
651 OFile { .fileposition } stopped { pop (???) } if =
658 /writePart1 { % - writePart1 -
664 % Write the linearization parameters dictionary.
665 /writePart2 { % - writePart2 -
671 % Write the First Page xref table and trailer.
672 % Free variables: FirstPageXN.
673 /writePart3 { % <xrefstart> writePart3 -
675 FirstPageXN NObjects 1 add 1 index sub pdfwritexref
676 Trailer dup length 1 add dict copy
677 dup /Size NObjects 1 add put
678 dup /Prev 4 -1 roll put
684 % Write the Catalog and other required document-level objects.
685 % Free variables: CatalogNs.
686 /writePart4 { % - writePart4 -
688 CatalogNs lwriteobjns
692 % Write the Primary Hint Stream.
693 /writePart5 { % - writePart5 -
699 % Write the First Page's objects.
700 % Free variables: FirstPageNs.
701 /writePart6 { % - writePart6 -
703 FirstPageNs writeobjns
707 % Write the objects of other pages (Page + non-shared objects).
708 % Free variables: OtherPageNs.
709 /writePart7 { % - writePart7 <lengths>
712 OFile fileposition exch
713 writeobjns OFile fileposition exch sub
718 % Write the shared objects of other pages.
719 % Free variables: SharedNs.
720 /writePart8 { % - writePart8 -
726 % Write the other objects not associated with pages.
727 % Free variables: NonPageNs.
728 /writePart9 { % - writePart9 -
730 NonPageNs { writeobjn } lforall
734 % Write the main xref table and trailer.
735 % Free variables: FirstPageXN.
736 /writePart11xref { % writePart11 -
738 0 FirstPageXN pdfwritexref
739 } (part11xref) writePart
741 /writePart11rest { % <part3start> writePart11rest -
743 << /Size FirstPageXN >> pdfwritetrailer
745 } (part11rest) writePart
748 % ---------------- Write hint tables ---------------- %
750 /bitsneeded { % <maxvalue> bitsneeded <#bits>
751 0 exch { dup 0 eq { pop exit } if exch 1 add exch 2 idiv } loop
754 % Find the start and end of objects in the output.
755 /omstart { % <obj#> omstart <pos>
756 PDFOPTDEBUG { (start\() print dup =only } if
758 PDFOPTDEBUG { (=>) print dup =only } if
760 PDFOPTDEBUG { (\) = ) print dup = } if
762 /omend { % <obj#> omend <pos>
763 % The end of an object is the start of the next object.
764 % The caller must be sure that this object is not the last one
766 PDFOPTDEBUG { (end\() print dup =only } if
768 PDFOPTDEBUG { (=>) print dup =only } if
770 % Check that the requested object wasn't the last one in part 6:
771 % the next object in the output file is the first in part 7.
772 PHSN omap 1 index eq { pop 1 } if
774 PDFOPTDEBUG { (\) = ) print dup = } if
776 /omlength { % <obj#> omlength <length>
777 dup omend exch omstart sub
780 % Find the Contents of a page.
781 /contentsobjects { % <pagedict> contentsobjects <firstobj#> <lastobj#> true
782 % <pagedict> contentsobjects false
783 /Contents .knownget {
785 dup type /dicttype eq {
786 pop 0 get dup true % ref ref
790 dup 0 get 0 get % [] 1st
792 length 1 sub get 0 get % 1st last
803 /contentsstart { % <pagedict> contentsstart <pos> true
804 % <pagedict> contentsstart false
805 contentsobjects { pop omstart true } { false } ifelse
808 /contentslength { % <pagedict> contentslength <length>
809 contentsobjects { omend exch omstart sub } { 0 } ifelse
813 /writePageOffsetHints {
814 PDFOPTDEBUG { /writePageOffsetHints == } if
816 /bits OFile bitstream def
818 % Calculate least length of a page.
819 FirstPageLength OtherPageLengths { .min } forall
822 % Calculate least contents length.
823 FirstPageNs 0 get Objects exch lget contentslength
824 OtherPageNs { 0 get Objects exch lget contentslength .min } forall
827 % The Adobe documentation says that all versions of Acrobat
828 % require item 8 (mincl) to be zero. Patch this here.
831 % Calculate bits needed to represent greatest page length.
832 FirstPageLength OtherPageLengths { .max } forall
833 minpl sub bitsneeded /maxplbits exch def
834 % Calculate bits needed to represent the greatest Contents length.
835 FirstPageNs 0 get Objects exch lget contentslength
836 OtherPageNs { 0 get Objects exch lget contentslength .max } forall
837 mincl sub bitsneeded /maxclbits exch def
839 % Per Adobe documentation, Acrobat requires that item 5 (maxplbits)
840 % be equal to item 9 (maxclbits). Set both to the max of the two.
841 maxplbits maxclbits .max /maxplbits 1 index def /maxclbits exch def
843 % Mapping from object number to shared object reference
844 /shared_id_dict FirstPageNs length SharedNs length add dict begin
845 0 FirstPageNs { 1 index def 1 add } forall
846 SharedNs { 1 index def 1 add } forall
850 % Table F.3 Page offset hint table, header section
852 % 1: Least number of objects in a page:
853 FirstPageNs length OtherPageNs { length .min } forall
854 /minnop 1 index def 32 bwn
855 % 2: Location of first page's Page object:
856 FirstPageNs 0 get omap XRef exch ld_get 32 bwn
857 % 3: Bits needed to represent greatest # of objects in a page:
858 FirstPageNs length OtherPageNs { length .max } forall
859 minnop sub bitsneeded /maxnopbits 1 index def 16 bwn
860 % 4: Least length of a page:
862 % 5: Bits needed to represent the greatest page length:
864 % 6: Least start of Contents offset:
865 0 % (Acrobat requires that this be 0.)
866 /minsco 1 index def 32 bwn
867 % 7: Bits needed to represent the greatest start of Contents
869 0 % (Acrobat ignores this.)
870 /maxscobits 1 index def 16 bwn
871 % 8: Least contents length:
873 % 9: Bits needed to represent the greatest Contents length:
875 % 10: Bits needed to represent the greatest number of Shared
877 FirstPageNs length SharedPageNs { length .max } forall bitsneeded
878 /maxsorbits 1 index def 16 bwn
879 % 11: Bits needed to identify a Shared Object:
880 FirstPageNs length SharedNs length add bitsneeded
881 /sobits 1 index def 16 bwn
882 % 12: Bits needed to represent numerator of fraction:
884 /numfbits 1 index def 16 bwn
885 % 13: Denominator of fraction:
887 /denf 1 index def 16 bwn
889 % Table F.4 Page offset hint table, per-page entry
891 % 1: Number of objects in pages:
892 FirstPageNs length minnop sub maxnopbits bwn
894 length minnop sub maxnopbits bwn
898 % 2: Total length of pages in bytes;
899 FirstPageLength minpl sub maxplbits bwn
901 minpl sub maxplbits bwn
905 % 3: Number of shared objects referenced from page:
906 FirstPageNs length maxsorbits bwn
907 SharedPageNs { length maxsorbits bwn } forall
909 % 4: A shared object identifier:
910 FirstPageNs { shared_id_dict exch get sobits bwn } forall
912 { shared_id_dict exch get sobits bwn
917 % 5: Numerator of fractional position for each shared object:
918 FirstPageNs { pop 0 numfbits bwn } forall
925 % 6: Contents offsets:
926 % Following Implementation Note 133 section 6 is empty.
928 [FirstPageNs OtherPageNs aload pop] {
929 0 get Objects exch lget contentsstart { minsco sub } { 0 } ifelse
935 % 7: Contents lengths:
936 [FirstPageNs OtherPageNs aload pop] {
937 0 get Objects exch lget contentslength mincl sub maxclbits bwn
945 /writeSharedObjectHints {
946 PDFOPTDEBUG { /writeSharedObjectHints == } if
948 /bits OFile bitstream def
949 /obj_count SharedNs length FirstPageNs length add def
951 % Table F.5 Shared object hint table, header section
953 % 1: Object number of first object in Shared Objects section
955 % 2: Location of first object in Shared Objects section:
956 % If there are no shared objects,
957 % Acrobat sets this to the location of linearization
958 % parameters object (the very first object).
959 { pdfwriteheader } tomemory length 32 bwn
960 % 3: Number of Shared Object entries for first page:
961 FirstPageNs length 32 bwn
962 % 4: Number of Shared Object entries for Shared Objects
965 % 5: Bits needed to represent the greatest number of objects
966 % in a shared object group (always 0, because all groups
967 % have only 1 object):
969 % 6: Least length of a Shared Object Group in bytes:
970 16#7fffffff FirstPageNs { omlength .min } forall
971 SharedNs { omlength .min } forall
972 /minsol 1 index def 32 bwn
973 % 7: Bits needed to represent the greatest length of a
974 % Shared Object Group:
975 0 FirstPageNs { omlength .max } forall
976 SharedNs { omlength .max } forall
977 minsol sub bitsneeded
978 /maxsolbits 1 index def 16 bwn
980 % Table F.6 Shared object hint table, shared object group entry
982 % 1: Lengths of shared object groups:
983 FirstPageNs { omlength minsol sub maxsolbits bwn } forall
984 SharedNs { omlength minsol sub maxsolbits bwn } forall
987 obj_count { 0 1 bwn } repeat
989 % 3: No MD5 shared object signatures.
991 % 4: No number_number_of_objects_in_the_group - 1
995 % ---------------- Main program ---------------- %
997 /pdfOptimize { % <infile> <outfile> pdfOptimize -
999 exch pdfdict begin pdfopenfile dup begin
1005 QUIET { pop } { print (, t = ) print realtime starttime sub = flush } ifelse
1009 % Create and open a temporary file.
1010 % Because of .setsafe, we have to open it as read-write, rather than
1011 % opening for writing, then closing it and reopening it for reading.
1013 null (w+) .tempfile /TFile exch def /TFileName exch def
1016 % Read all objects into memory.
1021 % Encrypted files are not yet supported.
1022 Trailer /Encrypt known {
1023 (ERROR: Encrypted files are not yet supported.) = flush
1024 /pdfOptimize cvx /limitcheck signalerror
1027 % Replace indirect references to numbers. This is needed
1028 % for the Length of streams, and doesn't hurt anything else.
1031 (Replaced references) now
1033 % Create the two new objects: the linearization parameter
1034 % dictionary, and the Primary Hint Stream.
1037 /PHS 10 dict cvx def % executable = stream
1038 [LPDict PHS] createObjects
1039 /LPDictN 1 index def 1 add
1041 PDFOPTDEBUG { << /LPDictN LPDictN /PHSN PHSN >> === } if
1043 % Count the number of objects in the output.
1045 0 0 1 Objects llength 1 sub {
1046 Generations exch lget 0 ne { 1 add } if
1049 QUIET not { NObjects =only ( objects total) = flush } if
1051 % Propagate inherited attributes down the page tree.
1054 (Propagated attributes) now
1056 % Identify the document-level objects (part 4).
1058 identifyDocumentObjects /CatalogNs exch def
1059 QUIET not { CatalogNs === flush } if
1060 (Identified Catalog) now
1062 % Identify the first page's objects (part 6),
1063 % including the Outlines tree if appropriate.
1066 /FirstPageNs identifyFirstPageObjects def
1067 QUIET not { FirstPageNs === flush } if
1068 (Identified first page) now
1070 % Identify shared vs. non-shared objects for remaining pages
1073 identifyOtherPageObjects
1075 /SharedPageNs exch def
1076 /OtherPageNs exch def
1077 QUIET not { OtherPageNs === flush SharedNs === flush } if
1078 (Identified other pages) now
1080 % Identify objects not associated with any page (part 9).
1082 /NonPageNs identifyNonPageObjects def
1083 QUIET not { NonPageNs { === } forall flush } if
1084 (Identified non-pages) now
1086 % Assign final object numbers to all the objects.
1087 % (The omap is currently empty.)
1089 /FirstPageXN assignObjectNumbers def
1090 (Assigned objects #s) now
1092 % Write the document-level objects (part 4).
1094 { writePart4 } totemp
1095 /CatalogTempEnd exch def /CatalogTempStart exch def
1098 % Write the first page's objects (part 6).
1100 { writePart6 } totemp
1101 /FirstPageTempEnd exch def /FirstPageTempStart exch def
1102 (Wrote first page) now
1104 % Write the non-shared objects for other pages (part 7).
1106 { writePart7 /OtherPageLengths exch def } totemp
1107 /OtherPageTempEnd exch def /OtherPageTempStart exch def
1108 (Wrote other pages) now
1110 % Write the shared objects for other pages (part 8).
1112 { writePart8 } totemp
1113 /SharedTempEnd exch def /SharedTempStart exch def
1114 (Wrote shared objects) now
1116 % Write the objects not associated with pages (part 9).
1118 { writePart9 } totemp
1119 /NonPageTempEnd exch def /NonPageTempStart exch def
1121 % Compute conservative lengths of parts 2,3,5,11 of the output.
1122 % It's OK for these to be too large, but not too small.
1124 % Make dummy XRef entres for LPDict and PHS.
1125 XRef LPDictN omap 0 ld_put
1126 XRef PHSN omap 0 ld_put
1128 /HeaderLength { % this is exact
1131 /CatalogLength % this is exact
1132 CatalogTempEnd CatalogTempStart sub def % part 4
1133 /FirstPageLength % this is exact
1134 FirstPageTempEnd FirstPageTempStart sub def % part 6
1135 /OtherObjectsLength % this is exact
1136 NonPageTempEnd OtherPageTempStart sub def % parts 7,8,9
1137 /ObjectsLength % this is exact
1138 CatalogLength FirstPageLength add OtherObjectsLength add def
1139 /XrefLength { % part 11
1140 % The LPDict must end within the first 1024 bytes,
1141 % so the start of the FirstPage xref table can't exceed 1024.
1142 writePart11xref 1024 writePart11rest
1144 /NominalFileLength % Make a generous allowance for parts 2,3,5.
1145 HeaderLength ObjectsLength 3 mul add 10000 add 99999 .max def
1146 /FirstPageXrefLength { % part 3
1147 NominalFileLength writePart3
1149 /LPDictLength { % part 2
1150 NominalFileLength dup 2 mul 2 copy add 1 index dup createLPDict writePart2
1153 % Compute a few additional values from the above.
1157 OFile FirstPageXN write=
1159 HeaderLength LPDictLength add
1160 /FirstPageXrefStart 1 index def
1161 FirstPageXrefLength add
1162 /CatalogStart 1 index def
1163 CatalogLength add % phsstart
1166 % Adjust the object positions ignoring PHS.
1167 % (Writing the PHS needs these.)
1169 0 0 CatalogStart CatalogTempStart sub adjustObjectPositions
1170 % Make a temporary XRef entry for the PHS, for the benefit of omend.
1171 XRef PHSN omap CatalogStart ld_put
1172 (Adjusted positions) now
1174 % Construct the hint tables (part 5).
1176 { writePageOffsetHints } totemp
1177 pop /PHSTempStart exch def
1178 { writeSharedObjectHints } totemp
1179 exch PHSTempStart sub PHS /S 3 -1 roll put
1180 PHSTempStart sub /PHSTempLength exch def
1183 % Prepare to read TFile.
1185 % /TFile TFileName (r) file def
1189 dup /FilePosition PHSTempStart put
1190 dup /Length PHSTempLength put
1192 /PHSLength { writePart5 } tolength def
1194 % Construct the linearization parameter dictionary (part 2).
1197 dup PHSLength add % phsend
1198 /FirstPageStart 1 index def
1199 dup FirstPageLength add % firstpageend
1200 dup OtherObjectsLength add
1201 /XrefStart 1 index def
1202 XrefBeginLength add % xref0start
1203 dup XrefBeginLength sub XrefLength add % fileend
1204 % Because of a bug, Acrobat Reader doesn't recognize any file
1205 % shorter than 1K as linearized. Patch this here.
1207 /FileLength 1 index def
1210 % Adjust the object positions again, taking the PHS into account.
1212 PHSStart 0 PHSLength adjustObjectPositions
1213 (Readjusted positions) now
1215 % Finally, write the output file.
1219 FirstPageXrefStart padto
1220 XrefStart writePart3
1222 CatalogTempStart CatalogTempEnd copyrange % part 4
1224 FirstPageStart padto
1225 FirstPageTempStart NonPageTempEnd copyrange % parts 6,7,8,9
1226 % No Overflow Hint Stream (part 10).
1229 { FirstPageXrefStart writePart11rest } tomemory
1230 FileLength 1 index length sub padto ows
1231 (Wrote output file) now
1235 TFile closefile TFileName deletefile
1236 end % temporary dict
1243 % Check for command line arguments.
1246 % Load the pdfwrite utilities if necessary.
1247 /omapinit where { pop } { (pdfwrite.ps) runlibfile } ifelse
1249 aload pop exch (r) file exch (w) file
1250 3000000 setvmthreshold
1252 pdfoptdict begin pdfOptimize end
1255 (Usage: gs -dNODISPLAY -- pdfopt.ps input.pdf output.pdf) = flush quit