% 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.
%

% Extending Font resource category with CIDFont-CMap fonts.

languagelevel 2 .setlanguagelevel currentglobal //true setglobal

% In the comments below, 'CSI' is an abbreviation/acronym for CIDSystemInfo.
% We pre-scan resource files to retrieve the CSI from them.
% First we define a hidden procset .prs_dict containing
% necessary variables and procedures.
% Then we redefine the old /Font category using this procset.

% We maintain internal caches for the CSI values retrieved from
% resource files. This supposes that document doesn't uninstall
% resource files. To disable caching, set enable_cache to false.

% We assume that names starting with '.prs' do not appear in resource files.
% If this causes any problem, this prefix should be systematically changed
% in this file.  ('prs' is an abbreviation for 'prescan'.)

25 dict begin

% Define local variables :

/.prs_dict currentdict def       % self-reference (constant)
/.prs_empty 0 dict readonly def
/path_buffer 8192 string def
/name_buffer 1024 string def
/minus (-) 0 get def             % character code constant for '-'
/period (.) 0 get def            % character code constant for '.'
/CMap 10 dict def                % CSI cache for CMaps
/CIDFont 10 dict def             % CSI cache for CIDFonts
/enable_cache //true def         % set false to disable cache

% The folloving variables are just placeholders for ones to be set
% dynamically :
/.prsFile 0 def                   % file to prescan
/.prsResult 0 def                 % result of prescan
/.prsDictCount 0 def              % save the dictionary stack depth

% Define a dummy CIDInit procset to use while pre-scanning :

/DummyCIDInit 15 dict
begin

  /begincmap {} def
  /usecmap {pop} .internalbind def

  {stop} .internalbind
  [ /begincodespacerange /endcodespacerange /beginnotdefchar /endnotdefchar
    /beginnotdefrange /endnotdefrange /begincidchar /endcidchar /begincidrange
    /endcidrange /endcmap /usefont /StartData
  ] {
    1 index def
  } .internalbind forall
  pop

currentdict end def

% Define a local 'findresource' for pre-scanning :
% (it returns the dummy CIDInit instead of the regular CIDInit ProcSet)

/findresource { % <InstName> <CatName> findresource <inst>
  2 copy /ProcSet eq exch             % /InstName /CatName bool /InstName
  /CIDInit eq and {
    pop pop //DummyCIDInit
  } {
    //findresource exec
  } ifelse
} .internalbind def

% Define procedures for pre-scanning :

/StopIfCSIDefined {   % - StopIfCSIDefined -

  % Check if the dictionary stack contains a dictionary containing /CIDSystemInfo.
  % The search is limited to the top .prsDictCount dictionaries in the stack.
  % If so, retrieve the CSI, and execute stop to terminate the pre-scanning of the file.
  % Otherwise, do nothing, so the pre-scanning continues.

  countdictstack //.prs_dict /.prsDictCount get sub dup {
    currentdict /CIDSystemInfo .knownget {
      //.prs_dict exch /.prsResult exch put
      stop
    } if
    currentdict exch end
  } repeat {
    begin
  } repeat
} .internalbind def

/PrescanFile {     % - PrescanFile -
  { //.prs_dict /.prsFile get token {
      dup type                          % token type
      dup /nametype eq exch /operatortype eq or {
        dup xcheck {
          exec
          //StopIfCSIDefined exec
        } if
      } if
    } {
      stop
    } ifelse
  } loop
} .internalbind odef

/GetCIDSystemInfoFromFile { % <file> GetCIDSystemInfoFromFile <CSI>

  % This procedure reads resource files with 'token',
  % executing the tokens untill /CIDSystemInfo appears to be defined.
  % Normally the resource file creates a new dictionary on
  % dictionary stack and defines /CIDSystemInfo in it.
  %
  % Returns an empty dictionary if no CIDSystemInfo is found.

  RESMPDEBUG { (cidcm GetCIDSystemInfoFromFile beg) = } if
  //.prs_dict begin
  /.prsFile exch def
  /.prsResult //.prs_empty def
  /.prsDictCount countdictstack def
  RESMPDEBUG { (cidcm GetCIDSystemInfoFromFile will PrescanFile.) = } if
  { //PrescanFile } stopped pop
  //.prs_dict /.prsResult get
  end
  RESMPDEBUG { (cidcm GetCIDSystemInfoFromFile end) = } if
} .internalbind def

/GetCIDSystemInfo {     % <InstName> <CatName> GetCIDSystemInfo <CSI>

  % Retrieve CSI, using caches.

  RESMPDEBUG { (cidcm GetCIDSystemInfo beg) = } if
  /Category findresource begin                % /InstName
  dup ResourceStatus
  {
    pop 2 lt {
      FindResource /CIDSystemInfo .knownget not {
        //.prs_empty
      } if                                    % CSI
    } {                                       % /InstName
      currentdict /GetCIDSystemInfoFromMap .knownget {
        exec
      } if
      dup type /nametype eq
      {
        RESMPDEBUG { (cidcm GetCIDSystemInfo got a name.) = } if
        //.prs_dict Category get              % /InstName CSIs
        dup 2 index known
        //enable_cache and {
          RESMPDEBUG { (cidcm GetCIDSystemInfo from cache.) = } if
          exch get                            % CSI
        } {
          RESMPDEBUG { (cidcm GetCIDSystemInfo from file.) = } if
          exch                                % CSIs /InstName
          dup //path_buffer ResourceFileName  % CSIs /InstName (path)
          RESMPDEBUG { (cidcm GetCIDSystemInfo from file ) print dup = } if
          currentglobal exch //true setglobal % CSIs /InstName g (path)
          mark exch                           % CSIs /InstName g [ (path)
          { (r) file } stopped {
            cleartomark //.prs_empty
          } {
            exch 1 index                      % CSIs /InstName g file [ file
            //GetCIDSystemInfoFromFile stopped {
              cleartomark closefile //.prs_empty
            } {
              exch pop exch closefile
            } ifelse
          } ifelse                            % CSIs /InstName g CSI
          exch setglobal                      % CSIs /InstName CSI
          dup 4 1 roll                        % CSI CSIs /InstName CSI
          put                                 % CSI
          RESMPDEBUG {
            (cidcm GetCIDSystemInfo got from file : <<) print
            dup { exch //=string cvs print ( ) print
                       //=string cvs print ( ) print
            } forall
            (>>) =
          } if
        } ifelse
      } if
    } ifelse
  } {
    pop //.prs_empty
  } ifelse
  end
  RESMPDEBUG { (cidcm GetCIDSystemInfo end) = } if
} .internalbind def

/IsCompatibleCSI {  % <CSI-M> <CSI-F> IsCompatibleCSI <bool>

  % The CSI in a CIDFont may be an array, a dict, or null.
  % If it is an array, it must be of 1 element, which is a dict.
  % In this case the dict is used for testing the compatibility.
  % Two dicts are compatible iff they contain same /Ordering and /Registry.
  % Identity CMap is compatible with any CIDFont.

  exch                                  % CSI-F CSI-M
  { dup type /arraytype eq {
      dup length 1 ne {
        pop pop //false exit
      } if
      0 get
    } if                                % CSI-F CSI-M
    dup type /dicttype ne {
      pop pop //false exit
    } if                                % CSI-F <<CSI-M>>
    exch                                % <<CSI-M>> CSI-F
    dup type /dicttype ne {
      pop pop //false exit
    } if                                % <<CSI-M>> <<CSI-F>>
    dup /Ordering .knownget {
      /Identity eq {
        pop pop //true exit
      } if
    } if
    //true                              % <<CSI-M>> <<CSI-F>> bEQ
    [/Registry /Ordering] {
      2 index 1 index .knownget not {
        1234567
      } if                              % <<CSI-M>> <<CSI-F>> bEQ /key vF
      exch                              % <<CSI-M>> <<CSI-F>> bEQ vF /key
      4 index exch .knownget not {
        7654321
      } if                              % <<CSI-M>> <<CSI-F>> bEQ vF vM
      eq and                            % <<CSI-M>> <<CSI-F>> bEQ
    } forall
    exch pop exch pop                   % bEQ
    exit
  } loop
} .internalbind def

/IsWellComposed {     % <CIDFontName> <CMapName> IsWellComposed <bool>

  % Check if the given CIDFont and CMap have compatible CSIs.
  exch                                  % /CMapName /CIDFontName
  /CIDFont //GetCIDSystemInfo exec      % /CMapName CSI-F
  dup type /dicttype eq {
    dup length 0 ne {
      exch                              % CSI-F /CMapName
      /CMap //GetCIDSystemInfo exec     % CSI-F CSI-M
      //IsCompatibleCSI exec            % bool
    } {
      pop pop //false
    } ifelse
  } {
    pop pop //false
  } ifelse
} .internalbind def

/IsComposedFont {   % <FontName> IsComposedFont <CIDFontName> <CMapName> true
                    % <FontName> IsComposedFont false

  % Check if the given font name may be decomposed into CIDFont.CMap, CIDFont-CMap
  % or into CIDFont--CMap, such that CIDFont and CMap have compatible CSIs.
                                        % FontName
  dup type /stringtype ne {
    //name_buffer cvs
  } if                                  % (FontName)
  { dup length 2 sub -1 1 {
                                        % (FontName) i
      2 copy get dup //minus eq exch //period eq or {
        2 copy 2 copy                   % (FontName) i (FontName) i (FontName) i
        2 copy get //minus eq {
          2 copy 1 sub get //minus eq {
            1 sub
          } if
        } if                            % (FontName) i (FontName) i (FontName) i0
        0 exch getinterval cvn          % (FontName) i (FontName) i /CIDFontName
        3 1 roll                        % (FontName) i /CIDFontName (FontName) i
        1 add dup                       % (FontName) i /CIDFontName (FontName) i1 i1
        5 index length                  % (FontName) i /CIDFontName (FontName) i1 i1 l
        exch sub getinterval cvn        % (FontName) i /CIDFontName /CMapName
        2 copy //IsWellComposed exec {  % (FontName) i /CIDFontName /CMapName
          4 2 roll pop pop              % /CIDFontName /CMapName
          stop
        } if
        pop pop pop
      } {
        pop
      } ifelse                          % (FontName)
    } for
    pop
  } stopped
} .internalbind def

/ComposeName { % <CIDFont> <CMap> <scr> ComposeName <CIDFont-CMap>
  dup dup 5 2 roll                        % (scr) (scr) /CIDFont /CMap (scr)
  3 2 roll exch cvs length dup            % (scr) (scr) /CMap l0 l0
  4 -1 roll exch //minus put              % (scr) /CMap l0
  1 add dup                               % (scr) /CMap l1 l1
  3 index dup length                      % (scr) /CMap l1 l1 (scr) L
  2 index sub                             % (scr) /CMap l1 l1 (scr) LT
  3 2 roll                                % (scr) /CMap l1 (scr) LT l1
  exch getinterval                        % (scr) /CMap l1 (scrT)
  3 2 roll exch cvs length                % (scr) l1 l2
  add 0 exch getinterval                  % (CIDFont-CMap)
} .internalbind def

% Redefine the /Font category with CIDFont-CMap construction :

% The following code supposes that the following names are not
% defined in the old /Font category dictionary :
% /IsComposedFont, /IsWellComposed .

/Font /Category findresource dup length dict copy begin

/FindResource {  % <InstName> FindResource <inst>
  dup //ResourceStatus exec {
    pop pop //FindResource exec
  } {
    dup //IsComposedFont exec {          % /FontName /CIDFontName /CMapName
      exch [ exch ] composefont          % inst
    } {
      //FindResource exec
    } ifelse
  } ifelse
} .internalbind def

/ResourceStatus {  % <InstName> ResourceStatus <nStatus> <nSize> true
                   % <InstName> ResourceStatus false
  dup //ResourceStatus exec {
    3 2 roll pop //true                   % nStatus nSize true
  } {
    //IsComposedFont exec {               % /CIDFontName /CMapName
      /CMap resourcestatus {              % /CIDFontName nStatusM nSizeM
        exch pop exch                     % nSizeM /CIDFontName
        /CIDFont resourcestatus {         % nSizeM nStatusF nSizeF
          exch pop                        % nSizeF nSizeM
          dup 0 ge {
            exch dup 0 ge {
              add
            } {
              exch pop
            } ifelse
          } {
            pop
          } ifelse                        % nSize
          2 exch //true                   % nStatus nSize true
        } {
          pop pop pop //false  % work around buggy resource file
        } ifelse
      } {
        pop pop pop //false    % work around buggy resource file
      } ifelse
    } {
      //false
    } ifelse
  } ifelse
} .internalbind def

/ResourceForAll { % <template> <proc> <scratch> ResourceForAll -

  % We suppose that the resourceforall procedure does not
  % define or install new fonts, CMaps, and/or CIDFonts.

  % First we create 3 temporary dictionaries to store temporary data
  % about fonts, CMaps and CIDFonts.
  % These dictionaries must be created dynamically, to allow for a possible
  % recursive call to resourceforall from the resourceforall procedure.
  currentglobal //false setglobal
  20 dict 20 dict 20 dict                  % (templ) proc (scr) g <<CIDFont>> <<CMap>> <<Fonts>>

  % Store resource identifiers into local dictionaries
  % A resource instance can have a key that is not a name or a string. In this
  % case, resourceforall passes the key directly to proc instead of copying it
  % into the scratch string. This case can arise only for a resource instance
  % defined in virtual memory by a previous defineresource

  % Discard non-string keys of CIDFont and CMap because <CIDFontName>-<CMapName>
  % is only defined for names.

  { /.DisableResourceOrdering pop % gs_resmp accesses this through execstack - don't remove !

    6 index [ 2 index {exch //null put} aload pop ] cvx bind 6 index //ResourceForAll exec

    (*) [ 3 index {exch dup type /stringtype eq { cvn dup put } { pop pop } ifelse } aload pop
        ] cvx bind 6 index /CMap resourceforall

    (*) [ 4 index {exch dup type /stringtype eq { cvn dup put } { pop pop } ifelse } aload pop
        ] cvx bind 6 index /CIDFont resourceforall

    exit
  } loop % This loop is a pattern for execstack_lookup - don't remove !
  4 -1 roll setglobal                     % (templ) proc (scr) <<CIDFont>> <<CMap>> <<Fonts>>

  %% Make the list of fonts in the form (/Name status) :

                                          % (templ) proc (scr) <<CIDFont>> <<CMap>> <<Fonts>>
  dup {
    pop dup
    //ResourceStatus exec {
      pop 2 index                         % (templ) proc (scr) <<CIDFont>> <<CMap>> <<Fonts>> /Name nStatus <<Font>>
      3 1 roll put                        % (templ) proc (scr) <<CIDFont>> <<CMap>> <<Fonts>>
    } {
      pop
    } ifelse
  } forall                                % (templ) proc (scr) <<CIDFont>> <<CMap>> <<Fonts>>

  %% Add CIDFont-CMap to it (filtering duplicates) :

  3 2 roll  {
    3 index {
      3 1 roll                            % (templ) proc (scr) <<CMap>> <<Font>> /CIDFont /CMap /CIDFont /CMap
      6 index //ComposeName exec          % (templ) proc (scr) <<CMap>> <<Font>> /CIDFont /CMap (Font)
      dup 8 index .stringmatch {
        cvn                               % (templ) proc (scr) <<CMap>> <<Font>> /CIDFont /CMap /Font
        dup 4 index exch known {
          pop pop
        } {
          2 index                         % (templ) proc (scr) <<CMap>> <<Font>> /CIDFont /CMap /Font /CIDFont
          4 2 roll                        % (templ) proc (scr) <<CMap>> <<Font>> /Font /CIDFont /CIDFont /CMap
          //IsWellComposed exec {
            exch 2 index exch 2 put       % (templ) proc (scr) <<CMap>> <<Font>> /CIDFont
          } {
            exch pop
          } ifelse
        } ifelse
      } {
        pop pop
      } ifelse
      dup                                 % (templ) proc (scr) <<CMap>> <<Font>> /CIDFont /CIDFont
    } forall
    pop pop                               % (templ) proc (scr) <<CMap>> <<Font>>
  } forall                                % (templ) proc (scr) <<CMap>> <<Font>>
  exch pop                                % (templ) proc (scr) <<Font>>
  4 3 roll pop                            % proc (scr) <<Font>>

  % Make the enumerator and apply it :
  /MappedCategoryRedefiner /ProcSet findresource /MakeResourceEnumerator get exec exec
} .internalbind def

currentdict end /Font exch /Category defineresource pop
end
setglobal .setlanguagelevel
