]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/ghostscript/pdfopt.ps
merge
[plan9front.git] / sys / lib / ghostscript / pdfopt.ps
1 %    Copyright (C) 2000, 2001 Aladdin Enterprises.  All rights reserved.
2
3 % This software is provided AS-IS with no warranty, either express or
4 % implied.
5
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.
9
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.
15
16 % $Id: pdfopt.ps,v 1.20 2003/06/02 19:52:58 alexcher Exp $
17 % PDF linearizer ("optimizer").
18
19 .currentglobal true .setglobal
20 /pdfoptdict 200 dict def
21 pdfoptdict begin
22
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.
26
27 % ---------------- Utilities ---------------- %
28
29 % ------ Data structures ------ %
30
31 % Distinguish dictionaries, arrays, and everything else.
32 /ifdaelse {             % <obj> <dictproc> <arrayproc> <otherproc> ifdaelse -
33   3 index type dup /dicttype eq {
34     pop pop pop
35   } {
36     dup /arraytype ne exch /packedarraytype ne and {
37       exch
38     } if pop exch pop
39   } ifelse exec
40 } bind def
41
42 % Implement dynamically growable arrays using a dictionary.
43 /darray {               % <size> darray <darray>
44   dict
45 } bind def
46 /dadd {                 % <darray> <value> dadd -
47   1 index length exch put
48 } bind def
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
52 } bind def
53 /dacontents {           % <darray> dacontents <array>
54   [ exch { } daforall ]
55 } bind def
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
61   closefile
62 } bind def
63
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
67 } bind def
68 /omforce {              % <obj> omforce <obj'>
69   omforcenew pop
70 } bind def
71 /omget {                % <dict|array> <key> omget <obj>
72   get omforce
73 } bind def
74 % Visit an entire tree.
75 /omvisit {              % <obj> omvisit -
76   omforcenew {
77     { { omvisit omvisit } forall }
78     { { omvisit } forall }
79     { pop }
80     ifdaelse
81   } {
82     pop
83   } ifelse
84 } bind def
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
90   } {
91     pop false
92   } ifelse {
93     pop         % Page reference
94   } {
95     omforcenew {
96       { { omvisitnopage omvisitnopage } forall }
97       { { omvisitnopage } forall }
98       { pop }
99       ifdaelse
100     } {
101       pop
102     } ifelse
103   } ifelse
104 } bind def
105
106 % Collect the list of currently mapped object numbers, in order.
107 /omapped {              % - omapped <obj#s>
108   RMap ld_length larray exch lgrowto
109   RMap {
110     2 index 3 1 roll 1 sub exch lput
111   } ld_forall
112 } bind def
113
114 % Collect the list of object numbers passed to omap by a procedure.
115 /visited {              % <proc> visited <obj#s>
116   false currentomap 2 .execn
117   omapped exch setomap
118 } bind def
119
120 % ------ Output ------ %
121
122 % Provide a framework for closure-based streams.
123 .currentglobal false .setglobal
124 userdict /clostreams 20 dict put        % stream -> [data endproc]
125 .setglobal
126 % Create a closure-based stream.
127 /clostream {            % <data> <proc> <endproc> clostream <stream>
128   2 index 3 -1 roll /exec load 3 packedarray cvx
129   /NullEncode filter
130                 % Stack: data endproc stream
131   clostreams 1 index 5 -2 roll 2 array astore put
132 } bind def
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
137 } bind def
138
139 % Implement in-memory output streams.
140 /msproc {               % <data> <more> <accum> msproc <scratch>
141   3 -1 roll dadd { 100 string } { () } ifelse
142 } bind def
143 /mstream {              % - mstream <mstream>
144   10 darray {msproc} {dacontstring} clostream
145 } bind def
146 /mcontents {            % <mstream> mcontents <string>
147   closend
148 } bind def
149
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
155   pop //posbuf
156 } bind def
157 /postream {             % - postream <postream>
158   [0] {posproc} {0 get} clostream
159 } bind def
160 /poslength {            % <postream> poslength <pos>
161   closend
162 } bind def
163
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
168   currentdict end
169 } bind def
170 /bitwrite {             % <bstream> <value> <width> bitwrite -
171   PDFOPTDEBUG { ( ) print 1 index =only (:) print dup = } if
172   3 -1 roll begin
173   N exch sub dup 0 ge {
174     /N exch def N bitshift B add
175   } {
176     2 copy bitshift B add S exch write
177                         % Stack: value -left
178     { 8 add dup 0 ge { exit } if
179       2 copy bitshift 255 and S exch write
180     } loop
181     /N 1 index def bitshift 255 and
182   } ifelse /B exch def
183   end
184 } bind def
185 /bitflush {             % <bstream> bitflush -
186   begin N 8 ne { S B write /B 0 def /N 8 def } if end
187 } bind def
188
189 /bwn {                  % <value> <width> bwn -
190   2 copy                % v w v w
191   2 exch exp ge {       % v w v>=2**w
192     /bwn cvx /rangecheck signalerror
193   } if
194   bits 3 1 roll bitwrite
195 } def
196
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
201   /OFile exch def
202   TFile fileposition
203 } bind def
204 /tomemory {             % <proc> tomemory <string>
205   OFile /OFile mstream def 2 .execn
206   OFile mcontents exch /OFile exch def
207 } bind def
208 /tolength {             % <proc> tolength <string>
209   OFile /OFile postream def 2 .execn
210   OFile poslength exch /OFile exch def
211 } bind def
212
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 {
217                 % Stack: buf left
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
221   } loop pop pop
222 } bind def
223
224 % Pad with blanks to a specified position.
225 /padto {                % <pos> padto -
226   OFile fileposition sub
227   dup 0 lt {
228     (ERROR: file position incorrect by ) print =
229     /padto cvx /rangecheck signalerror
230   } {
231     { ( ) ows } repeat
232   } ifelse
233 } bind def
234
235 % ---------------- Read objects into memory ---------------- %
236
237 /touch {                % <object> touch -
238   {
239     { touch touch } forall
240   } {
241     dup xcheck {
242                 % Executable array, must be an indirect object.
243       dup 0 get resolved? { pop pop } { oforce touch } ifelse
244     } {
245       { touch } forall
246     } ifelse
247   } {
248     pop
249   } ifdaelse
250 } bind def
251
252 % ---------------- Replace references with referents ---------------- %
253
254 /replaceable? {         % <value> replaceable? <bool>
255   dup type /integertype eq exch xcheck not and
256 } bind def
257 /replacement {          % <obj|ref> replacement <obj'>
258   dup oforce dup replaceable? { exch } if pop
259 } bind def
260
261 /replacerefs {          % <object> replacerefs <object>
262   {
263     dup {
264       2 index 2 index undef
265       exch replacement exch replacement
266       2 index 3 1 roll put
267     } forall
268   } {
269     0 1 2 index length 1 sub {
270       1 index exch 2 copy get replacement put
271     } for
272   } {
273   } ifdaelse
274 } bind def
275
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
283     } if pop
284   } for
285 } bind def
286
287 % ---------------- Create new objects ---------------- %
288
289 /createObjects {        % [<obj>...] createObjects <firstobj#>
290   Objects llength dup
291   dup 3 index length add growPDFobjects
292                 % Stack: objects objn objn
293   3 1 roll exch {
294     Objects 2 index 3 -1 roll lput
295     Generations 1 index 1 lput
296     1 add
297   } forall pop
298 } bind def
299
300 % ---------------- Propagate attributes ---------------- %
301
302 /nopropattrs <<
303         % Never propagate these.
304   /Type dup /Kids dup /Count dup /Parent dup
305         % Handle Resources specially.
306   /Resources dup
307 >> def
308
309 % Merge Resources.
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
318       exch {
319                 % Stack: fromdict todict tores' fromkey fromvalue
320         2 index 2 index knownoget {
321                 % Stack: fromdict todict tores' fromkey fromvalue tovalue
322           exch oforce exch
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
327           } {
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
333           } ifelse
334         } if
335         2 index 3 1 roll put
336       } forall
337     } if /Resources exch put pop
338   } {
339     pop pop
340   } ifelse
341 } bind def
342
343 % Merge attributes other than Resources.
344 /mergeattrs {           % <fromdict> <todict> mergeattrs <fromdict> <todict>
345                 % Values in todict take priority over fromdict.
346   1 index {
347                 % Stack: fromdict todict fromkey fromvalue
348     //nopropattrs 2 index known {
349       pop pop
350     } {
351       2 index 2 index known { pop pop } { 2 index 3 1 roll put } ifelse
352     } ifelse
353   } forall
354 } bind def
355
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.
364     3 1 roll
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
369   } {
370                 % Merge inherited values into the leaf.
371     mergeattrs mergeres
372   } ifelse
373 } bind def
374
375 % Propagate attributes to all pages.
376 /propagateAttributes {  % - propagateAttributes -
377   0 dict Trailer /Root oget /Pages oget proppage
378 } bind def
379
380 % ---------------- Identify document-level objects ---------------- %
381
382 /identifyDocumentObjects {      % - identifyDocumentObjects <obj#s>
383   {
384     Trailer /Root omget
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
391     } if
392     dup /AcroForm .knownget { omvisit } if
393     pop
394   } visited
395 } bind def
396
397 % ---------------- Identify the objects of each page ---------------- %
398
399 /identifyfont {         % <fontref> identifyfont -
400   omforce {
401     exch /FontDescriptor eq {
402       omforce dup /Flags .knownget { 32 and 0 ne } { false } ifelse
403       exch {
404         exch dup dup /FontFile eq exch /FontFile2 eq or
405         exch /FontFile3 eq or 2 index and {
406           fontfiles exch dadd
407         } {
408           omvisit
409         } ifelse
410       } forall pop
411     } {
412       omvisit
413     } ifelse
414   } forall
415 } bind def
416
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>
420   PDFOPTDEBUG {
421     (%Objects for page: ) print dup =
422   } if
423   pdffindpageref
424   dup 0 get 3 1 roll
425   4 dict begin
426   /images 10 darray def
427   /fontfiles 10 darray def
428   {
429     omforce
430                 % Stack: pageobj# extra page
431                 % Visit any extra objects if applicable.
432     exch omvisit
433                 % Visit Annots, if any.
434                 % We don't try to defer the drawing information.
435     dup /Annots .knownget { omvisit } if
436                 % Visit beads.
437     dup /B .knownget { omvisit } if
438                 % Visit resources dictionaries.
439     dup /Resources .knownget {
440       omforce dup {
441                 % Visit the first-level Resource dictionaries.
442         omforce pop pop
443       } forall {
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 {
450           pop oforce {
451             dup oforce /Subtype get /Image eq {
452               images exch dadd
453             } {
454               omvisit
455             } ifelse pop
456           } forall
457         } {
458           /Font eq {
459             oforce { identifyfont pop } forall
460           } {
461             oforce omvisit
462           } ifelse
463         } ifelse
464       } forall
465     } if
466                 % Visit the Contents stream(s).
467     dup /Contents .knownget { omvisit } if
468                 % Visit Image XObjects.  We don't try to visit them in
469                 % reference order.
470     images { omvisit } daforall
471                 % Visit FontFile streams.  We don't try to visit them in
472                 % reference order.
473     fontfiles { omvisit } daforall
474     pop
475   } visited end
476                 % Stack: pageobj# obj#s_larray
477   [ 3 1 roll {
478     2 copy eq { pop } { exch } ifelse
479   } lforall counttomark 1 roll ]
480   PDFOPTDEBUG {
481     (%Objects = ) print dup === flush
482   } if
483 } bind def
484
485 % Identify the objects of the first page.
486 /identifyFirstPageObjects {     % - identifyFirstPageObjects <obj#s>
487   Trailer /Root oget null
488   1 index /PageMode knownoget {
489     /UseOutlines eq {
490       1 index /Outlines knownoget { exch pop } if
491     } if
492   } if exch pop
493   1 identifyPageObjects
494 } bind def
495
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> ...]
502                                 %   <sharedobj#s>
503   4 dict begin
504   /marks lstring Objects llength lgrowto def
505                 % Collect objects of other pages and identify sharing.
506   [ 2 1 pdfpagecount { null exch identifyPageObjects } for ]
507   dup {
508     { marks exch 2 copy lget 1 add 254 .min lput } forall
509   } 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.
514   dup {
515     0 get marks exch 1 lput
516   } forall
517                 % Collect the non-shared objects of each page.
518   dup
519   [ exch {
520     [ exch {
521       marks 1 index lget 1 ne { pop } if
522     } forall ]
523   } forall ]
524                 % Collect the shared objects of each page.
525   exch
526   [ exch {
527     [ exch {
528       marks 1 index lget dup 1 le exch 255 eq or { pop } if
529     } forall ]
530   } forall ]
531
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
535   } for ]
536
537   end
538 } bind def
539
540 % Identify objects not associated with any page.
541 /identifyNonPageObjects {       % - identifyNonPageObjects <obj#s>
542   4 dict begin
543   /marks lstring Objects llength lgrowto def
544
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
551
552         %****** PUT THESE IN A REASONABLE ORDER ******
553   /npobj larray
554   0
555   1 1 Objects llength 1 sub {
556     marks 1 index lget 0 eq {
557       Generations exch lget 0 ne { 1 add } if
558     } {
559       pop
560     } ifelse
561   } for 
562   lgrowto def
563
564   0
565   1 1 Objects llength 1 sub {
566     marks 1 index lget 0 eq {
567                                           % i
568       Generations 1 index lget 0 ne {
569                                           % i
570         npobj 2 index                     % i nobj 0
571         3 -1 roll                         % nobj 0 i
572         lput 1 add
573       } {
574         pop
575       } ifelse
576     } {
577       pop
578     } ifelse
579   } for 
580   pop
581
582   npobj
583   end
584 } bind def
585
586 % ---------------- Assign object numbers ---------------- %
587
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
598   PHSN omap pop
599 } bind def
600
601 % ---------------- Create the LPDict ---------------- %
602
603 % Create the contents of the LPDict.
604 /createLPDict {                 % <phsstart> <phsend> <firstpageend>
605                                 %   <xref0start> <filelength> createLPDict -
606   LPDict
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
613   /N pdfpagecount put
614 } bind def
615
616 % ---------------- Adjust object positions ---------------- %
617
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.
622   XRef {
623                 % Stack: bdy below above key loc
624     dup 5 index ge { 2 } { 3 } ifelse index add
625     XRef 3 1 roll ld_put
626   } ld_forall pop pop pop
627   XRef LPDictN omap HeaderLength ld_put
628   XRef PHSN omap PHSStart ld_put
629 } bind def
630
631 % ---------------- Write the output file ---------------- %
632
633 % Write objects identified by object number.
634 /writeobjn {            % <obj#> writeobjn -
635   Generations 1 index lget pdfwriteobj
636 } bind def
637 /writeobjns {           % <obj#s> writeobjns -
638   { writeobjn } forall
639 } bind def
640 /lwriteobjns {          % <obj#s> writeobjns -
641   { writeobjn } lforall
642 } bind def
643
644 % Write a part of the output file.
645 /writePart {            % <proc> <label> writePart -
646   PDFOPTDEBUG {
647     dup print ( count=) print count =only ( start=) print
648     OFile { .fileposition } stopped { pop (???) } if =
649     2 .execn
650     print ( end=) print
651     OFile { .fileposition } stopped { pop (???) } if =
652   } {
653     pop exec
654   } ifelse
655 } bind def
656
657 % Write the header.
658 /writePart1 {           % - writePart1 -
659   {
660     pdfwriteheader
661   } (part1) writePart
662 } bind def
663
664 % Write the linearization parameters dictionary.
665 /writePart2 {           % - writePart2 -
666   {
667     LPDictN writeobjn
668   } (part2) writePart
669 } bind def
670
671 % Write the First Page xref table and trailer.
672 % Free variables: FirstPageXN.
673 /writePart3 {           % <xrefstart> writePart3 -
674   {
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
679     pdfwritetrailer
680     0 pdfwritestartxref
681   } (part3) writePart
682 } bind def
683
684 % Write the Catalog and other required document-level objects.
685 % Free variables: CatalogNs.
686 /writePart4 {           % - writePart4 -
687   {
688     CatalogNs lwriteobjns
689   } (part4) writePart
690 } bind def
691
692 % Write the Primary Hint Stream.
693 /writePart5 {           % - writePart5 -
694   {
695     PHSN writeobjn
696   } (part5) writePart
697 } bind def
698
699 % Write the First Page's objects.
700 % Free variables: FirstPageNs.
701 /writePart6 {           % - writePart6 -
702   {
703     FirstPageNs writeobjns
704   } (part6) writePart
705 } bind def
706
707 % Write the objects of other pages (Page + non-shared objects).
708 % Free variables: OtherPageNs.
709 /writePart7 {           % - writePart7 <lengths>
710   {
711     [ OtherPageNs {
712       OFile fileposition exch
713       writeobjns OFile fileposition exch sub
714     } forall ]
715   } (part7) writePart
716 } bind def
717
718 % Write the shared objects of other pages.
719 % Free variables: SharedNs.
720 /writePart8 {           % - writePart8 -
721   {
722     SharedNs writeobjns
723   } (part8) writePart
724 } bind def
725
726 % Write the other objects not associated with pages.
727 % Free variables: NonPageNs.
728 /writePart9 {           % - writePart9 -
729   {
730     NonPageNs { writeobjn } lforall
731   } (part9) writePart
732 } bind def
733
734 % Write the main xref table and trailer.
735 % Free variables: FirstPageXN.
736 /writePart11xref {      % writePart11 -
737   {
738     0 FirstPageXN pdfwritexref
739   } (part11xref) writePart
740 } bind def
741 /writePart11rest {      % <part3start> writePart11rest -
742   {
743     << /Size FirstPageXN >> pdfwritetrailer
744     pdfwritestartxref
745   } (part11rest) writePart
746 } bind def
747
748 % ---------------- Write hint tables ---------------- %
749
750 /bitsneeded {           % <maxvalue> bitsneeded <#bits>
751   0 exch { dup 0 eq { pop exit } if exch 1 add exch 2 idiv } loop
752 } bind def
753
754 % Find the start and end of objects in the output.
755 /omstart {              % <obj#> omstart <pos>
756   PDFOPTDEBUG { (start\() print dup =only } if
757   omap
758   PDFOPTDEBUG { (=>) print dup =only } if
759   XRef exch ld_get
760   PDFOPTDEBUG { (\) = ) print dup = } if
761 } bind def
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
765         % in part 9.
766   PDFOPTDEBUG { (end\() print dup =only } if
767   omap
768   PDFOPTDEBUG { (=>) print dup =only } if
769   1 add
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
773   XRef exch ld_get
774   PDFOPTDEBUG { (\) = ) print dup = } if
775 } bind def
776 /omlength {             % <obj#> omlength <length>
777   dup omend exch omstart sub
778 } bind def
779
780 % Find the Contents of a page.
781 /contentsobjects { % <pagedict> contentsobjects <firstobj#> <lastobj#> true
782                    % <pagedict> contentsobjects false
783   /Contents .knownget {
784     dup oforce                   % ref []
785     dup type /dicttype eq {
786       pop 0 get dup true         % ref ref
787     } {
788       exch pop                   % []
789       dup length 0 ne {
790         dup 0 get 0 get          % [] 1st
791         exch dup                 % 1st [] [] 
792         length 1 sub get 0 get   % 1st last 
793         true
794       } {
795         pop false
796       } ifelse
797     } ifelse
798   } {
799     false
800   } ifelse
801 } bind def
802
803 /contentsstart {        % <pagedict> contentsstart <pos> true
804                         % <pagedict> contentsstart false
805   contentsobjects { pop omstart true } { false } ifelse
806 } bind def
807
808 /contentslength {       % <pagedict> contentslength <length>
809   contentsobjects { omend exch omstart sub } { 0 } ifelse
810 } bind def
811
812
813 /writePageOffsetHints {
814   PDFOPTDEBUG { /writePageOffsetHints == } if
815   20 dict begin
816   /bits OFile bitstream def
817
818         % Calculate least length of a page.
819   FirstPageLength OtherPageLengths { .min } forall
820   /minpl exch def
821
822         % Calculate least contents length.
823   FirstPageNs 0 get Objects exch lget contentslength
824   OtherPageNs { 0 get Objects exch lget contentslength .min } forall
825   /mincl exch def
826
827         % The Adobe documentation says that all versions of Acrobat
828         % require item 8 (mincl) to be zero.  Patch this here.
829   /mincl 0 def
830
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
838
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
842
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
847   pop
848   currentdict end def 
849
850                 % Table F.3 Page offset hint table, header section
851
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:
861   minpl 32 bwn
862                 % 5: Bits needed to represent the greatest page length:
863   maxplbits 16 bwn
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
868                 % offset:
869   0             % (Acrobat ignores this.)
870   /maxscobits 1 index def 16 bwn
871                 % 8: Least contents length:
872   mincl 32 bwn
873                 % 9: Bits needed to represent the greatest Contents length:
874   maxclbits 16 bwn
875                 % 10: Bits needed to represent the greatest number of Shared
876                 % Object references:
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:
883   2
884   /numfbits 1 index def 16 bwn
885                 % 13: Denominator of fraction:
886   1
887   /denf 1 index def 16 bwn
888
889                 % Table F.4 Page offset hint table, per-page entry
890
891                 % 1: Number of objects in pages:
892   FirstPageNs length minnop sub maxnopbits bwn
893   OtherPageNs {
894     length minnop sub maxnopbits bwn
895   } forall
896   bits bitflush
897
898                 % 2: Total length of pages in bytes;
899   FirstPageLength minpl sub maxplbits bwn
900   OtherPageLengths {
901     minpl sub maxplbits bwn
902   } forall
903   bits bitflush
904
905                 % 3: Number of shared objects referenced from page:
906   FirstPageNs length maxsorbits bwn 
907   SharedPageNs { length maxsorbits bwn } forall
908   bits bitflush
909                 % 4: A shared object identifier:
910   FirstPageNs { shared_id_dict exch get sobits bwn } forall
911   SharedPageNs {
912     { shared_id_dict exch get sobits bwn
913     } forall
914   } forall
915   bits bitflush
916
917                 % 5: Numerator of fractional position for each shared object:
918   FirstPageNs { pop 0 numfbits bwn  } forall
919   SharedPageNs {
920     { pop 0 numfbits bwn 
921     } forall
922   } forall
923   bits bitflush
924
925                 % 6: Contents offsets:
926                 % Following Implementation Note 133 section 6 is empty.
927   maxscobits 0 gt {
928     [FirstPageNs OtherPageNs aload pop] {
929        0 get Objects exch lget contentsstart { minsco sub } { 0 } ifelse
930        maxscobits bwn
931     } forall
932     bits bitflush
933   } if
934
935                 % 7: Contents lengths:
936   [FirstPageNs OtherPageNs aload pop] {
937     0 get Objects exch lget contentslength mincl sub maxclbits bwn
938   } forall
939   bits bitflush
940
941   end
942
943 } bind def
944
945 /writeSharedObjectHints {
946   PDFOPTDEBUG { /writeSharedObjectHints == } if
947   20 dict begin
948   /bits OFile bitstream def
949   /obj_count SharedNs length FirstPageNs length add def
950
951                 % Table F.5 Shared object hint table, header section
952
953                 % 1: Object number of first object in Shared Objects section
954   0 32 bwn
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
963                 % section
964   obj_count 32 bwn
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):
968   0 16 bwn
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
979
980                 % Table F.6 Shared object hint table, shared object group entry
981
982                 % 1: Lengths of shared object groups:
983   FirstPageNs { omlength minsol sub maxsolbits bwn } forall
984      SharedNs { omlength minsol sub maxsolbits bwn } forall
985   bits bitflush
986                 % 2: MD5 flag:
987   obj_count { 0 1 bwn } repeat
988   bits bitflush
989                 % 3: No MD5 shared object signatures.
990
991                 % 4: No number_number_of_objects_in_the_group - 1
992   end
993 } bind def
994
995 % ---------------- Main program ---------------- %
996
997 /pdfOptimize {          % <infile> <outfile> pdfOptimize -
998   realtime 3 1 roll
999   exch pdfdict begin pdfopenfile dup begin
1000   40 dict begin
1001   /IDict exch def
1002   /OFile exch def
1003   /starttime exch def
1004   /now {
1005     QUIET { pop } { print (, t = ) print realtime starttime sub = flush } ifelse
1006   } def
1007   omapinit
1008   
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.
1012
1013   null (w+) .tempfile /TFile exch def /TFileName exch def
1014   .setsafe
1015
1016         % Read all objects into memory.
1017
1018   Trailer touch
1019   (Read objects) now
1020
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
1025   } if
1026
1027         % Replace indirect references to numbers.  This is needed
1028         % for the Length of streams, and doesn't hurt anything else.
1029
1030   replaceReferences
1031   (Replaced references) now
1032
1033         % Create the two new objects: the linearization parameter
1034         % dictionary, and the Primary Hint Stream.
1035
1036   /LPDict 10 dict def
1037   /PHS 10 dict cvx def          % executable = stream
1038   [LPDict PHS] createObjects
1039   /LPDictN 1 index def 1 add
1040   /PHSN exch def
1041   PDFOPTDEBUG { << /LPDictN LPDictN /PHSN PHSN >> === } if
1042
1043         % Count the number of objects in the output.
1044
1045   0 0 1 Objects llength 1 sub {
1046     Generations exch lget 0 ne { 1 add } if
1047   } for
1048   /NObjects exch def
1049   QUIET not { NObjects =only ( objects total) = flush } if
1050
1051         % Propagate inherited attributes down the page tree.
1052
1053   propagateAttributes
1054   (Propagated attributes) now
1055
1056         % Identify the document-level objects (part 4).
1057
1058   identifyDocumentObjects /CatalogNs exch def
1059   QUIET not { CatalogNs === flush } if
1060   (Identified Catalog) now
1061
1062         % Identify the first page's objects (part 6),
1063         % including the Outlines tree if appropriate.
1064
1065   pdfopencache
1066   /FirstPageNs identifyFirstPageObjects def
1067   QUIET not { FirstPageNs === flush } if
1068   (Identified first page) now
1069
1070         % Identify shared vs. non-shared objects for remaining pages
1071         % (parts 7 and 8).
1072
1073   identifyOtherPageObjects
1074   /SharedNs exch def
1075   /SharedPageNs exch def
1076   /OtherPageNs exch def
1077   QUIET not { OtherPageNs === flush SharedNs === flush } if
1078   (Identified other pages) now
1079
1080         % Identify objects not associated with any page (part 9).
1081
1082   /NonPageNs identifyNonPageObjects def
1083   QUIET not { NonPageNs { === } forall flush } if
1084   (Identified non-pages) now
1085
1086         % Assign final object numbers to all the objects.
1087         % (The omap is currently empty.)
1088
1089   /FirstPageXN assignObjectNumbers def
1090   (Assigned objects #s) now
1091
1092         % Write the document-level objects (part 4).
1093
1094   { writePart4 } totemp
1095   /CatalogTempEnd exch def /CatalogTempStart exch def
1096   (Wrote Catalog) now
1097
1098         % Write the first page's objects (part 6).
1099
1100   { writePart6 } totemp
1101   /FirstPageTempEnd exch def /FirstPageTempStart exch def
1102   (Wrote first page) now
1103
1104         % Write the non-shared objects for other pages (part 7).
1105
1106   { writePart7 /OtherPageLengths exch def } totemp
1107   /OtherPageTempEnd exch def /OtherPageTempStart exch def
1108   (Wrote other pages) now
1109
1110         % Write the shared objects for other pages (part 8).
1111
1112   { writePart8 } totemp
1113   /SharedTempEnd exch def /SharedTempStart exch def
1114   (Wrote shared objects) now
1115
1116         % Write the objects not associated with pages (part 9).
1117
1118   { writePart9 } totemp
1119   /NonPageTempEnd exch def /NonPageTempStart exch def
1120
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.
1123
1124   % Make dummy XRef entres for LPDict and PHS.
1125   XRef LPDictN omap 0 ld_put
1126   XRef PHSN omap 0 ld_put
1127
1128   /HeaderLength {       % this is exact
1129     writePart1                  % part 1
1130   } tolength def
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
1143   } tolength def
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
1148   } tolength def
1149   /LPDictLength {               % part 2
1150     NominalFileLength dup 2 mul 2 copy add 1 index dup createLPDict writePart2
1151   } tolength def
1152
1153         % Compute a few additional values from the above.
1154
1155   /XrefBeginLength {
1156     (xref\n0 ) ows
1157     OFile FirstPageXN write=
1158   } tolength def
1159   HeaderLength LPDictLength add
1160   /FirstPageXrefStart 1 index def
1161   FirstPageXrefLength add
1162   /CatalogStart 1 index def
1163   CatalogLength add             % phsstart
1164   /PHSStart exch def
1165
1166         % Adjust the object positions ignoring PHS.
1167         % (Writing the PHS needs these.)
1168
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
1173
1174         % Construct the hint tables (part 5).
1175
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
1181   (Wrote hints) now
1182
1183   % Prepare to read TFile.
1184 %  TFile closefile
1185 %  /TFile TFileName (r) file def
1186
1187   PHS
1188     dup /File TFile put
1189     dup /FilePosition PHSTempStart put
1190     dup /Length PHSTempLength put
1191   pop
1192   /PHSLength { writePart5 } tolength def
1193
1194         % Construct the linearization parameter dictionary (part 2).
1195
1196   PHSStart
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.
1206   1024 .max
1207   /FileLength 1 index def
1208   createLPDict
1209
1210         % Adjust the object positions again, taking the PHS into account.
1211
1212   PHSStart 0 PHSLength adjustObjectPositions
1213   (Readjusted positions) now
1214
1215         % Finally, write the output file.
1216
1217   writePart1
1218   writePart2
1219   FirstPageXrefStart padto
1220   XrefStart writePart3
1221   CatalogStart padto
1222   CatalogTempStart CatalogTempEnd copyrange     % part 4
1223   writePart5
1224   FirstPageStart padto
1225   FirstPageTempStart NonPageTempEnd copyrange   % parts 6,7,8,9
1226   % No Overflow Hint Stream (part 10).
1227   XrefStart padto
1228   writePart11xref
1229   { FirstPageXrefStart writePart11rest } tomemory
1230   FileLength 1 index length sub padto ows
1231   (Wrote output file) now
1232
1233         % Wrap up.
1234
1235   TFile closefile TFileName deletefile
1236   end           % temporary dict
1237   end           % IDict
1238 } bind def
1239
1240 end                     % pdfoptdict
1241 .setglobal
1242
1243 % Check for command line arguments.
1244 [ shellarguments {
1245   ] dup length 2 eq {
1246         % Load the pdfwrite utilities if necessary.
1247     /omapinit where { pop } { (pdfwrite.ps) runlibfile } ifelse
1248     save exch
1249     aload pop exch (r) file exch (w) file
1250     3000000 setvmthreshold
1251     0 setobjectformat
1252     pdfoptdict begin pdfOptimize end
1253     restore
1254   } {
1255     (Usage: gs -dNODISPLAY -- pdfopt.ps input.pdf output.pdf) = flush quit
1256   } ifelse
1257 } {
1258   pop
1259 } ifelse