        psfrag.sty and psfrag.pro^{∗}

Michael Grant, David Carlisle, and Craig Barratt

               psfrag@rascals.stanford.edu

1997/01/22

1 psfrag.sty

1 ⟨∗package⟩

1.1 Intialization

 2 \newif\ifpfg@compat
 3 \newif\ifpfg@prepass
 4 \newif\ifpfg@debug \pfg@debugfalse
 5 \if@compatibility
 6 \pfg@compattrue\pfg@prepasstrue
 7 \else
 8 \pfg@compatfalse\pfg@prepassfalse
 9 \fi
10 \def\psfragscanon{\pfg@prepasstrue}
11 \def\psfragscanoff{\pfg@prepassfalse}
12 \def\psfragdebugon{\pfg@debugtrue}
13 \def\psfragdebugoff{\pfg@debugfalse}
14 \DeclareOption{scanall}{\pfg@prepasstrue}
15 \DeclareOption{2emode}{\pfg@compatfalse\pfg@prepassfalse}
16 \DeclareOption{209mode}{\pfg@compattrue\pfg@prepasstrue}
17 \DeclareOption{debugshow}{\pfg@debugtrue
18  \PassOptionsToPackage\CurrentOption{graphics}}
19 \DeclareOption*{\PassOptionsToPackage\CurrentOption{graphics}}
20 \ProcessOptions

psfrag.sty requires the graphics package and the support file psfrag.pro. (epsf.sty can still be used,but graphics package is still used for internal purposes.) To load psfrag.pro in a portable way, we provide and use the \Gin@PS@file@header command, which the graphics will eventually provide for us.

21 \RequirePackage{graphics}
22 \providecommand\Gin@PS@file@header[1]{\AtBeginDvi{\special{header=#1}}}
23 \Gin@PS@file@header{psfrag.pro}

\pfg@orient In order to properly handle all cases of rotation, scaling, etc. that PSfrag may encounter, it is necessary that we know a bit about the transformations that the DVI to PS driver uses itself. Specifically, we need to know what transformations, if any, the DVI to PS driver appends to PostScript’s defaultmatrix before it starts to typeset text. dvips, for example, reverses the orientation of the y-axis. Many other drivers do nothing. However, until we can find a portable way to make this determination in PostScript itself, we have to supply that information here.

∗This file has version number v3.03, last revised 1997/01/22.

24 \def\pfg@orient{1}
25 \def\@tempa{dvips.def}\ifx\Gin@driver\@tempa\def\pfg@orient{-1}\fi%
26 \def\@tempa{textures.def}\ifx\Gin@driver\@tempa\def\pfg@orient{-1}\fi%

1.2 Interface to the graphics and epsf packages

\pfg@pcount Counts how many fragments are active in the current figure.

\pfg@scount Counts how many replacements strings are active in the current figure. Since it is possible for a single string to accept multiple replacements, \pfg@scount ≤

\pfg@pcount.
27 \newcount\pfg@pcount \pfg@pcount=\z@
28 \newcount\pfg@scount \pfg@scount=\z@

\epsfbox Here we intercept calls to \epsfbox and wrap them with the PSfrag processing. We need to differentiate between a call to the classic epsf.sty macros and calls to a wrapper around the graphics package; so \ifpfg@epsf is used to insure that our functions are called only once. (These wrappers were at one time distributed with psfrag, but they are no longer needed.)

29 \let\pfg@epsfbox\epsfbox
30 \newif\ifpfg@epsf\pfg@epsffalse
31 \def\epsfbox#1{%
32  \begingroup\pfg@epsftrue
33  \ifpfg@prepass\pfg@scan{#1}\fi
34  \ifnum\pfg@scount>\z@\pfg@hidestart\fi
35  \pfg@epsfbox{#1}%
36  \ifnum\pfg@scount>\z@\pfg@hideend\fi
37  \endgroup}

\Ginclude@eps We need to override this function from the graphics package in a similar fashion

to \epsfbox.
38 \let\pfg@Ginclude@eps\Ginclude@eps
39 \def\Ginclude@eps#1{%
40  \ifpfg@epsf\else
41  \begingroup\ifpfg@prepass\pfg@scan{#1}\fi
42  \ifnum\pfg@scount>\z@\pfg@hidestart\fi
43  \fi
44  \pfg@Ginclude@eps{#1}%
45  \ifpfg@epsf\else
46  \ifnum\pfg@scount>\z@\pfg@hideend\fi
47  \endgroup
48  \fi}

psfrags Defines a dummy environment to delimit the scope of PSfrag replacements.

49 \newenvironment{psfrags}{\ignorespaces}{\global\@ignoretrue}

\psfragspecial This macro is no longer supported.

50 \ifpfg@compat
51 \def\psfragspecial#1#2#3#4#5#6{%
52 \PackageError{PSfrag}%
53  {\string\psfragspecial no longer implemented.}{}}
54 \fi

\psfrag {⟨tag⟩}[⟨posn⟩][⟨psposn⟩][⟨scl⟩][⟨rot⟩] The main macro \psfrag must be defined specially in order to handle its odd combination of optional and required arguments. In addition, catcode tricks (thanks to David Carlisle and others) allow special characters to be used inside the tag text. It also replaces spaces with their octal equivalents to prevent drivers like dvips from breaking one in the middle.

55 \newif\ifpfg@star
56 \def\psfrag{\@ifstar{\pfg@startrue\@psfraga}{\pfg@starfalse\@psfraga}}
57 \def\@psfraga{\begingroup
58 \@makeother\"\@makeother\*\@makeother\!\@makeother\~%
59 \@makeother\:\@makeother\\\@makeother\%\@makeother\#%
60 \@makeother\ \@psfragb}
61 \ifpfg@compat
62 \def\@psfragb#1{\xdef\@gtempa{#1}\endgroup
63  \@ifnextchar[{\@psfragc{\@gtempa}}%
64  {\@psfrag{\@gtempa}[bl][bl][1][0]}}
65 \def\@psfragc#1[#2]{\@ifnextchar [{\@psfragd{#1}[#2]}%
66  {\@psfrag{#1}[#2][bl][1][0]}}
67 \else
68 \def\@psfragb#1{\xdef\@gtempa{#1}\endgroup
69  \@ifnextchar [{\@psfragc{\@gtempa}}%
70  {\@psfrag{\@gtempa}[Bl][Bl][1][0]}}
71 \def\@psfragc#1[#2]{\@ifnextchar [{\@psfragd{#1}[#2]}%
72  {\@psfrag{#1}[#2][Bl][1][0]}}
73 \fi
74 \def\@psfragd#1[#2][#3]{\@ifnextchar [{\@psfrage{#1}[#2][#3]}%
75  {\@psfrag{#1}[#2][#3][1][0]}}
76 \def\@psfrage#1[#2][#3][#4]{\@ifnextchar [{\@psfrag{#1}[#2][#3][#4]}%
77  {\@psfrag{#1}[#2][#3][#4][0]}}
\@psfrag  {⟨tag⟩}

Now that we have all of the arguments, we need to check to check to see if the string has been used yet; if not, increment the string count and create a place for the new string. Then pass the string number on down the line to \@@psfrag.

78 \def\pfg@newstring#1.#2{%
79  \@nameedef{pfg@using@#2}{#1}%
80  \pfg@starfalse\advance\pfg@scount\@ne}
81 \def\@psfrag#1{%
82  \@ifundefined{pfg@using@#1}%
83  {\expandafter\pfg@newstring\the\pfg@scount.{#1}}%
84  {}%
85  \edef\@tempa{\@nameuse{pfg@using@#1}}%
86  \expandafter\@@psfrag\@tempa.{#1}}

\@@psfrag {⟨tag-key⟩}.{⟨tag⟩} \@@psfrag passes along the current replacement number to to \@@@psfrag, which is doing all of the dirty work.

 87 \def\@@psfrag{\expandafter\@@@psfrag\the\pfg@pcount.}

\@@@psfrag {⟨repl-key⟩}.{⟨tag-key⟩}.{⟨tag⟩}[⟨posn⟩] [⟨psposn⟩][⟨scl⟩][⟨rot⟩][⟨repl⟩]

\pfg@align  {⟨tag-key⟩}.
\pfg@place  {⟨repl-key⟩}.

\@@@psfrag, armed with numeric keys for the tag string and the replacement, creates a \pfg@place procedure call for this fragment and appends the control point, scale, and rotation information to the proper tag’s list, found in

 \pfg@align<tag-key>.
 88 \def\@nameedef#1#2{\expandafter\edef\csname #1\endcsname{#2}}
 89 \def\@@@psfrag#1.#2.#3[#4][#5][#6][#7]#8{%
 90  \advance\pfg@pcount\@ne
 91  \ifpfg@star\else\@nameedef{pfg@align@#2}{(#3)[}\fi
 92  \@nameedef{pfg@align@#2}{\@nameuse{pfg@align@#2}[#1(#5)#6 #7]}%
 93  \@namedef{pfg@place@#1}{\pfg@@place{#1}{#4}{#8}}\ignorespaces}
 94 \def\pfg@align#1.{\@nameuse{pfg@align@#1}}
 95 \def\pfg@place#1.{\@nameuse{pfg@place@#1}}

1.3 Fragment processing

\pfg@hidestart This procedure provides PostScript with a list of the strings that PSfrag would like to replace, and the control points, scales, and rotations of each one. The PostScript code in psfrag.pro will use this information to construct transformation matrices for each replacement to move them into proper position. .

 96 \def\pfg@hidestart{%
 97  \def\pfg@{}\count@=\z@\loop\ifnum\count@<\pfg@scount
 98  \toks@=\expandafter{\expandafter\pfg@align\the\count@.}%
 99  \edef\pfg@{\pfg@\the\toks@]}%
100  \advance\count@ by\@ne\repeat
101  \Gin@PS@raw{/PSfrag where{pop\pfg@\the\pfg@scount\space
102  \ifpfg@debug1\else0\fi\space\pfg@orient/Begin
103  PSfrag}{userdict /PSfrag{pop}put}ifelse}}

\pfg@hideend Now that the EPS file has been loaded, and (therefore) all of the replacement positions have been determined, move each replacement to its proper position by calling the \pfg@place procedure for each one. Except for the calls to \Gin@PS@raw, it looks like we’re building a vertical list of the replacements: that’s the idea, because we want it to look decent when viewed by a DVI viewer.

104 \def\pfg@hideend{%
105  \Gin@PS@raw{/End PSfrag}%
106  \vbox to\z@{\vss
107  \Gin@PS@raw{/Hide PSfrag}%
108  \hbox to\z@{\hss \raisebox{\depth}{%
109  \underline{PSfrag replacements}}}%
110  \Gin@PS@raw{/Unhide PSfrag}%
111  \count@=\z@\loop\ifnum\count@<\pfg@pcount
112  \expandafter\pfg@place\the\count@.%
113  \advance\count@ by\@ne\repeat}}

\pfg@@place {⟨repl-key⟩}{⟨posn⟩}{⟨replacement⟩} The following macro does the dirty work. Its job is to insert the appropriate code so that the TEX replacement is transformed to its requested location; and, if desired, repeated for as many times as the PostScript tag text appeared in the figure.

It performs this magic by first surrounding the TEX-produced code in curly braces, so we can defer its execution while we figure out where to place it. Then, we call the Place routine in psfrag.pro, which takes {⟨repl-key⟩} and provides the proper coordinate transformations.

We’re allowed to reassign these dimension registers because we are always inside a group when we use them. This technique is taken from graphics.sty...

114 \let\pfg@dp\leftmargini\let\pfg@wd\leftmarginii
115 \let\pfg@dx\leftmarginiii\let\pfg@dy\leftmarginiv

We need to stuff the curly braces into macros.

116 \edef\pfg@bchar{ \string{ }
117 \edef\pfg@echar{ \string} }
118 \def\pfg@@place#1#2#3{\begingroup

Typeset the replacement into a box and determine its dimensions.

119  \sbox\z@{#3}%
120  \dimen@=\ht\z@ \advance\dimen@ by\dp\z@%

Determine the desired alignment of the TEX box, and the glue that will be needed in order to achieve this alignment.

121  \pfg@wd=0.5\wd\z@\pfg@dp=0.5\dimen@
122  \pfg@dx=\pfg@wd\pfg@dy=\pfg@dp
123  \@tfor\@tempa:=#2\do{%
124  \if l\@tempa \pfg@dx=\z@    \pfg@wd=\wd\z@ \else
125  \if r\@tempa \pfg@dx=\wd\z@ \pfg@wd=\z@    \else
126  \if b\@tempa \pfg@dy=\z@    \pfg@dp=\z@    \else
127  \if t\@tempa \pfg@dy=\dimen@\pfg@dp=\dimen@\else
128  \if B\@tempa \pfg@dy=\dp\z@ \pfg@dp=\dp\z@ \fi\fi\fi\fi\fi}%

Create a zero-area box with the desired PSfrag replacement text typeset inside of it (and aligned with the glue). This is actually the box that will be moved, rotated, and/or scaled.

129  \setbox\z@\hbox to\z@{\hskip-\pfg@dx\box\z@\hss}%
130  \setbox\z@\vbox to\z@{\vss\box\z@\vskip-\pfg@dy}%

Typeset the box. The inner, zero-size box is the one we place with the PostScript code, and the outer, full-height, right- justified box is the one that we use to stack up the preview list.

131  \vbox to\dimen@{\vss\hbox to\z@{\hss
132  \Gin@PS@raw{\pfg@bchar}\box\z@
133  \Gin@PS@raw{\pfg@echar#1/Place PSfrag}%
134  \hskip\pfg@wd}\vskip\pfg@dp}%
135  \endgroup}

136 ⟨/package⟩ 137 ⟨∗filepro⟩

2 The PostScript library, psfrag.pro

This code must perform two functions: 1) determine and save the alignment point, rotation, and scaling for each piece of PostScript text that is going to serve as a PSfrag tag; and 2) use that information to properly transform the coordinate system for each TEX replacement.

The only two symbols that we define globally are PSfragLib, the library of support routines, PSfragDict, the dictionary containing the “magic” show commands, and PSfrag, the gateway from the global namespace to PSfragLib. This technique minimizes the possibility that our procedure names will conflict with those defined by the PostScript driver or the figure itself.

138 userdict begin
139 /PSfragLib 90 dict def
140 /PSfragDict 6 dict def
141 /PSfrag { PSfragLib begin load exec end } bind def
142 end

PSfragLib PSfragLib is the dictionary with the meaty stuff. We’re also defining some useful abbreviations. Of note is the OE command (“outside exec”), which executes some code after removing PSfragLib from the stack (to insure that there are no collisions with PSfragLib’s internal names.

143 PSfragLib begin
144 /RO /readonly      load def
145 /CP /currentpoint  load def
146 /CM /currentmatrix load def
147 /B { bind RO def } bind def
148 /X { exch def } B
149 /MD { { X } forall } B
150 /OE { end exec PSfragLib begin } B
151 /tstr 8 string def
152 /islev2 { languagelevel } stopped { false } { 2 ge } ifelse def

Allocate some space for matrices that we will use frequently. srcFM contains a matrix which describes the difference between PostScript’s defaultmatrix and the transformation that the DVI-to-PS driver’s “default matrix.” This information is crucial to the proper placing of the PSfrag replacements.

153 [ /tM /srcM /dstM /dM /idM /srcFM /dstFM ] { matrix def } forall
154 dM defaultmatrix RO idM invertmatrix RO pop
155 srcFM identmatrix pop

Hide Unhide Surrounding a (fairly) arbitrary piece of PostScript code with calls to Hide and Unhide should render it invisible. PSfrag uses these calls to hide unused replacements. Note that we are assuming that the DVI-to-PS driver isn’t relying on any of the information that will be wiped out by the grestore, except for the

currentpoint.
156 /Hide { gsave { CP } stopped not newpath clip { moveto } if } B
157 /Unhide { { CP } stopped not grestore { moveto } if } B

setrepl getrepl These macros are defined differently for Level 1 and Level 2 PostScript. setrepl accepts an array of N elements, followed by the length N, and store them in a global variable (or the equivalent) for later use. getrepl restores that exact information to the stack. This is used to store the replacement information in a persistent fashion.

158 /setrepl islev2 {{ /glob currentglobal def true setglobal array astore
159  globaldict exch /PSfrags exch put glob setglobal }}
160  {{ array astore /PSfrags X }} ifelse B
161 /getrepl islev2 {{ globaldict /PSfrags get aload length }}
162  {{ PSfrags aload length }} ifelse B

convert This routine takes a string and replaces every character whose ASCII code is less than 32 with a space. This makes the replacement dictionary robust to multipleline tags, etc.

163 /convert {
164 /src X src length string
165 /c 0 def src length {
166  dup c src c get dup 32 lt { pop 32 } if put /c c 1 add def
167 } repeat

168 } B

Begin PSfrag calls this routine once per figure, once it has placed on the stack all of each tag’s alignment, rotation, and scaling information. This information is collected into a dictionary: each tag has an entry which is an array of [ repl-key, (psposn), scl, rot ] quads. Then it saves the current transformation information in a form ready for /replace to use.

169 /Begin {
170  /saver save def
171  srcFM exch 3 exch put
172  0 ne /debugMode X 0 setrepl
173  dup /S exch dict def { S 3 1 roll exch convert exch put } repeat
174  srcM CM dup invertmatrix pop
175  mark { currentdict { end } stopped { pop exit } if } loop
176  PSfragDict counttomark { begin } repeat pop

177 } B

End End converts the alignment information that has been collected into a dictionary: each repl-key that was encountered in Begin has an entry which is an array of transformation matrices.

178 /End {
179  mark { currentdict end dup PSfragDict eq { pop exit } if } loop
180  counttomark { begin } repeat pop
181  getrepl saver restore
182  7 idiv dup /S exch dict def {
183  6 array astore /mtrx X tstr cvs /K X
184  S K [ S K known { S K get aload pop } if mtrx ] put
185  } repeat

186 } B

Place This macro is called once for each PSfrag replacement. It checks to see if the replacement’s tag was actually encountered in the PS figure; if not, it surrounds the code in a “clip all” context and executes it. This makes it invisible, as requested, but still allows it to run so that any side effects that the code may have will take place.

If the replacement’s tag was encountered, however, it retrieves the transformation matrix that was calculated for it; it prepends this calculation to the current (TEX) transformation matrix (some tricky math to wade through there!), and then draws the replacement.

If there are multiple copies of the replacement to be laid down, this will do it, surrounding all but the last replacement in a save-restore pair. This saverestore pair may slow things down, but theoretically it is necessary, because the procedure might modify other variables in the PostScript namespace. We need to be sure that those modifications do not “accumulate”.

For multiple replacements to work, the code that PSfrag surrounds with curly braces must leave the stack the same way it found it. So far, we have confirmed this fact for dvips and DVIPSONE.

187 /Place {
188  tstr cvs /K X
189  S K known {
190  bind /proc X tM CM pop
191  CP /cY X /cX X
192  0 0 transform idtransform neg /aY X neg /aX X
193  S K get dup length /maxiter X
194  /iter 1 def {
195  iter maxiter ne { /saver save def } if
196  tM setmatrix aX aY translate
197  [ exch aload pop idtransform ] concat
198  cX neg cY neg translate cX cY moveto
199  /proc load OE
200  iter maxiter ne { saver restore /iter iter 1 add def } if
201  } forall
202  /noXY { CP /cY X /cX X } stopped def
203  tM setmatrix noXY { newpath } { cX cY moveto } ifelse

204 } {

205  Hide OE Unhide
206  } ifelse

207 } B

normalize This function accepts three numbers, x, y, and s, and returns sx/\sqrt{x^{2} + y^{2}} and sy/\sqrt{x^{2} + y^{2}}.

208 /normalize {
209  2 index dup mul 2 index dup mul add sqrt div
210  dup 4 -1 roll exch mul 3 1 roll mul

211 } B

replace This routine is called for every string in the string list. It must determine the translation, scaling, and rotation necessary to move a piece of TEXcode to the proper orientation. It stores in replArray, for each alignment/scale/rotation combination, a transformation matrix that achieves this.

212 /replace {
213  aload pop MD

Trace out the text and determine its bounding box. We need to temporarily revert to an identity transformation matrix, so that we can be sure to get the tightest bounding box possible. If the text is rotated, the bounding box that PostScript produces with the pathbbox command is not optimal.

We also need to save the position of the point following the text placement, and move to that position when we’ve finished, to fool the PS interpreter into thinking we actually drew that text.

214  CP /bY X /lX X gsave initmatrix
215  str stringwidth abs exch abs add dup 0 eq
216  { pop } { 360 exch div dup scale } ifelse
217  lX neg bY neg translate newpath lX bY moveto
218  str { /ch X ( ) dup 0 ch put false charpath ch Kproc } forall
219  flattenpath pathbbox [ /uY /uX /lY /lX ] MD
220  CP grestore moveto

If the FontMatrix shows that the font is draw in the opposite X or opposite Y direction, then the X or Y coordinates of the bounding box, respectively, need to be swapped. This actually occurs quite often. Here we also determine the center of the box.

221  currentfont /FontMatrix get dstFM copy dup
222  0 get 0 lt { uX lX /uX X /lX X } if
223  3 get 0 lt { uY lY /uY X /lY X } if
224  /cX uX lX add 0.5 mul def
225  /cY uY lY add 0.5 mul def

If debug mode has been enabled, we draw the bounding box, the baseline, and the center lines of the text.

226  debugMode { gsave 0 setgray 1 setlinewidth
227  lX lY moveto lX uY lineto uX uY lineto uX lY lineto closepath
228  lX bY moveto uX bY lineto lX cY moveto uX cY lineto
229  cX lY moveto cX uY lineto stroke
230  grestore } if

Add each replacement’s alignment to replArray.

231  dstFM dup invertmatrix dstM CM srcM
232  2 { dstM concatmatrix } repeat pop
233  getrepl /temp X
234  S str convert get {

Retrieve the replacement information

235  aload pop [ /rot /scl /loc /K ] MD

Determine the PostScript alignment point.

236  /aX cX def /aY cY def
237  loc {
238  dup 66  eq { /aY bY def } { % B
239  dup 98  eq { /aY lY def } { % b
240  dup 108 eq { /aX lX def } { % l
241  dup 114 eq { /aX uX def } { % r
242  dup 116 eq { /aY uY def }   % t
243  if } ifelse } ifelse } ifelse } ifelse pop
244  } forall

Store the replacement key, and the transformation information, in replArray. These transformations are appended to the CTM encountered when Place is run. Indeed, this and Place are the two most difficult pieces of code to understand.

245  K srcFM rot tM rotate dstM
246  2 { tM concatmatrix } repeat aload pop pop pop
247  2 { scl normalize 4 2 roll } repeat
248  aX aY transform
249  /temp temp 7 add def
250  } forall
251  temp setrepl

252 } B

rs rks ras rws raws These macros intercept calls to their PostScript counterparts and execute replace in their stead if the argument string matches one in the replacement list.

253 /Rif {
254  S 3 index convert known { pop replace } { exch pop OE } ifelse

255 } B

256 /XA { bind [ /Kproc /str } B /XC { ] 2 array astore def } B
257 /xs   { pop } XA XC
258 /xks  { /kern load OE } XA /kern XC
259 /xas  { pop ax ay rmoveto } XA /ay /ax XC
260 /xws  { c eq { cx cy rmoveto } if } XA /c /cy /cx XC
261 /xaws { ax ay rmoveto c eq { cx cy rmoveto } if }
262  XA /ay /ax /c /cy /cx XC
263 /raws { xaws { awidthshow } Rif } B
264 /rws  { xws { widthshow } Rif } B
265 /rks  { xks { kshow } Rif } B
266 /ras  { xas { ashow } Rif } B
267 /rs   { xs { show } Rif } B

restore In Level 1 PostScript, this is necessary because global variables do not exist (and we need one!). Every call to restore wipes out any variables that were modified since the previous call to save. Unfortunately, this is not guaranteed to work, although it does work for dvips.

For Level 2, we can use global variables so this is not necessary. We make this distinction at print time, so a file that does not seem to work on a Level 1 printer for which it was originally intended should still work on a Level 2 printer without modification.

268 /rrs { getrepl dup 2 add -1 roll restore setrepl } B

Dict This dictionary is added to the stack just before the figure, so that its definitions of the show operators can substitute for the originals.

269 PSfragDict begin
270 islev2 not { /restore { /rrs PSfrag } B } if
271 /show       { /rs   PSfrag } B
272 /kshow      { /rks  PSfrag } B
273 /ashow      { /ras  PSfrag } B
274 /widthshow  { /rws  PSfrag } B
275 /awidthshow { /raws PSfrag } B
276 end PSfragDict RO pop
277 end

278 ⟨/filepro⟩

279 ⟨∗package⟩

3 The scanner

This code implements the TEX-based scanner that performs the work that the PSfrag Perl script used to perform. This is very similar to the code used to scan for bounding boxes in graphics.sty.

We need to read the string with two sets of catcodes: as ‘verbatim text’ for using in the first argument of \psfrag, and as normal TEX commands for using in the replacemnt text argument. The only way to do this sensibly in standard TEX is to write the string to a file and then read it back. So while reading the EPS file, the character \ will be ‘active’. Then the argument can be duplicated before writing this temp file, once with \ expanding to \\ (two catcode 12 tokens) and once to \ (one catcode 12 token). When this is read back, these tokens will be re-catcoded by the psfrag command.

280 \newwrite\pfg@temp

\pfg@scan The main command. #1 is the name of the file. It might be better to use the bounding box (‘.bb’) file name so that compressed files could work by you copying any lines with (\\tex… strings in them to the bb file before compressing. However, if you are doing that, you may as well just move the \psfrag commands to the main TEX file. So currently compression is not supported by this system.

281 \def\pfg@scan#1{\begingroup

First the standard making ‘safe’ of illegal characters.

282  \@tempcnta\z@
283  \loop
284  \ifnum\@tempcnta<\@xxxii
285  \catcode\@tempcnta12 \advance\@tempcnta\@ne
286  \repeat

Now normalise some catcodes we need. Most things are treated verbatim, but {} have their normal catcodes so \read will read multi-line \\tex expresions; and, as discussed above, \ is made active. The end of a line is treated as space. Originally this meant that multi-line \tex commands did not work, but with v3.02, the PS side of things normalises white space to ‘space’, as well so everyone is in agreement.

287  \let\do\@makeother\dospecials\catcode`\ 10 %
288  \catcode`\{=1\catcode`\}=2\catcode127=12 %
289  \catcode`\\=\active\catcode\endlinechar5 %

Open the scratch file. Don’t complain if it already exists as that is probably just a previous graphic, or a previous run. pfgguide.tex warns that any existing file of this name will be zapped, so: you’ve been warned.

290  \immediate\openout\pfg@temp=\jobname.pfg %

If the graphic file is not there, complain, else start reading it line by line. Look at each line with \pfg@find. Unlike the looking for %%BoundingBox where you can stop once you’ve found it, here you need to go to the end of the file looking for all the occurrences of \tex so if you give it megabytes of data, this may take a while.

291  \immediate\openin\@inputcheck=#1 %
292  \ifeof\@inputcheck
293  \PackageWarning{psfrag}{Could not scan #1...}\endgroup
294  \else
295  \message{<psfrag:#1>}%
296  \@tempswatrue
297  \loop
298  \ifeof\@inputcheck
299  \@tempswafalse
300  \else
301  \read\@inputcheck to\@tempa

302  \expandafter\pfg@find\@tempa{\@nil}%
303  \fi
304  \if@tempswa
305  \repeat
306  \closein\@inputcheck
307  \fi
308  \immediate\closeout\pfg@temp
309  \endgroup
310  \@input{\jobname.pfg}}

\pfg@find This command first looks for an explicit brace group { }. (one definitely exists as {\@nil} is added at the end as a ‘marker’).

311 \long\def\pfg@find#1#{\pfg@finda{#1}}

To enable an ‘active’ copy or \ to be accessed easily, use a special lowercase table while defining these macros.

312 \begingroup
313 \lccode`\~=`\\
314 \lowercase{\endgroup

\pfg@finda If the group found contained the \@nil marker stop, else start looking for (\\tex in the tokens before the group.

315 \long\def\pfg@finda#1#2{%
316 \def\@tempa{#2}%
317 \ifx\@tempa\@nnil
318 \else
319  \pfg@findb#1(~~tex(~~tex%
320 \fi}

\pfg@findb Having found a brace group, look for the string \\tex in the preceding tokens. If there is such, discard any tokens before that then take all tokens after \\tex (up to the brace already found) to be the optional arguments. In that case write out a suitable call to \psfrag, and then continue after the brace group. Otherwise if no \\tex is found, Add the contents of the brace group (without the braces) back in front of the list before restarting the search. In that way any strings inside brace groups will be found.

As {} have their normal catcodes, multiline \tex commands work as long as any line breaks occur inside the main { } argument, not between the optional arguments (normally there is no space at all there, so no possibility of a break). This is because TEX will read more than a line if necessary to ensure that braces balance.

The arguments are delimited by (\\tex sequences: #1 Is always discarded; the tokens before any (\\tex string. #2 contains any optional arguments (complete with [] brackets). #3 Will be the ‘dummy tokens’ placed at the end, unless no (\\tex is in the string, in which case #3 will be empty. This argument is \fi delimited which allows for tail recursion in a slightly sneaky way.

321 \long\def\pfg@findb#1(~~tex#2(~~tex#3\fi{%
322 \fi
323 \ifx\box#3\box
324 \else

Since \ is the PS escape character in strings, as well as the TEX escape character, the following looks a bit weird, but it’s probably right. A \ will appear in the PS string as \\, so…

First set \ to be \string (recall \ is active, and can be written as ~ due to the \lowercase above). This means that \\foo expands to \foo (one catcode 12 \ coming from \string\ and then a catcode 11 foo). This expansion is frozen by an \edef for use in the second argument. Then redefine \ to be \relax so it is a non-expandable active token, which writes as itself.

Now write a call to \psfrag to the temp file. The ‘user’ string \foo which was the literal PS \\foo thus ends up as:

\psfrag{. . . \\foo . . . }{ . . . \foo }%

When this file is read back \psfrag will read the first argument verbatim, and the second argument with normal catcodes so finally \foo gets to be a single token instead of 4, and is executed as part of the replacement text.

Finally reset \@tempa (which contatined the contents of the brace group to empty, as we’ve done with that.

325  {\let~\string
326  \edef\@tempb{\@tempa}%
327  \let~\relax
328  \immediate\write\pfg@temp{%
329  \string\psfrag\space
330  {\string\\tex#2{\@tempa}}#2\@percentchar
331  ^^J\@spaces\@spaces{\@tempb}\@percentchar}}%
332  \let\@tempa\@empty
333 \fi

Now start looking for the next brace group to test. First tip the contents of \@tempa in front of the tokens not yet seen, so that the contents of a brace group will be searched if they were not used as the argument to \tex.

334 \expandafter\pfg@find\@tempa}

Finally close the call to \lowercase.

335 }

336 ⟨/package⟩