home *** CD-ROM | disk | FTP | other *** search
/ Acorn User 10 / AU_CD10.iso / Updates / GhostScript / !GhostScr / 6_01 / lib / pdf_base.ps < prev    next >
Text File  |  2000-03-29  |  20KB  |  615 lines

  1. %    Copyright (C) 1994, 2000 Aladdin Enterprises.  All rights reserved.
  2. % This file is part of Aladdin Ghostscript.
  3. % Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
  4. % or distributor accepts any responsibility for the consequences of using it,
  5. % or for whether it serves any particular purpose or works at all, unless he
  6. % or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
  7. % License (the "License") for full details.
  8. % Every copy of Aladdin Ghostscript must include a copy of the License,
  9. % normally in a plain ASCII text file named PUBLIC.  The License grants you
  10. % the right to copy, modify and redistribute Aladdin Ghostscript, but only
  11. % under certain conditions described in the License.  Among other things, the
  12. % License requires that the copyright notice and this notice be preserved on
  13. % all copies.
  14.  
  15. % $Id: pdf_base.ps,v 1.2 2000/03/10 03:58:14 lpd Exp $
  16. % pdf_base.ps
  17. % Basic parser for PDF reader.
  18.  
  19. % This handles basic parsing of the file (including the trailer
  20. % and cross-reference table), as well as objects, object references,
  21. % streams, and name/number trees; it doesn't include any facilities for
  22. % making marks on the page.
  23.  
  24. /.setlanguagelevel where { pop 2 .setlanguagelevel } if
  25. .currentglobal true .setglobal
  26. /pdfdict where { pop } { /pdfdict 100 dict def } ifelse
  27. pdfdict begin
  28.  
  29. % Define the name interpretation dictionary for reading values.
  30. /valueopdict mark
  31.   (<<) cvn { mark } bind    % don't push an actual mark!
  32.   (>>) cvn /.dicttomark load
  33.   ([) cvn { mark } bind        % ditto
  34.   (]) cvn dup load
  35.   /true true
  36.   /false false
  37.   /null null
  38.   /F dup cvx        % see Objects section below
  39.   /R dup cvx        % see Objects section below
  40.   /stream dup cvx    % see Streams section below
  41. .dicttomark readonly def
  42.  
  43. % ------ Utilities ------ %
  44.  
  45. % Define a scratch string.  The PDF language definition says that
  46. % no line in a PDF file can exceed 255 characters.
  47. /pdfstring 255 string def
  48.  
  49. % Read the previous line of a file.  If we aren't at a line boundary,
  50. % read the line containing the current position.
  51. % Skip any blank lines.
  52. /prevline        % - prevline <startpos> <substring>
  53.  { PDFfile fileposition dup () pdfstring
  54.    2 index 257 sub 0 .max PDFfile exch setfileposition
  55.     {        % Stack: initpos linepos line string
  56.       PDFfile fileposition
  57.       PDFfile 2 index readline pop
  58.       dup length 0 gt
  59.        { 3 2 roll 5 -2 roll pop pop 2 index }
  60.        { pop }
  61.       ifelse
  62.         % Stack: initpos linepos line string startpos
  63.       PDFfile fileposition 5 index ge { exit } if
  64.       pop
  65.     }
  66.    loop pop pop 3 -1 roll pop
  67.  } bind def
  68.  
  69. % Read a token from a file, recognizing the PDF 1.2 #nn escape convention.
  70. % This should be done in C!
  71. /.pdftoken {            % <file> .pdftoken <obj> -true-
  72.                 % <file> .pdftoken -false-
  73.   token {
  74.     dup type /nametype eq {
  75.       dup xcheck {
  76.     true
  77.       } {
  78.     PDFversion 1.2 ge {
  79.       dup .namestring (#) search {
  80.         name#escape cvn exch pop
  81.       } {
  82.         pop
  83.       } ifelse
  84.     } if true
  85.       } ifelse
  86.     } {
  87.       true
  88.     } ifelse
  89.   } {
  90.     false
  91.   } ifelse
  92. } bind def
  93. /name#escape            % <post> <(#)> <pre> name#escape <string>
  94. { exch pop
  95.   1 index 2 () /SubFileDecode filter dup (x) readhexstring
  96.         % Stack: post pre stream char t/f
  97.   not { /.pdftoken cvx /syntaxerror signalerror } if
  98.   exch closefile concatstrings
  99.   exch 2 1 index length 2 sub getinterval
  100.   (#) search { name#escape } if concatstrings
  101. } bind def
  102.  
  103. % Execute a file, interpreting its executable names in a given
  104. % dictionary.  The name procedures may do whatever they want
  105. % to the operand stack.
  106. /.pdfrun {            % <file> <opdict> .pdfrun -
  107.     % Construct a procedure with the stack depth, file and opdict
  108.     % bound into it.
  109.   1 index cvlit count 2 sub 3 1 roll mark mark 5 2 roll {
  110.     % Stack: ..operands.. count opdict file
  111.     .pdftoken not { (%%EOF) cvn cvx } if
  112.     dup xcheck {
  113.       DEBUG { dup == flush } if
  114.       2 copy .knownget {
  115.     exch pop exch pop exch pop exec
  116.       } {
  117.     BXlevel 0 le {
  118.       (%stderr) (w) file
  119.       dup (****************Unknown operator: ) writestring
  120.       dup 2 index .writecvs dup (\n) writestring flushfile
  121.     } if pop pop
  122.     count exch sub { pop } repeat    % pop all the operands
  123.       } ifelse
  124.     } {
  125.       exch pop exch pop DEBUG { dup ==only ( ) print flush } if
  126.     } ifelse
  127.   }
  128.   aload pop .packtomark cvx
  129.   /loop cvx 2 packedarray cvx
  130.   { stopped /PDFsource } aload pop
  131.   PDFsource
  132.   { store { stop } if } aload pop .packtomark cvx
  133.   /PDFsource 3 -1 roll store exec
  134. } bind def
  135.  
  136. % Execute a file, like .pdfrun, for a marking context.
  137. % This temporarily rebinds LocalResources.
  138. /.pdfruncontext {        % <resdict> <file> <opdict> .pdfruncontext -
  139.   /.pdfrun load /LocalResources LocalResources /store load
  140.   /LocalResources 8 -1 roll store 4 .execn
  141. } bind def
  142.  
  143. % Get the depth of the PDF operand stack.  The main program (pdf_main.ps)
  144. % sets pdfemptycount before calling .pdfrun.
  145. /.pdfcount {        % - .pdfcount <count>
  146.   count pdfemptycount sub
  147. } bind def
  148.  
  149. % ------ File reading ------ %
  150.  
  151. % Read the cross-reference entry for an (unresolved) object.
  152. % The caller must save and restore the PDFfile position if desired.
  153. % For invalid (free) objects, we return 0.
  154. /readxrefentry        % <object#> readxrefentry <objpos>
  155.  { dup Objects exch lget
  156.    PDFfile exch setfileposition
  157.    PDFfile token pop        % object position
  158.    PDFfile token pop        % generation #
  159.    PDFfile token pop        % n or f
  160.    dup /n eq
  161.     { pop 1 add dup 255 gt
  162.        { Generations ltype /stringtype eq
  163.       {        % Convert Generations from a string to an array.
  164.         larray Generations llength lgrowto dup
  165.         0 1 2 index llength 1 sub
  166.          { Generations 1 index lget lput dup
  167.          }
  168.         for pop /Generations exch store
  169.       }
  170.      if
  171.        }
  172.       if
  173.     }
  174.     { /f eq
  175.        { pop 0 }
  176.        { /readxrefentry cvx /syntaxerror signalerror }
  177.       ifelse
  178.     }
  179.    ifelse
  180.         % Stack: obj# objpos 1+gen#
  181.    Generations 4 -1 roll 3 -1 roll lput
  182.  } bind def
  183.  
  184. % ================================ Objects ================================ %
  185.  
  186. % Since we may have more than 64K objects, we have to use a 2-D array to
  187. % hold them (and the parallel Generations structure).
  188. /lshift 9 def
  189. /lnshift lshift neg def
  190. /lsubmask 1 lshift bitshift 1 sub def
  191. /lsublen lsubmask 1 add def
  192. /larray {    % - larray <larray>
  193.   [ [] ]
  194. } bind def
  195. /lstring {    % - lstring <lstring>
  196.   [ () ]
  197. } bind def
  198. /ltype {    % <lseq> type <type
  199.   0 get type
  200. } bind def
  201. /lget {        % <lseq> <index> lget <value>
  202.   dup //lsubmask and 3 1 roll //lnshift bitshift get exch get
  203. } bind def
  204. /lput {        % <lseq> <index> <value> lput -
  205.   3 1 roll
  206.   dup //lsubmask and 4 1 roll //lnshift bitshift get
  207.   3 1 roll put
  208. } bind def
  209. /llength {    % <lseq> llength <length>
  210.   dup length 1 sub dup //lshift bitshift
  211.   3 1 roll get length add
  212. } bind def
  213. % lgrowto assumes newlength > llength(lseq)
  214. /growto {    % <string/array> <length> growto <string'/array'>
  215.   1 index type /stringtype eq { string } { array } ifelse
  216.   2 copy copy pop exch pop
  217. } bind def
  218. /lgrowto {    % <lseq> <newlength> lgrowto <lseq'>
  219.     dup //lsubmask add //lnshift bitshift dup 3 index length gt {
  220.     % Add more sub-arrays.  Start by completing the last existing one.
  221.         % Stack: lseq newlen newtoplen
  222.     3 -1 roll dup llength 1 sub //lsubmask or 1 add lgrowto
  223.         % Stack: newlen newtoplen lseq
  224.     [ exch aload pop
  225.     counttomark 2 add -1 roll        % newtoplen
  226.     counttomark sub { dup 0 0 getinterval lsublen growto } repeat
  227.     dup 0 0 getinterval ] exch
  228.   } {
  229.     pop
  230.   } ifelse
  231.     % Expand the last sub-array.
  232.   1 sub //lsubmask and 1 add
  233.   exch dup dup length 1 sub 2 copy
  234.         % Stack: newsublen lseq lseq len-1 lseq len-1
  235.   get 5 -1 roll growto put
  236. } bind def
  237.  
  238. % We keep track of PDF objects using the following PostScript variables:
  239. %
  240. %    Generations (lstring): Generations[N] holds 1+ the current
  241. %        generation number for object number N.  (As far as we can tell,
  242. %        this is needed only for error checking.)  For free objects,
  243. %        Generations[N] is 0.
  244. %
  245. %    Objects (larray): If object N is loaded, Objects[N] is the actual
  246. %        object; otherwise, Objects[N] is an executable integer giving
  247. %        the file offset of the object's entry in the cross-reference
  248. %        table.
  249. %
  250. %    GlobalObjects (dictionary): If object N has been resolved in
  251. %        global VM, GlobalObjects[N] is the same as Objects[N]
  252. %        (except that GlobalObjects itself is stored in global VM,
  253. %        so the entry will not be deleted at the end of the page).
  254. %
  255. %    IsGlobal (lstring): IsGlobal[N] = 1 iff object N was resolved in
  256. %        global VM.  This is an accelerator to avoid having to do a
  257. %        dictionary lookup in GlobalObjects when resolving every object.
  258.  
  259. % Initialize the PDF object tables.
  260. /initPDFobjects {        % - initPDFobjects -
  261.   /Objects larray def
  262.   /Generations lstring def
  263.   .currentglobal true .setglobal
  264.   /GlobalObjects 20 dict def
  265.   .setglobal
  266.   /IsGlobal lstring def
  267. } bind def
  268.  
  269. % Grow the tables to a specified size.
  270. /growPDFobjects {        % <minsize> growPDFobjects -
  271.   dup Objects llength gt {
  272.     dup Objects exch lgrowto /Objects exch def
  273.   } if
  274.   dup Generations llength gt {
  275.     dup Generations exch lgrowto /Generations exch def
  276.   } if
  277.   dup IsGlobal llength gt {
  278.     dup IsGlobal exch lgrowto /IsGlobal exch def
  279.   } if
  280.   pop
  281. } bind def
  282.  
  283. % We represent an unresolved object reference by a procedure of the form
  284. % {obj# gen# resolveR}.  This is not a possible PDF object, because PDF has
  285. % no way to represent procedures.  Since PDF in fact has no way to represent
  286. % any PostScript object that doesn't evaluate to itself, we can 'force'
  287. % a possibly indirect object painlessly with 'exec'.
  288. % Note that since we represent streams by executable dictionaries
  289. % (see below), we need both an xcheck and a type check to determine
  290. % whether an object has been resolved.
  291. /unresolved? {        % <object#> unresolved? <bool>
  292.   Objects 1 index lget dup xcheck exch type /integertype eq and {
  293.     IsGlobal 1 index lget 0 eq {
  294.       pop true
  295.     } {
  296.         % Update Objects from GlobalObjects
  297.       DEBUG { (%Global=>local: ) print dup == } if
  298.       Objects exch GlobalObjects 1 index get lput false
  299.     } ifelse
  300.   } {
  301.     pop false
  302.   } ifelse
  303. } bind def
  304. /oforce /exec load def
  305. /oget {        % <array> <index> oget <object>
  306.         % <dict> <key> oget <object>
  307.   2 copy get dup xcheck {
  308.     exec dup 4 1 roll put
  309.   } {
  310.     exch pop exch pop
  311.   } ifelse
  312. } bind def
  313. % A null value in a dictionary is equivalent to an omitted key;
  314. % we must check for this specially.
  315. /knownoget {
  316.   2 copy .knownget {
  317.     dup xcheck {
  318.       exec dup 4 1 roll put
  319.     } {
  320.       exch pop exch pop
  321.     } ifelse
  322.     dup null eq { pop false } { true } ifelse
  323.   } {
  324.     pop pop false
  325.   } ifelse
  326. } bind def
  327.  
  328. % PDF 1.1 defines a 'foreign file reference', but not its meaning.
  329. % Per the specification, we convert these to nulls.
  330. /F {        % <file#> <object#> <generation#> F <object>
  331.         % Some PDF 1.1 files use F as a synonym for f!
  332.    .pdfcount 3 lt { f } { pop pop pop null } ifelse
  333. } bind def
  334.  
  335. /checkgeneration {  % <object#> <generation#> checkgeneration <object#> <OK>
  336.   Generations 2 index lget 1 sub 1 index eq {
  337.     pop true
  338.   } {
  339.     Generations 2 index lget 0 eq {
  340.       (Warning: reference to free object: )
  341.     } {
  342.       (Warning: wrong generation: )
  343.     } ifelse print 1 index =only ( ) print =only ( R) = false
  344.   } ifelse
  345. } bind def
  346. /R {        % <object#> <generation#> R <object>
  347.   1 index unresolved?
  348.     { /resolveR cvx 3 packedarray cvx }
  349.     { checkgeneration { Objects exch lget } { pop null } ifelse }
  350.    ifelse
  351. } bind def
  352.  
  353. % If we encounter an object definition while reading sequentially,
  354. % we just store it away and keep going.
  355. /objopdict mark
  356.   valueopdict { } forall
  357.   /endobj dup cvx
  358. .dicttomark readonly def
  359. /obj {            % <object#> <generation#> obj <object>
  360.   PDFfile objopdict .pdfrun
  361. } bind def
  362. /endobj {        % <object#> <generation#> <object> endobj <object>
  363.   3 1 roll
  364.         % Read the xref entry if we haven't yet done so.
  365.         % This is only needed for generation # checking.
  366.   1 index unresolved? {
  367.     PDFfile fileposition
  368.     2 index readxrefentry pop
  369.     PDFoffset add PDFfile exch setfileposition
  370.   } if
  371.   checkgeneration {
  372.         % The only global objects we bother to save are
  373.         % (resource) dictionaries.
  374.     1 index dup gcheck exch type /dicttype eq and {
  375.       DEBUG { (%Local=>global: ) print dup == } if
  376.       GlobalObjects 1 index 3 index put
  377.       IsGlobal 1 index 1 put
  378.     } if
  379.     Objects exch 2 index lput
  380.   } {
  381.     pop pop null
  382.   } ifelse
  383. } bind def
  384.  
  385. % When resolving an object reference, we stop at the endobj.
  386. /resolveopdict mark
  387.   valueopdict { } forall
  388.   /endobj { endobj exit } bind
  389. .dicttomark readonly def
  390. /resolveR        % <object#> <generation#> resolveR <object>
  391.  { DEBUG { (%Resolving: ) print 2 copy 2 array astore == } if
  392.    1 index unresolved?
  393.     { PDFfile fileposition 3 1 roll
  394.       1 index readxrefentry
  395.       3 1 roll checkgeneration
  396.        {        % Stack: savepos objpos obj#
  397.      exch PDFoffset add PDFfile exch setfileposition
  398.      PDFfile token pop 2 copy ne
  399.       { (xref error!) = /resolveR cvx /rangecheck signalerror
  400.       }
  401.      if pop PDFfile token pop
  402.      PDFfile token pop /obj ne
  403.       { (xref error!) = /resolveR cvx /rangecheck signalerror
  404.       }
  405.      if
  406.      pdf_run_resolve    % PDFfile resolveopdict .pdfrun
  407.        }
  408.        {        % Don't cache if the generation # is wrong.
  409.      pop pop null
  410.        }
  411.       ifelse exch PDFfile exch setfileposition
  412.     }
  413.     { pop Objects exch lget
  414.     }
  415.    ifelse
  416.  } bind def      
  417.  
  418. % ================================ Streams ================================ %
  419.  
  420. % We represent a stream by an executable dictionary that contains,
  421. % in addition to the contents of the original stream dictionary:
  422. %    /File - the file or string where the stream contents are stored,
  423. %      if the stream is not an external one.
  424. %    /FilePosition - iff File is a file, the position in the file
  425. %      where the contents start.
  426. %    /StreamKey - the key used to decrypt this stream, if any.
  427. % We do the real work of constructing the data stream only when the
  428. % contents are needed.
  429.  
  430. % Construct a stream.  The length is not reliable in the face of
  431. % different end-of-line conventions, but it's all we've got.
  432. %
  433. % PDF files are inconsistent about what may fall between the 'stream' keyword
  434. % and the actual stream data, and it appears that no one algorithm can
  435. % detect this reliably.  We used to try to guess whether the file included
  436. % extraneous \r and/or \n characters, but we no longer attempt to do so,
  437. % especially since the PDF 1.2 specification states flatly that the only
  438. % legal terminators following the 'stream' keyword are \n or \r\n, both of
  439. % which are properly skipped and discarded by the token operator.
  440. /stream {    % <dict> stream <modified_dict>
  441.   dup /F known dup PDFsource PDFfile eq or {
  442.     not {
  443.       dup /File PDFfile put
  444.       dup /FilePosition PDFfile fileposition put
  445.       DEBUG { (%FilePosition: ) print dup /FilePosition get == } if
  446.     } if
  447.     PDFfile fileposition 1 index /Length oget add
  448.       PDFfile exch setfileposition
  449.   } {
  450.     pop
  451.     % We're already reading from a stream, which we can't reposition.
  452.     % Capture the sub-stream contents in a string.
  453.     dup /Length oget string PDFsource exch readstring
  454.     not {
  455.       (Unexpected EOF in stream!) =
  456.       /stream cvx /rangecheck signalerror
  457.     } if
  458.     1 index exch /File exch put
  459.   } ifelse
  460.   PDFsource token pop
  461.     /endstream ne { /stream cvx /syntaxerror signalerror } if
  462.   cvx
  463. } bind def
  464. /endstream {
  465.   exit
  466. } bind def
  467.  
  468. % Contrary to the published PDF (1.3) specification, Acrobat Reader
  469. % accepts abbreviated filter names everywhere, not just for in-line images,
  470. % and some applications (notably htmldoc) rely on this.
  471. /unabbrevfilterdict mark
  472.   /AHx /ASCIIHexDecode  /A85 /ASCII85Decode  /CCF /CCITTFaxDecode
  473.   /DCT /DCTDecode  /Fl /FlateDecode  /LZW /LZWDecode  /RL /RunLengthDecode
  474. .dicttomark readonly def
  475.  
  476. % Extract and apply filters.
  477. /filterparms {        % <dict> <DPkey> <Fkey> filterparms
  478.             %   <dict> <parms> <filternames>
  479.   2 index exch .knownget {
  480.     exch 2 index exch .knownget {
  481.         % Both filters and parameters.
  482.       exch dup type /nametype eq {
  483.     1 array astore exch 1 array astore exch
  484.       } if
  485.     } {
  486.         % Filters, but no parameters.
  487.       null exch
  488.       dup type /nametype eq { 1 array astore } if
  489.     } ifelse
  490.   } {
  491.         % No filters: ignore parameters, if any.
  492.     pop null { }
  493.   } ifelse
  494. } bind def
  495. /filtername {        % <filtername> filtername <filtername'>
  496.   //unabbrevfilterdict 1 index .knownget { exch pop } if
  497. } bind def
  498. /applyfilters {        % <parms> <source> <filternames> applyfilters <stream>
  499.   2 index null eq {
  500.     { filtername filter }
  501.   } {
  502.     {        % Stack: parms stream filtername
  503.       2 index 0 get dup null eq { pop } { exch } ifelse filtername filter
  504.       exch dup length 1 sub 1 exch getinterval exch
  505.     }
  506.   } ifelse forall exch pop
  507. } bind def
  508.  
  509. % Resolve a stream dictionary to a PostScript stream.
  510. % Streams with no filters require special handling:
  511. %    - If we are going to interpret their contents, we let endstream
  512. %      terminate the interpretation loop;
  513. %    - If we are just going to read data from them, we impose
  514. %      a SubFileDecode filter that reads just the requisite amount of data.
  515. % Note that, in general, resolving a stream repositions PDFfile.
  516. % Clients must save and restore the position of PDFfile themselves.
  517. /resolvestream {    % <streamdict> <readdata?> resolvestream <stream>
  518.   1 index /F .knownget {
  519.         % This stream is stored on an external file.
  520.     (r) file exch
  521.     /FDecodeParms /FFilter filterparms
  522.         % Stack: readdata? dict parms filternames
  523.     4 -1 roll exch
  524.     pdf_decrypt_stream
  525.     applyfilters
  526.   } {
  527.     exch dup /FilePosition .knownget {
  528.       1 index /File get exch setfileposition
  529.     } if
  530.         % Stack: readdata? dict
  531.     /DecodeParms /Filter filterparms
  532.         % Stack: readdata? dict parms filternames
  533.     2 index /File get exch
  534.         % Stack: readdata? dict parms file/string filternames
  535.     pdf_decrypt_stream        % add decryption if needed
  536.     dup length 0 eq {
  537.         % All the PDF filters have EOD markers, but in this case
  538.         % there is no specified filter.
  539.       pop exch pop
  540.         % Stack: readdata? dict file/string
  541.       2 index {
  542.         % We're going to read data; use a SubFileDecode filter.
  543.     1 index /Length oget () /SubFileDecode filter
  544.       } {
  545.     dup type /filetype ne {
  546.         % Use a SubFileDecode filter to read from a string.
  547.       0 () /SubFileDecode filter
  548.     } if
  549.       } ifelse
  550.     } {
  551.       applyfilters
  552.     } ifelse
  553.   } ifelse
  554.         % Stack: readdata? dict file
  555.   exch pop exch pop
  556. } bind def
  557.  
  558. % ============================ Name/number trees ============================ %
  559.  
  560. /nameoget {        % <nametree> <key> nameoget <obj|null>
  561.   exch /Names exch .treeget
  562. } bind def
  563.  
  564. /numoget {        % <numtree> <key> numoget <obj|null>
  565.   exch /Nums exch .treeget
  566. } bind def
  567.  
  568. /.treeget {        % <key> <leafkey> <tree> .treeget <obj|null>
  569.   dup /Kids knownoget {
  570.     exch pop .branchget
  571.   } {
  572.     exch get .leafget
  573.   } ifelse
  574. } bind def
  575.  
  576. /.branchget {        %  <key> <leafkey> <kids> .branchget <obj|null>
  577.   dup length 0 eq {
  578.     pop pop pop null
  579.   } {
  580.     dup length -1 bitshift 2 copy oget
  581.             % Stack: key leafkey kids mid kids[mid]
  582.     dup /Limits oget aload pop
  583.             % Stack: key leafkey kids mid kids[mid] min max
  584.     6 index lt {
  585.       pop pop
  586.       1 add 1 index length 1 index sub getinterval .branchget
  587.     } {
  588.       5 index gt {
  589.     pop
  590.     0 exch getinterval .branchget
  591.       } {
  592.     exch pop exch pop .treeget
  593.       } ifelse
  594.     } ifelse
  595.   } ifelse
  596. } bind def
  597.  
  598. /.leafget {        % <key> <pairs> .leafget <obj|null>
  599.   dup length 2 eq {
  600.     dup 0 get 2 index eq { 1 oget } { pop null } ifelse
  601.     exch pop
  602.   } {
  603.     dup length -1 bitshift -2 and 2 copy oget
  604.             % Stack: key pairs mid pairs[mid]
  605.     3 index gt { 0 exch } { 1 index length 1 index sub } ifelse
  606.     getinterval .leafget
  607.   } ifelse
  608. } bind def
  609.  
  610. end            % pdfdict
  611. .setglobal
  612.