% Copyright (C) 2001-2023 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.
%

% Allow the interpreter to encapsulate EPS files, to recognize MS-DOS
% EPSF file headers, and skip to the PostScript section of the file.

% Encapsulate EPS files and optionally resize page or rescale image.
% To display an EPS file cropped to the bounding box:
%  gs -dEPSCrop file.eps
% To display an EPS file scaled to fit the page:
%  gs -dEPSFitPage file.eps
% To display a file without EPS encapsulation:
%  gs -dNOEPS file.ps

% When starting to process an EPS file, state is 0.
% After %%BoundingBox processed, state is 1 if OK or 2 if cropped.
% After %%HiResBoundingBox processed, state is 3 if OK or 4 if cropped.
% After %%EndComments processed, state is 5.
/EPSBoundingBoxState 5 def
/EPSBoundingBoxString () def	% set if either BoundingBox is seen (even if invalid)
/EPSBoundingBoxSetState {
  //systemdict /EPSBoundingBoxState 3 -1 roll .forceput
} .forcebind odef % .forceput must be bound and hidden

% Parse 4 numbers for a bounding box
/EPSBoundingBoxParse { % (llx lly urx ury) -- llx lly urx ury true OR false
    mark exch
    token {exch token {exch token {exch token {exch pop} if} if} if} if
    counttomark
    4 eq {
      5 -1 roll pop % remove mark
      //true
    } {
      cleartomark //false
    } ifelse
} .internalbind odef

% Crop the page to the BoundingBox
/EPSBoundingBoxCrop { % llx lly urx ury --
  EPSDEBUG {
    (gs_epsf.ps: Setting pagesize from EPS bounding box\n) print flush
  } if
  exch 3 index sub exch 2 index sub % stack: llx lly urx-llx ury-lly
  << /PageSize [ 5 -2 roll ] >> setpagedevice
  neg exch neg exch translate
} .internalbind odef

% Rescale, translate and rotate to fit the BoundingBox on the page
/EPSBoundingBoxFitPage { % llx lly urx ury --
  EPSDEBUG { (gs_epsf.ps: Rescaling EPS to fit page\n) print flush } if
  clippath pathbbox newpath    % ellx elly eurx eury  pllx plly purx pury
  EPSDEBUG {
    (Page Coordinates: LLX: ) print 3 index =print (, LLY: ) print
    2 index =print (, URX: ) print 1 index =print (, URY: ) print dup = flush
  } if
  % Convert box corners to coordinates of the center and box sizes
  2 {	% loop doing the page coordinates, the the EPS bbox coordinates
    3 -1 roll exch        % ... llx urx lly ury
    2 {		% loop doing Y then X coordnates
      2 copy exch sub     % ... llx urx lly ury ury-lly
      3 1 roll            % ... llx urx ury-lly lly ury
      add 2 div           % ... llx urx ury-lly (lly+ury)/2
      4 2 roll            % ... ury-lly (lly+ury)/2 llx urx
    } repeat
    8 4 roll
  } repeat
  % edx, edy = EPS dimension X and Y, ecx, ecy = EPS Center X and Y.
  % pdx and pcx, etc, are for the Page values.
  % edx ecx edy ecy pdx pcx pdy pcy

  % Move the origin to the center of the printable area.
  3 -1 roll exch          % edx ecx edy ecy pdx pdy pcx pcy
  translate               % edx ecx edy ecy pdx pdy

  % Find orientation of the best fit. Square pages or files don't rotate.
  2 copy sub              % edx ecx edy ecy pdx pdy pdx-pdy
  EPSDEBUG {
    (pdx: ) print 2 index =print (, pdy: ) print 1 index =print
    (, pdx-pdy: ) print dup = flush
  } if
  6 index 5 index sub
  EPSDEBUG {
    (edx: ) print 7 index =print (, edy: ) print 5 index =print
    (, edx-edy: ) print dup = flush
  } if
  mul % edx ecx edy ecy pdx pdy (pdx-pdy)*(edx-edy)
  EPSDEBUG {
    (product: ) print dup = flush
  } if
  0 lt {
    90 rotate
    exch
  } if

  % Scale to fit in the most restricting direction.
  4 -1 roll div           % edx ecx ecy pdx pdy/edy
  exch 5 -1 roll div      % ecx ecy pdy/edy pdx/edx
  //.min exec
  dup scale               % ecx ecy

  % Center the document
  neg exch neg exch translate

} .internalbind odef

/EPSBoundingBoxProcess { % (llx lly urx ury) state --
  % The following 'lt' check prioritzies HiResBoundingBox over BoundingBox
  % even if HiResBoundingBox occurs first in the EPS file.
  //systemdict /EPSBoundingBoxState get 1 index lt {
    % save the BBoxString for possible FitPage when EndComments is seen
    exch dup currentglobal //true setglobal exch
    dup length string copy //systemdict /EPSBoundingBoxString 3 -1 roll .forceput
    setglobal
    EPSBoundingBoxParse
    {
      //systemdict /EPSCrop known {
        EPSBoundingBoxCrop
      } {
        //systemdict /EPSFitPage known not {
          % Warn if some of the EPS file will be clipped
          clippath pathbbox newpath
          { % context for exit
            5 -1 roll lt { 6 { pop } repeat //true exit } if
            4 -1 roll lt { 4 { pop } repeat //true exit } if
            3 -1 roll gt { 2 { pop } repeat //true exit } if
            exch gt { //true exit } if
            //false exit
          } loop
          QUIET not and /EPSBoundingBoxState .systemvar 1 and 1 eq and {
            (\n   **** Warning: Some of the BoundingBox for the EPS file will be clipped.) =
            (                 Use -dEPSCrop or -dEPSFitPage to avoid clipping.\n) =
            flush
            1 add
          } if
        } {
          pop pop pop pop
        } ifelse
      } ifelse
      EPSBoundingBoxSetState
    } {
      % improperly formed BoundingBox string.
      QUIET not {
        (\n   **** Warning: BoundingBox values are invalid and will be ignored: ') print
        EPSBoundBoxString print (') = flush
      } if
      pop % state
    } ifelse
  } {
    pop pop
  } ifelse
} .internalbind odef

% Perform anchorsearch on the strings in the array until a match is found.
/anchorsearchforany { % haystack [needle1 ...] --> post needle true
                      %                        --> haystack false
    false 3 1 roll    % false haystack [...]
    {                 % false haystack needle
      dup 3 1 roll    % false needle haystack needle
      anchorsearch {
                      % false needle post needle
        pop           % false needle post
        3 1 roll      % post false needle
        exch not exch % post true needle
        exit
      } {
                      % false needle haystack
        exch pop      % false haystack
      } ifelse
    } forall
    exch              % haystack false | post needle true
} .internalbind def

/ProcessEPSComment { % file comment --  file comment
  /EPSBoundingBoxState .systemvar 3 lt {
    dup
    (%%BoundingBox:) anchorsearch {
      pop
      EPSDEBUG { (gs_epsf.ps: found %%BoundingBox\n) print flush } if
      1 EPSBoundingBoxProcess
    } {
      (%%HiResBoundingBox:) anchorsearch {
        pop
        EPSDEBUG { (gs_epsf.ps: found %%HiResBoundingBox\n) print flush } if
      3 EPSBoundingBoxProcess
      } {
        pop % Not interested in this DSC comment
      } ifelse
    } ifelse
  } if
  /EPSBoundingBoxState .systemvar 5 lt {
    dup (%%DocumentCustomColors:) anchorsearch {
      pop
      [ exch
        { {
            token not { exit } if
            dup type /stringtype ne { stop } if
            dup (atend) eq { stop } if
            exch
          } loop
          {
            counttomark 2 add index
            dup (123) .peekstring not { stop } if
            (%%+) eq {
              dup (123) readstring pop pop
              256 string readline pop
              cvx exec
            } {
              pop exit
            } ifelse
          } loop
        } stopped {
          cleartomark
        } {
          counttomark 0 gt {
            currentpagedevice /MaxSeparations get 4 gt {
              ] << /SeparationColorNames 3 -1 roll >> setpagedevice
            } {
              cleartomark
            } ifelse
          } {
            cleartomark
          } ifelse
        } ifelse
    } {
      {(%%EndComments) (%%BeginProlog) (%%BeginSetup)} anchorsearchforany {
        EPSDEBUG { (EPSComment processing finished, encountered: ) print dup = } if
        pop pop	% discard the strings from the anchorsearch
        % We may have seen BoundingBox or HiResBounfingBox. If so and if EPSFitPage
        % is set, then we do the transformation here to scale and center the page,
        % rotating if needed (and AllowFitPageRotation is true -- the default.)
        //systemdict /EPSFitPage known
        //systemdict /EPSBoundingBoxState get 0 gt
        and {
          EPSBoundingBoxString EPSBoundingBoxParse {
            EPSBoundingBoxFitPage
          } if
        } if
        % Ignore any following comments
        5 EPSBoundingBoxSetState
      } {
        pop	% Not %%EndComments -- ignore it
      } ifelse
    } ifelse
  } if
} .internalbind def

% Install EPS handler for DSC comments, which we do later
/EPSBoundingBoxInit {
  systemdict /NOEPS known not {
    % Merge ProcessEPSComment with existing handler
    //ProcessEPSComment /exec load
    currentuserparams /ProcessDSCComment get
    dup //null eq {pop {pop pop}} if /exec load
    4 array astore cvx readonly
    << /ProcessDSCComment 3 -1 roll >> setuserparams
  } if
} .internalbind odef


/.runNoEPS /run load def

/.runEPS { % file OR string --
  .updatematrices
  /runEPS_save save def
  /runEPS_dict_count countdictstack def
  /runEPS_op_count count 2 sub def
  /runEPS_page_count .currentshowpagecount not {0} if def
  0 EPSBoundingBoxSetState
  //.runNoEPS
  .currentshowpagecount not {0} if runEPS_page_count sub 0 eq
  { /showpage load exec } if
  count runEPS_op_count sub {pop} repeat
  countdictstack runEPS_dict_count sub {end} repeat
  runEPS_save restore
} .internalbind odef

/run { % file OR string --
  dup type /filetype ne { (r) file } if
  dup (%!PS-Adobe-) .peekstring {
    (%!PS-Adobe-) eq {
      dup (%!PS-Adobe-X.X EPSF-X.X) .peekstring {
      (EPSF) search {
        pop pop pop
        EPSDEBUG {(runEPS: Found EPS\n) print flush} if
        systemdict /NOEPS known {
          cvx //.runNoEPS
        } {
          cvx .runEPS
        } ifelse
      } {
        EPSDEBUG {(runEPS: Normal DSC\n) print flush} if
        pop
          cvx //.runNoEPS

      } ifelse
      } {
        EPSDEBUG {(runEPS: Short DSC\n) print flush} if
      pop
        cvx //.runNoEPS
      } ifelse
    } {
      EPSDEBUG {(runEPS: Not DSC\n) print flush} if
      cvx //.runNoEPS
    } ifelse
  } {
    EPSDEBUG {(runEPS: Short non-DSC\n) print flush} if
    pop
    cvx //.runNoEPS
  } ifelse
} .internalbind odef

% Handle DOS EPS files.

/.runnoepsf /run load def
/.epsfheader <C5D0D3C6> def
/run
 { dup type /filetype ne { (r) file } if
                % Check for MS-DOS EPSF file (see Red Book p. 729).
 dup (    ) .peekstring
  { .epsfheader eq { dup (    ) readstring exch pop } { //false } ifelse }
  { pop //false }
 ifelse
                % Stack: file true/false
    {		% This block is executed if the file is MS-DOS EPSF.
                % Build up the little-endian byte offset and length.
      2
        { 1 0 4
           { 2 index read not { pop exit } if % if EOF, let error happen
             2 index mul add exch 256 mul exch
           }
          repeat exch pop exch
        }
      repeat
                % Stack: offset length file
                % Use flushfile to skip quickly to the start of the
                % PostScript section.
      dup 4 -1 roll 12 sub () /SubFileDecode filter flushfile
                % Now interpret the PostScript.
      exch () /SubFileDecode filter cvx run
    }
    { //.runnoepsf
    }
   ifelse
 } odef

% rebind .runstdin to use redefined run
userdict begin
/.runstdin {
  { (%stdin) run } execute0
} .internalbind def
end

% undefine things defined in this file and not referenced elsewhere
[
    /.runNoEPS
    /.runnoepsf
    /.runEPS
    /EPSBoundingBoxSetState
    /EPSBoundingBoxString
    /EPSBoundingBoxCrop
    /EPSBoundingBoxFitPage
    /EPSBoundingBoxParse
    /EPSBoundingBoxProcess
    /ProcessEPSComment
] currentdict .undefinternalnames
