% \iffalse meta-comment
%
%% File: l3backend-header.dtx
%
% Copyright (C) 2019-2024 The LaTeX Project
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
% license or (at your option) any later version.  The latest version
% of this license is in the file
%
%    https://www.latex-project.org/lppl.txt
%
% This file is part of the "l3backend bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\documentclass[full,kernel]{l3doc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{l3backend-header} module\\ Backend graphics support^^A
% }
%
% \author{^^A
%  The \LaTeX{} Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
%
% \date{Released 2024-05-08}
%
% \maketitle
%
% \begin{documentation}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3backend-header} implementation}
%
%    \begin{macrocode}
%<*dvips&header>
%    \end{macrocode}
%
% \begin{macro}[no-user-doc]{color.sc}
%   Empty definition for color at the top level.
%    \begin{macrocode}
/color.sc { } def
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{TeXcolorseparation, separation}
%   Support for separation/spot colors: this strange naming is so
%   things work with the color stack.
%    \begin{macrocode}
TeXDict begin
/TeXcolorseparation { setcolor } def
end
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{pdf.globaldict}
%   A small global dictionary for backend use.
%    \begin{macrocode}
true setglobal
/pdf.globaldict 4 dict def
false setglobal
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
%   {
%     pdf.cvs     ,
%     pdf.dvi.pt  ,
%     pdf.pt.dvi  ,
%     pdf.rect.ht
%   }
%   Small utilities for PostScript manipulations. Conversion to DVI dimensions
%   is done here to allow for |Resolution|. The total height of a rectangle
%   (an array) needs a little maths, in contrast to simply extracting a value.
%    \begin{macrocode}
/pdf.cvs { 65534 string cvs } def
/pdf.dvi.pt { 72.27 mul Resolution div } def
/pdf.pt.dvi { 72.27 div Resolution mul } def
/pdf.rect.ht { dup 1 get neg exch 3 get add } def
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{pdf.linkmargin, pdf.linkdp.pad, pdf.linkht.pad}
%   Settings which are defined up-front in |SDict|.
%    \begin{macrocode}
/pdf.linkmargin { 1 pdf.pt.dvi } def
/pdf.linkdp.pad { 0 } def
/pdf.linkht.pad { 0 } def
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
%   {
%     pdf.rect        ,
%     pdf.save.ll     ,
%     pdf.save.ur     ,
%     pdf.save.linkll ,
%     pdf.save.linkur ,
%     pdf.llx         ,
%     pdf.lly         ,
%     pdf.urx         ,
%     pdf.ury
%   }
%   Functions for marking the limits of an annotation/link, plus drawing the
%   border. We separate links for generic annotations to support adding a
%   margin and setting a minimal size.
%    \begin{macrocode}
/pdf.rect
  { /Rect [ pdf.llx pdf.lly pdf.urx pdf.ury ] } def
/pdf.save.ll
  {
    currentpoint
    /pdf.lly exch def
    /pdf.llx exch def
  }
    def
/pdf.save.ur
  {
    currentpoint
    /pdf.ury exch def
    /pdf.urx exch def
  }
    def
/pdf.save.linkll
  {
    currentpoint
    pdf.linkmargin add
    pdf.linkdp.pad add
    /pdf.lly exch def
    pdf.linkmargin sub
    /pdf.llx exch def
  }
    def
/pdf.save.linkur
  {
    currentpoint
    pdf.linkmargin sub
    pdf.linkht.pad sub
    /pdf.ury exch def
    pdf.linkmargin add
    /pdf.urx exch def
  }
    def
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
%   {
%     pdf.dest.anchor ,
%     pdf.dest.x      ,
%     pdf.dest.y      ,
%     pdf.dest.point  ,
%     pdf.dest2device ,
%     pdf.dev.x       ,
%     pdf.dev.y       ,
%     pdf.tmpa        ,
%     pdf.tmpb        ,
%     pdf.tmpc        ,
%     pdf.tmpd
%   }
%   For finding the anchor point of a destination link. We make the use case
%   a separate function as it comes up a lot, and as this makes it easier to
%   adjust if we need additional effects. We also need a more complex approach
%   to convert a coordinate pair correctly when defining a rectangle: this
%   can otherwise be out when using a landscape page. (Thanks to Alexander
%   Grahn for the approach here.)
%    \begin{macrocode}
/pdf.dest.anchor
  {
    currentpoint exch
    pdf.dvi.pt 72 add
    /pdf.dest.x exch def
    pdf.dvi.pt
    vsize 72 sub exch sub
    /pdf.dest.y exch def
  }
    def
/pdf.dest.point
  { pdf.dest.x pdf.dest.y } def
/pdf.dest2device
  {
    /pdf.dest.y exch def
    /pdf.dest.x exch def
    matrix currentmatrix
    matrix defaultmatrix
    matrix invertmatrix
    matrix concatmatrix
    cvx exec
    /pdf.dev.y exch def
    /pdf.dev.x exch def
    /pdf.tmpd exch def
    /pdf.tmpc exch def
    /pdf.tmpb exch def
    /pdf.tmpa exch def
    pdf.dest.x pdf.tmpa mul
      pdf.dest.y pdf.tmpc mul add
      pdf.dev.x add
    pdf.dest.x pdf.tmpb mul
      pdf.dest.y pdf.tmpd mul add
      pdf.dev.y add
  }
    def
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
%   {
%     pdf.bordertracking          ,
%     pdf.bordertracking.begin    ,
%     pdf.bordertracking.end      ,
%     pdf.leftboundary            ,
%     pdf.rightboundary           ,
%     pdf.brokenlink.rect         ,
%     pdf.brokenlink.skip         ,
%     pdf.brokenlink.dict         ,
%     pdf.bordertracking.endpage  ,
%     pdf.bordertracking.continue ,
%     pdf.originx                 ,
%     pdf.originy
%   }
%    To know where a breakable link can go, we need to track the boundary
%    rectangle. That can be done by hooking into |a| and |x| operations:
%    those names have to be retained. The boundary is stored at the end of
%    the operation. Special effort is needed at the start and end of pages
%    (or rather galleys), such that everything works properly.
%    \begin{macrocode}
/pdf.bordertracking false def
/pdf.bordertracking.begin
  {
    SDict /pdf.bordertracking true put
    SDict /pdf.leftboundary undef
    SDict /pdf.rightboundary undef
    /a where
      {
        /a
          {
            currentpoint pop
            SDict /pdf.rightboundary known dup
              {
                SDict /pdf.rightboundary get 2 index lt
                  { not }
                if
              }
            if
              { pop }
              { SDict exch /pdf.rightboundary exch put }
            ifelse
            moveto
            currentpoint pop
            SDict /pdf.leftboundary known dup
              {
                SDict /pdf.leftboundary get 2 index gt
                  { not }
                if
              }
            if
              { pop }
              { SDict exch /pdf.leftboundary exch put }
            ifelse
          }
        put
      }
    if
  }
    def
/pdf.bordertracking.end
  {
    /a where { /a { moveto } put } if
    /x where { /x { 0 exch rmoveto } put } if
    SDict /pdf.leftboundary known
      { pdf.outerbox 0 pdf.leftboundary put }
    if
    SDict /pdf.rightboundary known
      { pdf.outerbox 2 pdf.rightboundary put }
    if
    SDict /pdf.bordertracking false put
  }
    def
  /pdf.bordertracking.endpage
{
  pdf.bordertracking
    {
      pdf.bordertracking.end
      true setglobal
      pdf.globaldict
        /pdf.brokenlink.rect [ pdf.outerbox aload pop ] put
      pdf.globaldict
        /pdf.brokenlink.skip pdf.baselineskip put
      pdf.globaldict
        /pdf.brokenlink.dict
          pdf.link.dict pdf.cvs put
      false setglobal
      mark pdf.link.dict cvx exec /Rect
        [
          pdf.llx
          pdf.lly
          pdf.outerbox 2 get pdf.linkmargin add
          currentpoint exch pop
          pdf.outerbox pdf.rect.ht sub pdf.linkmargin sub
        ]
      /ANN pdf.pdfmark
    }
  if
}
  def
/pdf.bordertracking.continue
  {
    /pdf.link.dict pdf.globaldict
      /pdf.brokenlink.dict get def
    /pdf.outerbox pdf.globaldict
      /pdf.brokenlink.rect get def
    /pdf.baselineskip pdf.globaldict
      /pdf.brokenlink.skip get def
    pdf.globaldict dup dup
    /pdf.brokenlink.dict undef
    /pdf.brokenlink.skip undef
    /pdf.brokenlink.rect undef
    currentpoint
    /pdf.originy exch def
    /pdf.originx exch def
    /a where
      {
        /a
          {
            moveto
            SDict
            begin
            currentpoint pdf.originy ne exch
              pdf.originx ne or
              {
                pdf.save.linkll
                /pdf.lly
                  pdf.lly pdf.outerbox 1 get sub def
                pdf.bordertracking.begin
              }
            if
            end
          }
        put
      }
    if
    /x where
      {
        /x
          {
            0 exch rmoveto
            SDict
            begin
            currentpoint
            pdf.originy ne exch pdf.originx ne or
              {
                pdf.save.linkll
                /pdf.lly
                  pdf.lly pdf.outerbox 1 get sub def
                pdf.bordertracking.begin
              }
            if
            end
          }
        put
      }
    if
  }
    def
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
%   {
%     pdf.breaklink       ,
%     pdf.breaklink.write ,
%     pdf.count           ,
%     pdf.currentrect
%   }
%   Dealing with link breaking itself has multiple stage. The first step is to
%   find the |Rect| entry in the dictionary, looping over key--value pairs.
%   The first line is handled first, adjusting the rectangle to stay inside the
%   text area. The second phase is a loop over the height of the bulk of the
%   link area, done on the basis of a number of baselines. Finally, the end of
%   the link area is tidied up, again from the boundary of the text area.
%    \begin{macrocode}
/pdf.breaklink
  {
    pop
    counttomark 2 mod 0 eq
      {
        counttomark /pdf.count exch def
          {
            pdf.count 0 eq { exit } if
            counttomark 2 roll
            1 index /Rect eq
              {
                dup 4 array copy
                dup dup
                  1 get
                  pdf.outerbox pdf.rect.ht
                  pdf.linkmargin 2 mul add sub
                  3 exch put
                dup
                  pdf.outerbox 2 get
                  pdf.linkmargin add
                  2 exch put
                dup dup
                  3 get
                  pdf.outerbox pdf.rect.ht
                  pdf.linkmargin 2 mul add add
                  1 exch put
                /pdf.currentrect exch def
                pdf.breaklink.write
                  {
                    pdf.currentrect
                    dup
                      pdf.outerbox 0 get
                      pdf.linkmargin sub
                      0 exch put
                    dup
                      pdf.outerbox 2 get
                      pdf.linkmargin add
                      2 exch put
                    dup dup
                      1 get
                      pdf.baselineskip add
                      1 exch put
                    dup dup
                      3 get
                      pdf.baselineskip add
                      3 exch put
                    /pdf.currentrect exch def
                    pdf.breaklink.write
                  }
                1 index 3 get
                pdf.linkmargin 2 mul add
                pdf.outerbox pdf.rect.ht add
                2 index 1 get sub
                pdf.baselineskip div round cvi 1 sub
                  exch
                repeat
                pdf.currentrect
                dup
                  pdf.outerbox 0 get
                  pdf.linkmargin sub
                  0 exch put
                dup dup
                  1 get
                  pdf.baselineskip add
                  1 exch put
                dup dup
                  3 get
                  pdf.baselineskip add
                  3 exch put
                dup 2 index 2 get  2 exch put
                /pdf.currentrect exch def
                pdf.breaklink.write
                SDict /pdf.pdfmark.good false put
                exit
              }
              { pdf.count 2 sub /pdf.count exch def }
            ifelse
          }
        loop
      }
    if
    /ANN
  }
    def
/pdf.breaklink.write
  {
    counttomark 1 sub
    index /_objdef eq
      {
        counttomark -2 roll
        dup wcheck
          {
            readonly
            counttomark 2 roll
          }
          { pop pop }
        ifelse
      }
    if
    counttomark 1 add copy
    pop pdf.currentrect
    /ANN pdfmark
  }
    def
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
%   {
%     pdf.pdfmark      ,
%     pdf.pdfmark.good ,
%     pdf.outerbox     ,
%     pdf.baselineskip ,
%     pdf.pdfmark.dict
%   }
%   The business end of breaking links starts by hooking into |pdfmarks|.
%   Unlike \pkg{hypdvips}, we avoid altering any links we have not created
%   by using a copy of the core |pdfmarks| function. Only mark types which
%   are known are altered. At present, this is purely |ANN| marks, which are
%   measured relative to the size of the baseline skip. If they are
%   more than one apparent line high, breaking is applied.
%    \begin{macrocode}
/pdf.pdfmark
  {
    SDict /pdf.pdfmark.good true put
    dup /ANN eq
      {
        pdf.pdfmark.store
        pdf.pdfmark.dict
          begin
            Subtype /Link eq
            currentdict /Rect known and
            SDict /pdf.outerbox known and
            SDict /pdf.baselineskip known and
              {
                Rect 3 get
                pdf.linkmargin 2 mul add
                pdf.outerbox pdf.rect.ht add
                Rect 1 get sub
                pdf.baselineskip div round cvi 0 gt
                  { pdf.breaklink }
                if
              }
            if
          end
        SDict /pdf.outerbox undef
        SDict /pdf.baselineskip undef
        currentdict /pdf.pdfmark.dict undef
      }
    if
    pdf.pdfmark.good
      { pdfmark }
      { cleartomark }
    ifelse
  }
    def
/pdf.pdfmark.store
  {
    /pdf.pdfmark.dict 65534 dict def
    counttomark 1 add copy
    pop
      {
        dup mark eq
          {
            pop
            exit
          }
          {
            pdf.pdfmark.dict
            begin def end
          }
        ifelse
      }
    loop
}
  def
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</dvips&header>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
