[Tex/LaTex] How to clip a TeX box using low-level PS commands

boxesoutput-driverpath-clippingpostscript

I like to clip an arbitrary TeX box (\hbox) but without using larger graphic related packages like PGF/TikZ or PSTricks. This is intended to be used in packages which should not force the user to also load one of that packages.

Here I mean the same as the clip option (together with viewport or trim) of \includegraphics does: some outer material is not displayed any longer. I just want to apply this to a TeX box. I realize that this is output driver dependent. I found a pdftex solution already which uses PDF commands.
I'm now looking for a DVI/PS solution, i.e. some \special instructions which clip a Tex box. Any hints about other drivers are very welcome.

I need this for two of my packages gincltex and adjustbox, which both extend the features of \includegraphics to arbitrary TeX boxes.
What I do at the moment I simply use PGF for this: I place the content in a node and clip the picture accordantly. Because the content is boxed I can calculate the required size beforehand.

So basically I'm looking for the low level definition of a macro like:

\@clipbox{<llx>}{<lly>}{<urx>}{<ury>}{<boxnumber>}

which accepts a previously saved box and clips the four amounts from that box (ll = lower left, ur = upper right).


For pdftex I found the related code in $TEXMF/tex/latex/pdftex-def/pdftex.def (driver file for graphics) which tells me trim the box first using normal TeX commands (i.e. to \lower and \hskip the lower left margin and to limit the box height (\ht) and width (\wd) dependent on the upper right corner. The depth \dp is there set to zero for images, but I have to handle that differently.). After that it uses the following low level macros to actually clip the box:

\pdfxform<boxnumber>
\pdfrefxform\pdflastxform

I imagine that for DVI-mode (dvps driver) I need a PostScript \special command to do the same. How would such a command look like? Any ideas about a dvipdf solution?

Best Answer

Both Herbert and Alexander have offered solutions for dvips. Here, I'm taking inspiration from those answers plus the more convenient approach available in pdfTeX, plus a modified version of the pgf method for XeTeX, and combining into a single approach. First, note that I'm assuming e-TeX and also using a somewhat 'LaTeX3-like' programming approach. I've also shared as much code as possible.

I'll use a single example but add comments along the way. First, as there are packages for the driver detections I'll load those.

\documentclass{article}
\usepackage{ifpdf,ifxetex}
\makeatletter

The main internal macro takes five arguments: the box to modify, then four dimension expressions to be clipped off the left, bottom, right and top respectively. You could also set up a similar approach to take a final size. The idea here is that the baseline is respected if possible, so there is a bit of care needed with the vertical placement so that the content only moves down while there is some depth available.

\protected\long\def\box@clip#1#2#3#4#5%
  {%
    \ht#1\dimexpr\ht#1 - \dimexpr#5\relax\relax
    \ifdim\dp#1>\dimexpr#3\relax
      \dp#1\dimexpr\dp#1 - \dimexpr#3\relax\relax
    \else
      \setbox#1=\hbox
        {\lower\dimexpr\dimexpr#3\relax - \dp#1\relax\box#1}%
      \dp#1\z@
    \fi
    \wd#1\dimexpr\wd#1-\dimexpr#4\relax\relax
    \setbox#1=\hbox
      {%
        \hskip-\dimexpr#2\relax
        \box#1%
      }%
    \ifxetex
      \expandafter\box@clip@xdvipdfmx
    \else
      \ifpdf
        \expandafter\expandafter\expandafter\box@clip@pdfmode
      \else
        \expandafter\expandafter\expandafter\box@clip@dvips
      \fi
    \fi
    #1%
  }

For each driver supported, there is an auxiliary. First, for dvips this is Herbert's method slightly altered (pgf makes things very complex):

\protected\long\def\box@clip@dvips#1%
  {%
    \setbox#1=\hbox
      {%
        \special
          {%
            ps:
              /mtrxc matrix currentmatrix def 
              currentpoint gsave
              translate
              Resolution 72 div VResolution 72 div scale
              0 -\to@bp{\dp#1} neg  \to@bp{\wd#1} \to@bp{\ht#1 + \dp#1} neg
              rectclip
              mtrxc setmatrix 
          }%
        \box#1%
        \special{ps: grestore }%
      }%
  }

As I said, pdfTeX makes life very easy :-)

\protected\long\def\box@clip@pdfmode#1%
  {%
    \pdfxform#1%
    \setbox#1=\hbox{\pdfrefxform\pdflastxform}%
  }

XeTeX is possibly the most complex one to tackle. The pgf approach is used, but here I've removed a lot of unnecessary transformations. After reading the dvipdfmx manual, it becomes clear that the best approach is as follows

\protected\long\def\box@clip@xdvipdfmx#1%
  {%
    \setbox#1=\hbox
      {%

The first special saves the current point and starts a new 'graphics level'. Using the bcontent operation saves the current location automatically.

        \special{pdf:bcontent }%

Draw a rectangle the size of the modified box: in pgf this is done using the lower-level m, l and h operations, but there is no gain in working that way. This will be located at 'current point' TeX-wise.

          \special
            {%
              pdf:literal direct 
                0 -\to@bp{\dp#1} \to@bp{\wd#1} \to@bp{\ht#1 + \dp#1} re 
            }%

The W operation specifies a clip, and n finalises the path without any output (it's a 'no-op').

          \special{pdf:literal direct W }%
          \special{pdf:literal direct n }%

Insert the box and tidy up.

                \box#1%
              \special{pdf:econtent }%
      }%
  }

A simple conversion taken from Is there a command to convert cm to bp?

\long\def\to@bp#1{\strip@pt\dimexpr0.99626\dimexpr#1\relax\relax}

Wrap everything up in a user macro and finish the code block

\protected\long\def\boxclip#1#2#3#4#5{\box@clip#1{#2}{#3}{#4}{#5}}
\makeatother

\newbox\testbox

Now for some testing.

\begin{document}
\setbox\testbox=\hbox{Some test text with (g)}
\boxclip{\testbox}{10 pt}{2pt}{5pt}{2 pt}

\noindent\box\testbox{}

\end{document}

(I'll be adding this to LaTeX3 now I know how it works!) If you want to see the effect of various parts of the XeTeX code, comment out the W line to turn off the clipping. You can also replace the n operations by s so that you get a box where the clipping path is.


In earlier versions of the answer, for XeTeX I used the content q operation to save the current location, but this requires a series of manipulations to get the clip path and the box insert to line up. Using the bcontent ... econtent pair is much clearer.


While using the XForm implementation for pdfTeX is convenient, in a case where code is to be shared between branches an alternative approach is possible. (The above could be viewed as an abuse of the XForm object system in any case).

\protected\long\def\box@clip@pdfmode#1%
  {%
    \setbox#1=\hbox
      {%
        \pdfsave
          \pdfliteral direct
            {%
              0 -\to@bp{\dp#1} \to@bp{\wd#1} \to@bp{\ht#1 + \dp#1} re W n
            }%
          \hbox to 0pt{\copy#1\hss}%
        \pdfrestore
        \hskip \wd#1
      }%
  }

The zero width box here is used to keep placement correct in the \pdfsave/\pdfrestore pair (which perform the same task as the bcontent/econtent pair for XeTeX but which should be places in the same output position.)

Related Question