% Copyright (C) 2001-2024 Artifex Software, Inc.
% All Rights Reserved.
%
% This software is provided AS-IS with no warranty, either express or
% implied.
%
% This software is distributed under license and may not be copied,
% modified or distributed except as expressly authorized under the terms
% of the license contained in the file LICENSE in this distribution.
%
% Refer to licensing information at http://www.artifex.com or contact
% Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
% CA 94129, USA, for further information.
%

% pdf_main.ps
% PDF file- and page-level operations.

/.setlanguagelevel where { pop 2 .setlanguagelevel } if
.currentglobal //true .setglobal

% ======================== Main program ======================== %

userdict begin

% Redefine 'run' so it recognizes PDF files.
systemdict begin

systemdict /OLDPDF known {
  systemdict /OLDPDF get {
    (   **** WARNING ****\n) print
    (The old, written in PostScript, PDF interpreter has been removed entirely.\n) print
    (You should cease using -dOLDPF as it has no effect now.\n) print
    (Continuing to process PDF file using the new, written in C, PDF interpreter.\n\n) print
  } if
} if

systemdict /NEWPDF known {
  systemdict /NEWPDF get not {
    (The old, written in PostScript, PDF interpreter has been removed entirely.\n) print
    (You should cease using -dNEWDPF as it has no effect now.\n) print
    (Continuing to process PDF file using the new, written in C, PDF interpreter.\n) print
  } if
} if


% PostScript interface to the ghostpdf C-based interpreter
%
% Promised or documented as used by customers
% ===========================================
% runpdf            <file> runpdf -
%     Called from the modified run operator (which copies stdin to a temp
%     file if required). Calls runpdfbegin, check for PDF collections. Then
%     calls process_trailer_attrs, runpdfpagerange, dopdfpages and runpdfend
%     to process all the required pages.
%
% runpdfbegin        <file> runpdfbegin -
%     creates /pdfdict in userdict set userparmas (ProcessDSCComment
%     setobjectformat to 0 /Page# /Page to null DSCPageCount to 0
%     /PDFSave /null.
%     calls pdfopen and then 'begin's the returned dictionary.
%     Also disables page handler device.
%
% pdfgetpage        <int> pdfgetpage <pagedict> | <null>
%     int is a number from 1 to N indicating the desired page number from
%     the PDF file. Returns a dictionary, apparently the /Page dictionary. Probably
%     we only need the Boxes and Rotate key ? If this fails, returns a null object.
%
% pdfshowpage_init    <int> pdfshowpage_init <dict>
%     This simply adds 1 to the /DSCPageCount value in the supplied dictioanry
%
% pdfshowpage_setpage    <pagedict> pdfshowpage_setpage <pagedict>
%     Takes a dictionary as returned from pdfgetpage, extracts various
%     parameters from it, and sets the media size for the page, taking into
%     account the boxes, and requested Box, Rotate value and PDFFitPage.
%
% pdfshowpage_finish    <pagedict> pdfshowpage_finish -
%     Takes a dictionary as returned from pdfgetpage, renders the page content
%     executes showpage to transfer the rendered content to the device.
%
% runpdfend        -
%     Uses currentdict as an argument to pdfclose, which closes /PDFfile. Also
%     executes restore and various cleanup activities.
%
% pdfavailable     - pdfavailable <bool>
%     Determines if there is a PDF interpreter available
%
% Also Used by gsview 5
% =====================
% pdfopen            <file> pdfopen <dict>
%     According to the comments; open a PDF file and read the header, trailer
%     and cross-reference. Calls pdfopenfile and 'begin's the returned dictionary
%     calls pdfopencache and then closes the current dictionary.
%     pdfopenfile appears to create a dictionary containing a load of stuff but
%     most importantly, sets /PDFfile to the passed-in file. Probably also contains
%     the Trailer dictionary.
%
% pdfclose        <dict> pdfclose -
%     The supplied dictionary contains /PDFfile and it executes closefile on that
%     supplied file.
%
% Supplied by Ray to customer
% ============================
% pdfshowpage        <pagedict> pdfshowpage
%     Takes a dictionary returned from pdfgetpage and calls the pdfshowpage_init
%     pdfshowpage_setpage, pdfshowpage_finish trio to start the page, set up the
%     media and render the page.
%
% dopdfpages        <int> <int> dopdfpages -
%     The integers are the first and last pages to be run from the file. Runs a loop from
%     the fist integer to the last. NOTE! If the current dictionary contains a PDFPageList
%     array then we 'get' the entry from the array corresponding to the current loop
%     index, and use that to determine whether we should draw the page. Otherwise we
%     simply draw the page. Uses pdfshowpage to actually render the page.
%
% Additionallly useful ?
% =======================
% runpdfpagerange        - runpdfpagerange <int> <int>
%     Processes the PostScript /FirstPage, /LastPage and /PageList parameters to build an array
%     of page numbers to run (if PageList is present) and a FirstPage and LastPage value.
%     There seems no point in rewriting this, may as well use it to control page processing.
%
% Normal operation
% =================
% runpdf    -    runpdfbegin     -    pdfopen
%     -    process_trailer_attrs
%     -    runpdfpagerange
%     -    dopdfpages     -    pdfgetpage
%                 -    pdfshowpage    -    pdfshowpage_init
%                             -    pdfshowpage_setpage
%                             -    pdfshowpage_finish
%     -    runpdfend    -    pdfclose
%

/DisablePageHandlerDevice
{
  systemdict /FirstPage known
  systemdict /LastPage known or
  systemdict /PageList known or
  {
      <</DisablePageHandler //true>> setpagedevice
  } if
} .internalbind def

/EnablePageHandlerDevice
{
  systemdict /FirstPage known
  systemdict /LastPage known or
  systemdict /PageList known or
  {
      <</DisablePageHandler //false>> setpagedevice
  } if
} .internalbind def

/newpdf_pdfformaterror {    % <string> pdfformaterror -
  (%stdout) (w) file
  dup 3 -1 roll writestring flushfile
} .internalbind def

% This routine sets up the transparency compositor requirement, and
% number of spot channels required (if any) in the device. This needs
% to be done before we set the media size, so that the erasepage is
% correct. Called with the dictionary returned from .PDFPageInfo
/newpdf_device_setup
{
  2 dict exch                           % <<page dict>> << >>
  dup /UsesTransparency get             % << >> <<page dict>> bool
  /PageUsesTransparency exch            % << >> <<page dict>> /PageUsesTransparency bool
  3 index 3 1 roll                      % << >> <<page dict>> <<info dict>> << >> /PageUsesTransparency bool
  put                                   % <</PageUsesTransparency bool>> <<page dict>>
  currentpagedevice /PageSpotColors known {
    /NumSpots get                       % <</PageUsesTransparency bool>> int
    /PageSpotColors exch                % <</PageUsesTransparency bool>> /PageSpotColors int
    2 index 3 1 roll                    % <</PageUsesTransparency bool>> <<page dict>> /PageSpotColors int
    put                                 % <</PageUsesTransparency bool /PageSpotColors int >>
  }{
    pop
  } ifelse
  setpagedevice
}.internalbind def

% Takes the selected Box as a parameter and scales it to fit the current media.
% If the amount of scaling would be reduced by rotating the content, then
% rotate the content.
% Caters for the box non-zero lower left x and y (ie cropping)
/NewPDF_FitPage
{
    dup 2 get 1 index 0 get sub                 %urx -llx = width
    1 index 3 get 2 index 1 get sub             %ury - lly = height

    currentpagedevice /PageSize get aload pop   % Stack - box boxwidth boxheight mediawidth mediaheight

    % Start by deciding if we should rotate the page to better fit the media.
    4 copy eq
    3 1 roll eq
    or                   % square media or square page, no point in rotating
    {
      pop pop pop pop
      //false
    }
    {
      gt
      {                                           % landscape media
        ge
        {                                         % landscape media, landscape box, no rotation
          //false
        }
        {                                         % landscape media, portrait box, rotate it to landscape
          //true
        } ifelse
      }
      {                                           % portrait media
        ge
        {                                         % portrait media, landscape box, rotate it to portrait
          //true
        }
        {                                         % portrait media, portrait box, no rotation
          //false
        } ifelse
      }ifelse
    } ifelse

    % Store values for calculating scale, we do this here because we want to
    % swap the media width and height (for the scale calculation) if we are
    % rotating the page
    1 index 2 get 2 index 0 get sub             % urx -llx = width
    2 index 3 get 3 index 1 get sub             % ury - lly = height
    currentpagedevice /PageSize get aload pop   % Stack - box rotate boxwidth boxheight mediawidth mediaheight
    5 -1 roll                                   % Stack - box boxwidth boxheight mediawidth mediaheight rotate

    % If we need to rotate, recalculate the box
    {
      % We take any existing /Rotate key in the dictionary into account as well, because
      % that way we will preserve the orientation which would be used when displaying
      % the original, un-modified page. Note that PostScript's rotate operator works
      % counter-clockwise, while PDF works clockwise....
      %
      7 index dup /Rotate known {/Rotate get}{pop 90}ifelse
      270 eq {
        90 rotate
        0 2 index neg translate                 % move 0,0 to bottom right of media to account for rotation
        exch                                    % swap media width/height for scaling calculation
      }
      {
        270 rotate
        dup neg 0 translate                     % move 0,0 to top right of media to account for rotation
        exch                                    % swap media width/height for scaling calculation
      } ifelse
    }{
      % If we are not rotating the content to better fit the media, then the only existing Rotate
      % value we need to care about is a 180 rotation, because anything else will cause the content
      % not to fit the media.
      7 index dup /Rotate known {/Rotate get}{pop 0}ifelse
      180 eq {
        180 rotate
        2 copy neg exch neg exch translate      % move 0,0 to top right of media to account for rotation
      } if
    }ifelse

    % Now use the box and media values to calculate the required x/y scale factors
    4 copy                              % Stack - box boxwidth boxheight mediawidth mediaheight boxwidth boxheight mediawidth mediaheight
    3 -1 roll div                       % box boxwidth boxheight mediawidth mediaheight boxwidth mediawidth (mediaheight / boxheight)
    3 1 roll                            % box boxwidth boxheight mediawidth mediaheight (mediaheight / boxheight) boxwidth mediawidth
    exch                                % box boxwidth boxheight mediawidth mediaheight (mediaheight / boxheight) mediawidth boxwidth
    div                                 % box boxwidth boxheight mediawidth mediaheight (mediaheight / boxheight) (mediawidth / boxwidth)

    % Stack - box boxwidth boxheight mediawidth mediaheight Yscale Xscale

    % Centre the output on the media
    2 copy                              % box boxwidth boxheight mediawidth mediaheight Yscale Xscale Yscale Xscale
    gt {
      exch pop                          % box boxwidth boxheight mediawidth mediaheight Xscale
      dup 4 index mul                   % box boxwidth boxheight mediawidth mediaheight Xscale (boxheight * Xscale)
      3 -1 roll sub dup
      0 lt {-1 mul} if                  % box boxwidth boxheight mediawidth Xscale ((boxheight * Xscale) - mediaheight)
      dup 0 ne {2 div} if               % box boxwidth boxheight mediawidth Xscale (heightdiff / 2)
      0 exch translate                  % box boxwidth boxheight mediawidth Xscale
    } {
      pop                               % box boxwidth boxheight mediawidth mediaheight Yscale
      dup 5 index mul                   % box boxwidth boxheight mediawidth mediaheight Yscale (boxwidth * Yscale)
      4 -1 roll sub dup
      0 lt {-1 mul} if                  % box boxwidth boxheight mediaheight Yscale ((boxwidth * Yscale) - mediawidth)
      dup 0 ne {2 div} if               % box boxwidth boxheight mediawidth Yscale (widthdiff / 2)
      0 translate                       % box boxwidth boxheight mediawidth Yscale
    } ifelse

    % Apply any 'offset' in the Box. Tht is, if the llx and lly are not 0, then we need to
    % shift the origin of the content so that they become 0,0. We need to take into
    % account the scaling from above.
    4 index aload pop pop pop
    neg 2 index mul exch neg 2 index mul exch
    translate

    dup scale                           % scale both axes the same
    pop pop pop                         % remove the leftover boxwidth, boxheight and mediawidth/height
} .internalbind def

/newpdf_get_media_box { % <pagedict> get_media_box <box> <bool>
  dup /MediaBox known {
    /MediaBox get
    dup length 4 eq {
      //true
    }
    {
      pop
      (   **** Error: Page has an invalid /MediaBox attribute. Using the current page size.\n)
      newpdf_pdfformaterror
      (               Output may be incorrect.\n) pdfformaterror
      [ 0 0 currentpagedevice /PageSize get aload pop ]
      //false
    }ifelse
  }
  {
    pop
    (   **** Error: Page has no /MediaBox attribute. Using the current page size.\n)
    newpdf_pdfformaterror
    (               Output may be incorrect.\n) newpdf_pdfformaterror
    [ 0 0 currentpagedevice /PageSize get aload pop ] //false
  } ifelse
} .internalbind def

% [llx lly urx ury] newpdf_check_empty_box [llx lly urx ury] true | false
% rturns true and the original array if its valid, otherwise returns false
/newpdf_check_empty_box
{
  dup type /arraytype eq
  {
    dup aload pop
    dup 3 index eq
    {
      //true
    }
    {
      1 index 4 index eq
    } ifelse
    {
      pop pop pop pop
     (   **** Warning:  File has an empty Box parameter.\n) print
     (                  Using the MediaBox instead.\n) print
     (                  Output may be incorrect.\n) print flush
     pop //false
    }
    {
      pop pop pop pop
      //true
    } ifelse
  }
  {
     (   **** Warning:  File has an invalid Box parameter.\n) print
     (                  Using the MediaBox instead.\n) print
     (                  Output may be incorrect.\n) print flush
    pop //false
  } ifelse
} .internalbind def

/newpdf_get_any_box { % <pagedict> get_any_box <box name> <box>
  //systemdict /UseBleedBox .knownget dup { and } if {
    dup /BleedBox .knownget {
        newpdf_check_empty_box
        {
          /BleedBox exch
        } if
    } if
  } if
  dup type /arraytype ne {
    //systemdict /UseTrimBox .knownget dup { and } if {
      dup /TrimBox .knownget {
        newpdf_check_empty_box
        {
            /TrimBox exch
        } if
      } if
    } if
  } if
  dup type /arraytype ne {
    //systemdict /UseArtBox .knownget dup { and } if {
      dup /ArtBox .knownget {
        newpdf_check_empty_box
        {
            /ArtBox exch
        } if
      } if
    } if
  } if
  dup type /arraytype ne {
    //systemdict /UseCropBox .knownget dup { and } if {
      dup /CropBox .knownget {
        newpdf_check_empty_box
        {
          /CropBox exch
        } if
      } if
    } if
  } if
  dup type /arraytype ne {
    /MediaBox exch newpdf_get_media_box pop
  } {
    %% Complicated stuff. We need to use the 'Box' we identified (if any), but we
    %% need to clamp the boundaries of the 'Box' to the MediaBox. This appears to
    %% be what Acrobat does. The complication arises because the Box values don't
    %% have to be sensibly positive, its permissible to have the MediaBox set up
    %% so that it extends down and left instead of up and right. We take care of the
    %% content when we st up the CTM, but we do need to make sure that we clamp
    %% the BoundingBox, and that means we need to take direcitonality into account...

    aload pop
    6 -1 roll newpdf_get_media_box { % /SomeBox x0 y0 x1 y1 [MediaBox]
      aload pop               % /SomeBox x0 y0 x1 y1 X0 Y0 X1 Y1

      %% Start with the width, get the X0 and X1 values of the MediaBox
      3 index                 % /SomeBox x0 y0 x1 y1 X0 Y0 X1 Y1 X0
      2 index                 % /SomeBox x0 y0 x1 y1 X0 Y0 X1 Y1 X0 X1
      gt {
        %% Media extends to left
        4 -1 roll             % /SomeBox x0 y0 x1 y1 Y0 X1 Y1 X0
        8 -1 roll             % /SomeBox y0 x1 y1 Y0 X1 Y1 X0 x0
        .min                  % /SomeBox y0 x1 y1 Y0 X1 Y1 mX0
        7 1 roll              % /SomeBox mX0 y0 x1 y1 Y0 X1 Y1
        exch                  % /SomeBox mX0 y0 x1 y1 Y0 Y1 X1
        5 -1 roll             % /SomeBox mX0 y0 y1 Y0 Y1 X1 x1
        .max                  % /SomeBox mX0 y0 y1 Y0 Y1 mX1
        5 1 roll              % /SomeBox mX0 mX1 y0 y1 Y0 Y1
      }{
        %% Media extends to right
        4 -1 roll             % /SomeBox x0 y0 x1 y1 Y0 X1 Y1 X0
        8 -1 roll             % /SomeBox y0 x1 y1 Y0 X1 Y1 X0 x0
        .max                  % /SomeBox y0 x1 y1 Y0 X1 Y1 mX0
        7 1 roll              % /SomeBox mX0 y0 x1 y1 Y0 X1 Y1
        exch                  % /SomeBox mX0 y0 x1 y1 Y0 Y1 X1
        5 -1 roll             % /SomeBox mX0 y0 y1 Y0 Y1 X1 x1
        .min                  % /SomeBox mX0 y0 y1 Y0 Y1 mX1
        5 1 roll              % /SomeBox mX0 mX1 y0 y1 Y0 Y1
      } ifelse

      %% Now deal with the height
      2 copy                  % /SomeBox mX0 mX1 y0 y1 Y0 Y1 Y0 Y1
      gt {
        %% Media extends down
        exch                  % /SomeBox mX0 mX1 y0 y1 Y1 Y0
        4 -1 roll             % /SomeBox mX0 mX1 y1 Y1 Y0 y0
        .min                  % /SomeBox mX0 mX1 y1 Y1 mY0
        3 1 roll              % /SomeBox mX0 mX1 mY0 y1 Y1
        .max                  % /SomeBox mX0 mX1 mY0 mY1
      }{
        %% Media extends up
        exch                  % /SomeBox mX0 mX1 y0 y1 Y1 Y0
        4 -1 roll             % /SomeBox mX0 mX1 y1 Y1 Y0 y0
        .max                  % /SomeBox mX0 mX1 y1 Y1 mY0
        3 1 roll              % /SomeBox mX0 mX1 mY0 y1 Y1
        .min                  % /SomeBox mX0 mX1 mY0 mY1
      } ifelse
      exch                    % /SomeBox mX0 mX1 mY1 mY0
      3 1 roll                % /SomeBox mX0 mY0 mX1 mY1
    } {
      pop
    } ifelse
    4 array astore            % /SomeBox [mX0 mY0 mX1 mY1]
  } ifelse
} .internalbind def

% This routine is used to set the PostScript /PageSize from the requested Box of the
% PDF file. Much of this is copied from the old pdfshowpage_setpage routine which
% is used for the PDF interpreter written in PostScript.
%
% Called with a dictionary containing the PDF page information.
%
/newpdf_set_pagesize
{
  % Stack: pdfpagedict

  % Don't understand this at all, removed for now replace if we ever figure out
  % what it's for.
  % Only lock in Orientation if we need to for pdfmarks
%  .writepdfmarks { /Orientation 0 def } if

  dup
  dup newpdf_get_any_box       % Stack: pdfpagedict pdfpagedict /BoxName [box]

  % Check that the box is 'normal' and make it so if not
  % Also check the array is 4 elements and if it isn't then use the
  % current PageSize. The normalise arithmetic assumes the array is a 4
  % element array.
  dup length 4 ne {
    % Invalid size of mediabox
    pop currentpagedevice /PageSize get
  } {
    aload 5 1 roll  % array x1 y1 x2 y2
    exch            % array x1 y1 y2 x2
    4 -1 roll       % array y1 y2 x2 x1
    2 copy gt {
      exch          % array y1 y2 xmin xmax
    } if
    4 -2 roll       % array xmin xmax y1 y2
    2 copy gt {
      exch          % array xmin xmax ymin ymax
    } if
    4 1 roll
    exch 4 -1 roll
    5 -1 roll
    astore
  } ifelse

  //systemdict /PDFFitPage known {
    NewPDF_FitPage
  } {
    6 dict begin        % for setpagedevice

    % Set the page size.
    2 index /UserUnit known
    {
      systemdict /NoUserUnit .knownget not {//false} if
      {
        2 index /UserUnit undef
        % No scaling required, calculate PageSize as the width and height of the box
        aload 5 1 roll      % box llx lly urx ury
        2 index sub         % box llx lly urx (ury - lly)
        exch 3 index sub    % box llx lly (ury - lly) (urx - llx)
        exch
      }
      {
        2 index /UserUnit get
        /PassUserUnit /GetDeviceParam .special_op {exch pop} {//false} ifelse {
          [ /UserUnit 3 -1 roll .pdfputparams pop pop
          % No media scaling required, calculate PageSize as the width and height of the box
          aload 5 1 roll      % box llx lly urx ury
          2 index sub         % box llx lly urx (ury - lly)
          exch 3 index sub    % box llx lly (ury - lly) (urx - llx)
          exch
        }{
          % The PageSize needs to be multiplied too
          exch aload 5 1 roll % UserUnit box llx lly urx ury
          2 index sub exch    % UserUnit box llx lly (ury - lly) urx
          3 index sub         % UserUnit box llx lly boxheight boxwidth
          5 index mul         % UserUnit box llx lly boxheight (Boxwidth*UserUnit)
          exch
          5 index mul         % UserUnit box llx lly (Boxwidth*UserUnit) (boxheight*UserUnit)
          4 -2 roll
          5 index mul exch    % UserUnit box (Boxwidth*UserUnit) (boxheight*UserUnit) (lly*UserUnit) llx
          5 index mul exch    % UserUnit box (Boxwidth*UserUnit) (boxheight*UserUnit) (llx*UserUnit) (lly*UserUnit)
          4 2 roll            % UserUnit box (llx*UserUnit) (lly*UserUnit) (Boxwidth*UserUnit) (boxheight*UserUnit)
          6 -1 roll pop       % box (llx*UserUnit) (lly*UserUnit) (Boxwidth*UserUnit) (boxheight*UserUnit)
        } ifelse
      } ifelse
    }
    {
      /PassUserUnit /GetDeviceParam .special_op {
        exch pop {
          [ /UserUnit 1 .pdfputparams pop pop
        } if
      }if
      % No scaling required, calculate PageSize as the width and height of the box
      aload 5 1 roll      % box llx lly urx ury
      2 index sub         % box llx lly urx (ury - lly)
      exch 3 index sub    % box llx lly (ury - lly) (urx - llx)
      exch
    } ifelse

    % handle page rotation here
    6 index /Rotate known {
      % PDF page rotation runs clockwise and must be a multiple of 90, unlike PostScript.....
      % convert Rotate into a quadrant number
      6 index /Rotate get 90 div cvi
      % Make sure quadrant is 0-3
      dup 0 lt {
        % Negative rotation... Turn it into a positive rotation
        % Again, limit the quadrant to -0 to -3
        dup -4 lt {4 mod} if
        % Adding 360 degrees results in the same positive rotation
        4 add
      } if
      dup 3 gt {4 mod} if
      dup 0 eq
      {
        pop
        2 array astore /PageSize exch def
        currentdict end setpagedevice
        neg exch neg exch
        translate
      }
      {
        dup 1 eq
        {
          pop
          exch 4 2 roll exch 4 2 roll
          2 array astore dup /PageSize exch def
          currentdict end setpagedevice
          270 rotate
          1 get add neg exch neg
          translate
        }
        {
          2 eq
          {
            2 array astore dup /PageSize exch def
            currentdict end setpagedevice
            180 rotate
            aload pop 3 -1 roll add neg 3 1 roll add neg exch
            translate
          }
          {
            exch 4 2 roll exch 4 2 roll
            2 array astore dup /PageSize exch def
            currentdict end setpagedevice
            90 rotate
            0 get 3 -1 roll add
            neg exch neg exch
            translate
          } ifelse
        } ifelse
      } ifelse
    }
    {
      2 array astore /PageSize exch def
      currentdict end setpagedevice
      neg exch neg exch
      translate
    }ifelse

    % scale the co-ordinate system by the UserUnit
    % We have to do this after setting the PageSize, because
    % setpagedevice does an implicit initgraphics, resetting the CTM.
    %
    //systemdict /NoUserUnit .knownget not { //false } if not
    {
      2 index /UserUnit known {
        /PassUserUnit /GetDeviceParam .special_op {
          exch pop not
        } {
          true
        }ifelse

        % If PassUserUnit isn't handled by the device, or it returns 'false'
        % then scale the content by the UserUnit. Otherwise do not scale.
        {
          2 index /UserUnit get
          dup scale
        } if
      } if
    } if
  } ifelse

  3 1 roll          % <page dict> /BoxName [box dimensions] -> [box dimensions] <page dict> /BoxName

  % If we are using the MediaBox (and only the MediaBox) then we
  % want to clip to the CropBox, if there is one present. For every
  % other case, clip to the given Box.

  /MediaBox eq {
    dup /CropBox known {
      /CropBox get
      aload pop 2 index sub exch 3 index sub exch rectclip
      pop           % [box dimensions] array
    } {
      pop           % <page dict>

      aload         % Load the co-ordinates of the box onto the stack
      pop           % The array with the box co-ordinates
                    % llx lly urx ury
      2 index sub   % ll lly urx (ury - lly)
      exch          % llx lly height urx
      3 index sub   % llx lly height (urx - llx)
      exch          % llx lly width height
      rectclip
    } ifelse
  } {
    pop             % <page dict>

    aload           % Load the co-ordinates of the box onto the stack
    pop             % The array with the box co-ordinates
                    % llx lly urx ury
    2 index sub     % ll lly urx (ury - lly)
    exch            % llx lly height urx
    3 index sub     % llx lly height (urx - llx)
    exch            % llx lly width height
    rectclip
  }ifelse

  pop
} .internalbind def

% This routine checks a list of known/implemented command line switches to see if they
% have been defined in the PostScript environment. If they have we construct a dictionary
% containing the names and their values, and return it. That dictionary can then be
% passed to a custom PostScript operator and used to configure the PDF interpreter.
%
% NB device parameters will already have been sent to the device and used to configure it
% so here we should only handle parameters which control the behaviour of the interpreter.
%
/PDFSwitches [ /QUIET /PDFPassword /PDFDEBUG /PDFSTOPONERROR /PDFSTOPONWARNING /NOTRANSPARENCY /FirstPage /LastPage
               /PDFA /PDFACompatibilityPolicy /PDFNOCIDFALLBACK /NO_PDFMARK_OUTLINES /NO_PDFMARK_DESTS /PDFFitPage /Printed /UsePDFX3Profile
               /UseBleedBox /UseCropBox /UseArtBox /UseTrimBox /ShowAcroForm /ShowAnnots /PreserveAnnots
               /NoUserUnit /RENDERTTNOTDEF /DOPDFMARKS /PDFINFO /ShowAnnotTypes /PreserveAnnotTypes
               /CIDFSubstPath /CIDFSubstFont /SUBSTFONT /IgnoreToUnicode /NONATIVEFONTMAP /PreserveMarkedContent /OutputFile
               /PreserveDocView /PreserveEmbeddedFiles ] def

/newpdf_gather_parameters
{
  10 dict begin

  //PDFSwitches {
    dup where
    { exch dup 3 1 roll get def }
    { pop } ifelse
  } forall

  % This isn't a command line parameter, we track it internally, but we need to
  % send it to the interpreter. It is used to 'offset' the page Dest for Link
  % annotations and Outlines by the numebr of pages processed so far.
  /PageCount /CumulativePageCount where { /CumulativePageCount get }{currentpagedevice /PageCount get} ifelse def

  currentdict end
} .internalbind def

currentdict /PDFSwitches undef

/pdfpagecount
{
  currentdict /PDFInfo known
  {
    PDFInfo
  }
  {
    PDFFile //null eq not
    {
      PDFSTOPONERROR
      {
        PDFFile .PDFInfo //false
      }
      {
        PDFFile {.PDFInfo} stopped
      } ifelse
    }
    {
      //true
    }ifelse

    {
      <</NumPages 0>>
    } if
  } ifelse

  dup /NumPages known
  {
    /NumPages get
  }
  {
    pop 0
  } ifelse
}.internalbind def

/runpdfpagerange
{
  /PageList where {
    pop
    PageList pdfpagecount .PDFparsePageList
    dup 0 eq {	% No ranges, error
      (\n   **** Error: Invalid PageList: ) print
      PageList  print
      (\n   No pages will be processed.) = flush
      /runpdfpagerange cvx /syntaxerror signalerror
    } if
    array astore	% move integer triples from the stack to the array
    % newpdf PDFPageList is an array of 3 elements per range: even/odd flag, start, end
    /PDFPageList exch def
    QUIET not {
      (Processing pages ) print PageList =only (.) = flush
    } if
    1 pdfpagecount	% dummy parameters for newpdf_dopages
  }{
    /FirstPage where {
      pop FirstPage dup pdfpagecount gt {
        (\nRequested FirstPage is greater than the number of pages in the file: ) print
         pdfpagecount = flush
      } if
    } {
       1
    } ifelse
    /LastPage where {pop LastPage pdfpagecount .min } { pdfpagecount } ifelse
    1 index 1 index gt {
      (   No pages will be processed \(FirstPage > LastPage\).) = flush
    } {
      QUIET not {
        (Processing pages ) print 1 index =only ( through ) print dup =only
        (.) = flush
      } if
    } ifelse
  } ifelse
} .internalbind def


/runpdf_collection_entry
{
  <</PreserveDocView false>> runpdfbegin_with_params                       % <file> runpdfbegin -
  PDFInfo type /dicttype eq
  {
    PDFInfo /Collection known
    {
      PDFInfo /Collection get
      pdfclose
      dup length 1 sub 0 2 3 -1 roll
      {
        1 index exch get (r) file runpdf_collection_entry
      } for
      pop
    }
    {
      process_trailer_attrs             % - process_trailer_attrs -
      runpdfpagerange                   % - runpdfpagerange <int> <int>
      dopdfpages                        % <int> <int> dopdfpages -
      runpdfend                         % - runpdfend -
    } ifelse
  } {
    pop pop
  }ifelse
} .internalbind def

% <file> runpdf -
/newpdf_runpdf
{
  runpdfbegin                       % <file> runpdfbegin -
  PDFInfo type /dicttype eq
  {
    PDFInfo /Collection known
    {
      PDFInfo /Collection get
      pdfclose
      dup length 1 sub 0 2 3 -1 roll
      {
        1 index exch get (r) file runpdf_collection_entry
      } for
      pop
      % We need to do this, even though we called pdfclose above, in order to clean
      % up our dictionary and restore the state. We can't use runpdfend above because
      % if we do then we will delete all the temporary files containing the embedded
      % PDF files.
      runpdfend
    }
    {
      process_trailer_attrs             % - process_trailer_attrs -
      runpdfpagerange                   % - runpdfpagerange <int> <int>
      dopdfpages                        % <int> <int> dopdfpages -
      runpdfend                         % - runpdfend -
    } ifelse
  } {
    pop pop
  }ifelse
} .internalbind def

% start a PDF file, and specify interpreter parameters to be used
% to override any command line ones.
% Mostly for the benefit of PDF Collections where we want to disable
%preserving DocView information for pdfwrite.
%
% file <dict> runpdfbegin_with_params -
/runpdfbegin_with_params
{
  /pdfdict 10 dict def
  pdfdict begin

  currentpagedevice /PageCount get
  /CumulativePageCount exch def
  % This is for the benefit of pdf2dsc which assumes it will be present
  /Trailer << >> def
  /PDFSave save def
  % Define these in the current dictionary, if anyone uses
  % systemdict then this will be defeated so we'll add extra
  % code to the systemdict definitions as well.
%  /pdfpagecount /newpdf_pagecount load def
%  /pdfgetpage /newpdf_pdfgetpage load def
%  /pdfshowpage /newpdf_pdfshowpage load def
%  /pdfshowpage_init /newpdf_pdfshowpage_init load def
%  /pdfshowpage_setpage /newpdf_pdfshowpage_setpage load def
%  /pdfshowpage_finish /newpdf_pdfshowpage_finish load def
%  /pdfopen /newpdf_pdfopen load def
%  /pdfclose /newpdf_pdfclose load def
%  /dopdfpages /newpdf_dopdfpages load def
%  /runpdfend /newpdf_runpdfend load def
%  /runpdfpagerange /newpdf_runpdfpagerange load def
%  /process_trailer_attrs /newpdf_process_trailer_attrs load def

  % These are also for the benefit of pdf2dsc, which assumes they
  % are available.
  /knownoget {2 copy known {get //true}{pop pop //false}ifelse} bind def
  /pget {2 copy known {get //true}{pop pop //false}ifelse}bind def

  newpdf_gather_parameters          % <parameter overrides dict> <parameter dict>
  exch {2 index 3 1 roll put} forall
  PDFSTOPONERROR
  {
    .PDFInit
    /PDFFile exch def
    pdfopen
    /PDFInfo exch def
    pop
  }
  {
    {.PDFInit} stopped
    {
      (   **** Error: Failed to initialise PDF interpreter.\n) newpdf_pdfformaterror
      /PDFFile //null def
      /PDFInfo //null def
    }
    {
      /PDFFile exch def
      pdfopen
      /PDFInfo exch def
      pop
    }ifelse
  }ifelse
} .internalbind def

% -file- runpdfbegin -
/runpdfbegin
{
  <<>> runpdfbegin_with_params
} .internalbind def

/pdfgetpage
{
  dup 1 sub
  PDFSTOPONERROR
  {
    PDFFile exch .PDFPageInfo
    dup 3 -1 roll
    /Page# exch put
  }
  {
    PDFFile exch {.PDFPageInfo} stopped
    {
      pop pop
      (   **** Error: Couldn't get page info.\n) newpdf_pdfformaterror
      (               Output may be incorrect.\n) newpdf_pdfformaterror
      //null
    }{
      dup 3 -1 roll
      /Page# exch put
    } ifelse
  }ifelse
} .internalbind def

/process_trailer_attrs
{
}.internalbind def

/pdfshowpage_init
{
} .internalbind def

/pdfshowpage_setpage
{
  dup newpdf_device_setup
  dup newpdf_set_pagesize
} .internalbind def

/pdfshowpage_finish
{
  PDFSTOPONERROR
  {
    /Page# get PDFFile exch 1 sub .PDFDrawPage
    showpage
  }
  {
    /Page# get PDFFile exch 1 sub {.PDFDrawPage} stopped
    {
      pop pop
      (   **** Error: Page drawing error occurred.\n) newpdf_pdfformaterror
      (               Output may be incorrect.\n) newpdf_pdfformaterror
    } if
    {showpage} stopped
    {
      (   **** Error: Page drawing error occurred.\n) newpdf_pdfformaterror
      (               Could not draw this page at all, page will be missing in the output.\n) newpdf_pdfformaterror
    } if
  }ifelse
} .internalbind def

/pdfshowpage
{
  /PDFINFO where {/PDFINFO get}{//false}ifelse
  {
    /Page# get PDFFile exch 1 sub {.PDFDrawPage} stopped pop
  }
  {
    pdfshowpage_init
    pdfshowpage_setpage
    pdfshowpage_finish
  }ifelse
} .internalbind def

/runpdfend
{
  % Get the accumulated count of pages processed so far
  % and the number of pages in this file. Do this before
  % we close the file and restore the state. Save the values
  % on the stack.
  pdfpagecount
  CumulativePageCount

  pdfclose
  PDFSave restore

  end   % pdfdict

  % add the number of pages in this file to the accumulated count,
  % and store that in the device for later reuse. This allows us to
  % add the number of pages already in the output to the 'Dest' of
  % Outlines and Link annotations.
  add <</PageCount 3 -1 roll >> setpagedevice
} .internalbind def

/pdfopen
{
  dup PDFFile //null ne
  {
    PDFSTOPONERROR
    {
      PDFFile .PDFStream
      PDFFile .PDFInfo
    }
    {
      PDFFile {.PDFStream} stopped
      {
        pop pop
        (   **** Error: Couldn't initialise file.\n) newpdf_pdfformaterror
        (               Output may be incorrect.\n) newpdf_pdfformaterror
        <</NumPages 0>>
      }
      {
        PDFFile {.PDFInfo} stopped
        {
          pop
          (   **** Error: Couldn't get page information.\n) newpdf_pdfformaterror
          (               Output may be incorrect.\n) newpdf_pdfformaterror
          <</NumPages 0>>
        } if
      } ifelse
    }ifelse
  }
  {
    pop
    <</NumPages 0>>
  } ifelse
} .internalbind def

/pdfclose
{
  PDFSTOPONERROR
  {
    PDFFile .PDFClose
  }
  {
    PDFFile {.PDFClose} stopped
    {
      pop
    } if
  }ifelse
} .internalbind def

/pdfavailable
{
  .PDFAvailable
} .internalbind def

% <int> <int> dopdfpages -
% First Page and then LastPage
% If PDFPageList array exists, the parameters are 1 pdfpagecount and are ignored.
/dopdfpages
{
  //DisablePageHandlerDevice exec
  %% If we have a array of page ranges to render, use it.
  /PDFPageList where {
    pop
    pop pop		% don't use dummy parameters
    PDFPageList
    % process the ranges (3 elements per range)
    0 3 2 index length 1 sub {
      1 index 1 index get		% even = 2, odd = 1 any = 0
      2 index 2 index 1 add get		% start of range
      exch
      3 index 3 index 2 add get		% end of range
      exch
      % stack: start end even/odd
      0 eq { 1 } { 2 } ifelse
      2 index 2 index gt { neg } if	% negate increment for reverse range
      exch
      {
        pdfgetpage
        dup //null ne {
          pdfshowpage
        } {
          PDFSTOPONERROR {
            /dopdfpages cvx /syntaxerror signalerror
          } {
            pop pop
            (   **** Error: page) newpdf_pdfformaterror
            ( not found.\n) newpdf_pdfformaterror
          } ifelse
        } ifelse
      } for
      pop		% for loop index
    } for
    pop		% done with array
  } {
    % else, Process the pages given by the FirstPage, LastPage
    1 exch
    {
      pdfgetpage
      dup //null ne {
        pdfshowpage
      } {
        PDFSTOPONERROR {
          /dopdfpages cvx /syntaxerror signalerror
        } {
          pop pop
          (   **** Error: page) newpdf_pdfformaterror
          ( not found.\n) newpdf_pdfformaterror
        } ifelse
      } ifelse
    } for
  } ifelse
  //EnablePageHandlerDevice exec
} .internalbind def

/.runps /run load def
/run {
  dup type /filetype ne { (r) file } if
  % skip leading whitespace characters (actually anything less than or equal to <sp>)
  { dup ( ) .peekstring not { //false exit } if
    dup 0 get 32 le { pop dup read pop pop } { //true exit } ifelse
  } loop
  exch pop
  {
    % Appletalk PAP sends short strings with %! header expecting a response.
    % 'gv' swallows the %!PS line, then sends DSC comments beginning with %%
    % and also waits for a response. The following avoids those hangs.
    %
    % This section commented out. It causes problems with (poorly created) PDF files which have
    % extraneous data prior to the '%PDF-' string which are incorrectly recognised as PostScript
    % programs. I really don't think we need care about AppleTalk any more and testing with 'gv'
    % shows no problems with removing this early test, and simply falling into the 'not a PDF file'
    % case below.
    %
    % I've preserved the code in comments for the next release (10.05.0), in case it does prove to be essential.
    %
    %    dup 2 string .peekstring pop dup (%!) eq exch (%%) eq or {
    %      cvx .runps
    %    } {

    dup 1023 string .peekstring pop
    % "1024 string" exceeds current %stdin buffer
    % Valid PDF file cannot be smaller than 400 bytes.
    (%PDF-) search {
      3 1 roll pop pop
      dup (%!PS) search not {
        length 0 ne {
          1 index exch readstring pop pop
          (%stderr) (w) file dup
          (   **** Warning: File has some garbage before %PDF- .\n)
          writestring flushfile
          PDFSTOPONWARNING {
            /run cvx /unregistered signalerror
          } if
        } {
          pop
        } ifelse
        dup (%stdin) (r) file eq {
          % Copy PDF from stdin to temporary file then run it.
          //null (w+) /.tempfile .systemvar exec exch 3 1 roll
          % stack: tempname stdin tempfile
          64000 string
          {
            % stack: tempname stdin tempfile string
            2 index 1 index readstring
            exch 3 index exch writestring
            not { exit } if
          }
          loop
          pop exch closefile
          % stack: tempname tempfile
          dup 0 setfileposition
          dup
            pdfavailable {
              runpdf
            }{
              closefile
              (%stderr) (w) file (   **** ERROR: No PDF interpreter available, unable to process PDF files as input.\n)writestring
            } ifelse
          closefile deletefile
        } {
            pdfavailable {
              runpdf
            }{
              closefile
              (%stderr) (w) file (   **** ERROR: No PDF interpreter available, unable to process PDF files as input.\n)writestring
            } ifelse
        } ifelse
      } {
        pop pop pop pop cvx .runps % (%!PS) found first
      } ifelse
    } {
      pop cvx .runps % (%PDF-) not found
    } ifelse
    %    } ifelse
  } {
    closefile % file was empty
  } ifelse
} .internalbind odef
currentdict /runpdfstring .undef

currentdict /DisablePageHandlerDevice undef
currentdict /EnablePageHandlerDevice undef

% Copy stream to an external temporary file and
% return the file name as PS name.
/copy_embedded_file {
  //true resolvestream      % strm
  dup 1023 string .peekstring pop % "1024 string" exceeds current %stdin buffer
  dup length 400 ge {             % Valid PDF file cannot be smaller than 400 bytes.
     (%PDF-) search {
       pop pop pop //true
     } {
       pop //false
     } ifelse
   } {
     pop //false
   } ifelse {
    //null (w) /.tempfile   % strm (name) null (w) /.tempfile
    .systemvar exec         % strm (name) file
    3 -1 roll               % (name) file strm
    32768 string            % (name) file strm (buf)
    { 3 copy readstring     % (name) file strm (buf) file (data) bool
      3 1 roll              % (name) file strm (buf) bool file (data)
      writestring           % (name) file strm (buf) bool
      not { exit } if
    } loop
    pop closefile           % (name) file
    closefile               % (name)
    cvn                     % /name
  } {
    closefile
  } ifelse
} .internalbind def

% Recursively enumerate /Names entries
% <node> pdf_collection_names /temp_file_name ...
/pdf_collection_names {
  dup /Names knownoget {
    exch pop
    { oforce
      dup type /dicttype eq {
        /EF knownoget {
          /F knownoget {
            copy_embedded_file
          } if
        } if
      } {
        pop
      } ifelse
    } forall
  } {
    /Kids knownoget {
      { oforce
        dup //null ne {
          pdf_collection_names
        } {
          pop
        } ifelse
      } forall
    } if
  } ifelse
} .internalbind def

/runpdf {        % <file> runpdf -
    dup type /filetype eq
    {
      dup
      PDFSTOPONERROR
      {
        newpdf_runpdf
      }
      {
        {newpdf_runpdf} stopped
        {
          (   **** Error: PDF interpreter encountered an error processing the file.\n) pdfformaterror
        } if
      }ifelse
      closefile
    }
    {
      (   **** Error: Attempt to process something other than a file object in runpdf.\n) pdfformaterror
    } ifelse
} .internalbind odef

end            % systemdict

% Redefine the procedure that the C code uses for running piped input.
% It is OK to use { (%stdin) run } here, because a startjob cannot occur.
/.runstdin {
  { (%stdin) run } execute0
} .internalbind def

end            % userdict

% ------ Transparency support ------ %

systemdict /ALLOWPSTRANSPARENCY get
{
  /.setopacityalpha
  {
    /.setfillconstantalpha where
    {
      pop
      (   **** WARNING: .setopacityalpha is deprecated (as of 9.53.0) and will be removed in a future release\n) print
      (   **** See .setfillconstantalpha/.setalphaisshape for the improved solution\n) print flush
      false .setalphaisshape
      dup .setfillconstantalpha
      .setstrokeconstantalpha
    }
    {
      /.setopacityalpha /undefined cvx signalerror
    } ifelse
  } .internalbind def

  /.setshapealpha
  {
    /.setfillconstantalpha where
    {
      pop
      (   **** WARNING: .setshapealpha is deprecated (as of 9.53.0) and will be removed in a future release.\n) print
      (   **** See .setfillconstantalpha/.setalphaisshape for the improved solution\n) print flush
      true .setalphaisshape
      dup .setfillconstantalpha
      .setstrokeconstantalpha
    }
    {
      /.setshapealpha /undefined cvx signalerror
    } ifelse
  } .internalbind def
} if

.setglobal

%% This list of operators are used internally by various parts of the Ghostscript PDF interpreter.
%% Since each operator is a potential security vulnerability, and any operator listed here
%% is not required once the initislisation is complete and functions are bound, we undefine
%% the ones that aren't needed at runtime.
[
/.setdistillerparams
] systemdict .undefinternalnames

% The following are split out allowing control via ALLOWPSTRANSPARENCY command line param
% The examples/transparency_example.ps uses some of these (on the first line).
[
  /.pushpdf14devicefilter /.poppdf14devicefilter /.setstrokeconstantalpha /.setfillconstantalpha /.endtransparencygroup
  /.currentblendmode /.currenttextknockout /.begintransparencytextgroup
  /.endtransparencytextgroup /.begintransparencymaskgroup /.begintransparencymaskimage /.begintransparencypagegroup
  /.endtransparencymask /.image3x /.abortpdf14devicefilter /.setstrokeconstantalpha /.setfillconstantalpha /.setalphaisshape /.currentalphaisshape
  % undefining these causes errors/incorrect output
  %/.setblendmode /.begintransparencygroup /.settextknockout /.setstrokeoverprint /.setfilloverprint
  %/.currentstrokeoverprint /.currentfilloverprint /.currentfillconstantalpha /.currentstrokeconstantalpha
  %/.setSMask /.currentSMask

] systemdict dup /ALLOWPSTRANSPARENCY get {pop pop}{.undefinternalnames}ifelse
