psfrag.sty, epsf.sty, and ps2frag.ps ^{∗}

Craig Barratt ^{†}

craig@arraycomm.com

Michael C. Grant

                       mcgrant@isl.stanford.edu

?

Change History

1.0

"General": Released version 1.0. . 1 1.1

"General": Released version 1.1, adding support for ashow, widthshow, awidthshow, and kshow. . . . . . . . . . . . . . . . . . . 1

1.99

"General": an interim version before 2.0 for L^{A}TEX 2ε, containing much of the 2.0 code, including renovated bounding box calculations and improved support for alternate show operators . . 8 Taken over from Craig, completely overhauled for L^{A}TEX 2ε. Completely changed the placement code; baseline alignment added. Integrated \psfrag commands into the PostScript file. graphics package used for portability across multiple DVIto-PostScript packages. Improved ps2frag.ps file provides improved (correct) support for ashow, widthshow, awidthshow, and kshow. . . . . . . . . . . . . . . . 1 2.0

"General": a few bug fixes, and the activation of L^{A}TEX 2ε-specific behavior . . . . . . . . . . . . . . . . . 8 Bug fixes . . . . . . . . . . . . . . . . . 1 Bug fixes. Better support for L^{A}TEX 2.09 PSfrag PSfrag files, including both naming schemes. Improved documentation and separated it from this file. . . . . 1

\pfg@dospecs: Fixed some rather stupid bugs in here . . . . . . . . . 7 Rearranged so that PSfragShow-Dict is the only name polluting the global namespace. PSfrag-Dict is still a “reserved” word, though. . . . . . . . . . . . . . . . . . 7

                                   \pfg@info: Added the \vbox to

protect any vertical space that might have been requested in the replacement text. . . . . . . . 4

                                   PSFragmentLocs: Fixed up the ig-

norespaces calls . . . . . . . . . . . . 6 awproc: Removed dictionary manipulation because it causes problems with Adobe Illustrator. . . . . . . . . . . . . . . . . . . 9

1 psfrag.sty

1 ⟨∗package⟩

∗This file has version number ?, last revised ?.

†This author is no longer participating in the development of PSfrag.

1.1 Intialization

We will need the graphics package, at least. In my opinion, graphicx should be used, but I can’t force anyone...

 2 \RequirePackage{graphics}

Here we allocate the dimension registers, file descriptors, and \if statements we will need. Because of the extremely local scope with which we use the dimension registers, we can safely re-use some of the registers from graphics.sty (which are, in turn, re-used from L^{A}TEX).

 3 \let\pfg@x\Gin@urx \let\pfg@mx\Gin@nat@width
 4 \let\pfg@y\Gin@ury \let\pfg@my\Gin@nat@height
 5 \newif\ifpfg@file \newif\ifpfg@found \newif\ifpfg@spec
 6 \newif\ifpfg@compat \if@compatibility\pfg@compattrue\fi
 7 \newread\pfg@file
 8 \newtoks\pfg@proc \newtoks\pfg@str

1.2 Package Options

 9 \DeclareOption{showall}{%
10 \let\psfragshowfalse\relax
11 \let\psfragshowdefault\relax
12 \psfragshowtrue}
13 \if@compatibility
14 \DeclareOption{2emode}{\pfg@compatfalse}
15 \else
16 \DeclareOption{209mode}{\pfg@compattrue}
17 \fi

1.3 Interface to the graphics package

\pfg@rawps This macro is used, in the interim, until one is provided by the graphics package. It must send the contents of its argument straight to the PostScript file.

18 ⟨dvips | dvipsone | pubps⟩\def\pfg@rawps#1{\special{ps: #1}}
19 ⟨textures⟩\def\pfg@rawps#1{\special{rawpostscript #1}}
20 ⟨dvialw⟩\def\pfg@rawps#1{\special{language  "PS", literal "#1"}}
21 ⟨pctexps⟩\def\pfg@rawps#1{\special{ps::#1}}

\Ginclude@eps We need to redefine this macro from the graphics package so that it inserts the PSfrag replacements. So we save the original definition of \Ginclude@eps, and wrap it with code that scans the EPS file for any frags that PSfrag has been requested to replace, either through \psfrag commands or embedded \tex strings. If any are found, then the appropriate adjustments to the PostScript environment are made to filter them out; otherwise, the original \Ginclude@eps command is executed unchanged.

22 \let\pfg@include@eps\Ginclude@eps
23 \def\Ginclude@eps#1{%
24 \pfg@scan{#1}\ifpfg@found
25  \ifpsfragshow\else\pfg@hidestart\fi
26  \pfg@include@eps{#1}%
27  \ifpsfragshow\else\pfg@hideend\fi
28  \the\pfg@proc\global\pfg@proc={}%
29 \else\pfg@include@eps{#1}\fi}

1.4 User interface macros

\ifpsfragshowall \psfragshowtrue \psfragshowfalse \psfragshowall \psfragshowdefault Define the on-off switches for the replaced text, as described above.
30 \newif\ifpsfragshow \psfragshowfalse
31 \let\psfragshowall\psfragshowtrue
32 \let\psfragshowdefault\psfragshowfalse
33 \ProcessOptions

\psfrag The main macro \psfrag must be defined specially in order to handle its odd combination of optional and required arguments.

34 \edef\pfg@dflt{\ifpfg@compat bl\else Bl\fi}
35 \def\psfrag#1{\@ifnextchar [{\@psfraga{#1}}%
36  {\@psfrag{#1}[\pfg@dflt][\pfg@dflt][1][0]}}
37 \def\@psfraga#1[#2]{\@ifnextchar [{\@psfragb{#1}[#2]}%
38  {\@psfrag{#1}[#2][\pfg@dflt][1][0]}}
39 \def\@psfragb#1[#2][#3]{\@ifnextchar [{\@psfragc{#1}[#2][#3]}%
40  {\@psfrag{#1}[#2][#3][1][0]}}
41 \def\@psfragc#1[#2][#3][#4]{\@ifnextchar [{\@psfrag{#1}[#2][#3][#4]}%
42  {\@psfrag{#1}[#2][#3][#4][0]}}
43 \long\def\@psfrag#1[#2][#3][#4][#5]#6{%
44 \@ifundefined{pfg@n@#1}{}{%
45  \def\@tempa{#1}\ifx\@tempa\@empty\else
46  \PackageWarning{PSfrag}%
47  {Replacement for PostScript fragment `#1' on
48  page \thepage \space redefined.}\fi}%

For each fragment, two macros are defined based on its name. The macro \pfg@m@name is defined so that, when executed, it fills temporary variables with the position, scale, and rotation information for the fragment. The macro \pfg@n@name contains the L^{A}TEX code to be typeset for a replacement.

49  \@namedef{pfg@m@#1}{\edef\pfg@posn{#2}\edef\pfg@psposn{#3}%
50  \edef\pfg@scale{#4pt}\edef\pfg@rotn{#5pt}}%
51  \@namedef{pfg@n@#1}{#6}\ignorespaces}

psfrags This is a dummy environment whose sole purpose is to delimit the scope of \psfrag declarations. This environment will rarely be necessary, as any surrounding environment (such as a figure) will suffice.

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

1.5 Fragment processing

\pfg@info {text}(x,y)(bbhx,bbhy)(bbvx,bbvy){rotn}{base} The following macro does the dirty work. It uses the information generated by the ps2frag script to determine a complete breakdown of the positioning of a piece of text in the PostScript file.

{text} is the argument of the PostScript text operator (show, ashow, kshow,

     widthshow, or awidthshow).

(x,y) is the absolute cooridinate, in points, of the text’s starting point (if the text is not rotated, the lower left corner).

(bbhx,bbhy) is the vector from the lower left corner to the lower right corner of the text’s bounding box.

(bbvx,bbvy) is the vector from the lower left corner to the upper left corner of the text’s bounding box.

{rotn} is the rotation, in degrees, of the text’s baseline.

{base} is the fraction of the vector (bbvx,bbvy) which would span the distance from the lower-left corner to the left end of the baseline.

Note that these descriptions are technically accurate only for unrotated text; for rotated text, rotations are removed before the bounding box is calculated.

53 \def\pfg@excl{!}
54 \def\pfg@tfor#1\do{\@tfor\@tempa:=#1\do}
55 \def\pfg@info #1(#2,#3)(#4,#5)(#6,#7)#8#9{%
56 \bgroup\@nameuse{pfg@m@#1}%

Determine the position of the anchor point on the PostScript text, and scale that position according to the scaling factor determined by the graphics.sty macros.

57  \pfg@x=#2bp \advance\pfg@x by-\Gin@llx \pfg@mx=0.5bp%
58  \pfg@y=#3bp \advance\pfg@y by-\Gin@lly \pfg@my=0.5bp%
59  \expandafter\pfg@tfor\pfg@psposn\do{%
60  \if l\@tempa\pfg@mx=0\else\if r\@tempa\pfg@mx=1\else
61  \if b\@tempa\pfg@my=0\else\if t\@tempa\pfg@my=1\else
62  \if B\@tempa\pfg@my=#9\fi\fi\fi\fi\fi bp}%
63  \advance\pfg@x by#4\pfg@mx \advance\pfg@y by#5\pfg@mx
64  \advance\pfg@x by#6\pfg@my \advance\pfg@y by#7\pfg@my
65  \ifx\Gin@scalex\pfg@excl\let\Gin@scalex\Gin@scaley
66  \else\ifx\Gin@scaley\pfg@excl\let\Gin@scaley\Gin@scalex\fi\fi
67  \pfg@x=\Gin@scalex\pfg@x \pfg@y=\Gin@scaley\pfg@y

Create a zero-area box, with the desired PSfrag replacement text typeset inside of it. Align the box with the alignment point of the replacement text.

68  \setbox\z@\vbox{\hbox{\@nameuse{pfg@n@#1}}}%
69  \dimen@=\ht\z@ \advance\dimen@ by\dp\z@
70  \pfg@mx=0.5\wd\z@ \pfg@my=0.5\dimen@
71  \expandafter\pfg@tfor\pfg@posn\do{%
72  \if l\@tempa \pfg@mx=0bp\else\if r\@tempa \pfg@mx=\wd\z@\else
73  \if b\@tempa \pfg@my=0bp\else\if B\@tempa \pfg@my=\dp\z@\else
74  \if t\@tempa \pfg@my=\dimen@\fi\fi\fi\fi\fi}%
75  \setbox\z@\hbox to\z@{\hskip-\pfg@mx
76  \vbox to\z@{\vss\box\z@\vskip-\pfg@my}\hss}%

Place rotation and/or scaling constructs around the box. Rotation comes first. Using the graphics package’s internal macros enhances portability. It is necessary to surround each construct with a zero-size box to insure that the coordinate system is indeed local when the transformations are performed.

77  \pfg@mx=#8pt \advance\pfg@mx by\pfg@rotn \pfg@my=\pfg@scale
78  \ifdim\pfg@mx=0pt\else
79  \edef\Grot@angle{\strip@pt\pfg@mx}%
80  \setbox\z@\hbox to\z@{\Grot@start\box\z@\Grot@end}\fi
81  \ifdim\pfg@my=1pt\else
82  \edef\Gscale@x{\strip@pt\pfg@my}\let\Gscale@y\Gscale@x
83  \setbox\z@\hbox to\z@{\Gscale@start\box\z@\Gscale@end}\fi

Place the box.

84  \@killglue\raise\pfg@y\hbox to\z@{\kern\pfg@x\box\z@\hss}%
85 \egroup\ignorespaces}

1.6 File handling

\pfg@scan It is with this macro that the EPS file is scanned for PSfrag information. The replacement information is placed in two token registers: \pfg@proc holds the replacement macros, and \pfg@str contains the strings to be replaced, in PostScript syntax, to be passed to the PostScript filtering \special.

For compatibility mode, we just redefine those commands used in the .frag files to call the updated L^{A}TEX 2ε versions.

86 \ifpfg@compat
87 \typeout{PSfrag Compatibility Mode Enabled.}%
88 \else
89 \def\pfg@scan#1{%
90 \edef\pfg@fname{#1}%
91 \immediate\openin\pfg@file=#1\relax
92 \pfg@foundfalse
93 \ifeof\pfg@file\else
94  \bgroup

Loop 1: We make rearrange the catcodes so that everything read in from the file remains unprocessed. Then we read in consecutive lines until the %PSfragBegin comment is encountered. If the header comments terminate first, then we know that PSfrag has not touched this file...

95  \let\do\@makeother\dospecials\catcode`\ 10 %
96  \@makeother\^^D\catcode\endlinechar9 %
97  \catcode`\{1\catcode`\}2 %
98  \pfg@filetrue\loop\read\pfg@file to\pfg@line
99  \ifeof\pfg@file\pfg@filefalse
100  \else\ifx\pfg@line\@empty\pfg@filefalse
101  \else\expandafter\pfg@find\pfg@line.....\\\fi\fi
102  \ifpfg@file\repeat
103  \egroup
104  \ifpfg@found
105  \global\pfg@foundfalse
106  \bgroup

Loop 2: From now until the %PSfragEnd comment is found, we assume a very strict structure: one PSfrag command per line, starting with the comment character (whose catcode is now changed to 0). So we can simply execute each line to obtain the desired information.

107  \catcode`\% 0 %
108  \pfg@filetrue\loop
109  \read\pfg@file to\pfg@line
110  \ifeof\pfg@file\pfg@filefalse\else\pfg@line\\\fi
111  \ifpfg@file\repeat
112  \egroup
113  \fi\immediate\closein\pfg@file\fi}
114 \fi

\pfg@find \pfg@@find \pfg@gobble These macros are used during the first loop in \pfg@scan to hunt for the first PSfrag comment. Each line is tested by \pfg@find starting comment character; if one is not found, then we know that there must be no PSfrag comments, and we may exit the loop early. Otherwise, we use \pfg@@find to determine if the current comment is in fact a PSfrag comment, all of which start with %PSfrag (actually, only %PSfra is confirmed, because that is all the Adobe Document Structuring comments require for uniqueness.) \pfg@gobble simply eats the remaining text on the line.

115 {\catcode`\%=12\global\let\pfg@pcnt=%}
116 \def\pfg@find#1{%
117 \ifx#1\pfg@pcnt\expandafter\pfg@@find\else
118 \pfg@filefalse\expandafter\pfg@gobble\fi}
119 \def\pfg@pfx{PSfra}
120 \def\pfg@@find#1#2#3#4#5{%
121 \def\pfg@{#1#2#3#4#5}\ifx\pfg@\pfg@pfx
122  \global\pfg@foundtrue\global\pfg@filefalse\fi\pfg@gobble}
123 \def\pfg@gobble#1\\{}

\PSfragNext \PSfragInfo \PSfragEnd \PSfragBegin These macros are embedded in every PSfrag-aware EPS file. The first two basically extract the information for their internal counterparts and place them in the \pfg@proc token register; the third signals the end the file search. \PSfragBegin actually isn’t used; while it occurs in the file, it is discarded as text instead of executed. But I define it here for symmetry anyway, just in case I do decide to use it.

124 \def\PSfragNext#1\\{%
125 \def\pfg@m@{}\global\pfg@proc=\expandafter{\the\pfg@proc\psfrag{}#1}}
126 \def\PSfragInfo#1#2\\{%
127 \@ifundefined{pfg@m@#1}{}{%
128  \global\pfg@foundtrue
129  \global\pfg@proc=\expandafter{\the\pfg@proc\pfg@info{#1}#2}%
130  \global\pfg@str=\expandafter{\the\pfg@str (#1)}}}
131 \def\PSfragEnd\\{\pfg@filefalse}

1.7 L^{A}TEX 2.09 compatibility macros

The following macros—a different definition of \pfg@scan, and a few others—are here to maintain compatibility as best as possible with L^{A}TEX 2.09’s version of PSfrag.

\psfragspecial This macro is supported only in back-compatabilty mode, and was used to call \special manually. I haven’t implemented it yet; does anyone out there use it?

132 \ifpfg@compat
133 \def\psfragspecial#1#2#3#4#5#6{%
134  \PackageError{PSfrag}{\string\psfragspecial
135  not implemented yet.}}

\pfg@scan The L^{A}TEX 2.09 version of this macro simply \inputs a .frag, .epsfrag, or .psfrag file (depending on the configuration). The commands within it are redefined to call the new versions.

136 \def\pfg@strip#1.#2\relax{#1}
137 \def\pfg@scan#1{%
138 \pfg@foundfalse

139 ⟨/package⟩ 140 ⟨∗package & longnames⟩

141 \InputIfFileExists{#1frag}\relax{%
142  \edef\@tempa{\pfg@strip#1..\relax.frag}
143  \InputIfFileExists\@tempa\relax\relax}}

144 ⟨/package & longnames⟩ 145 ⟨∗package & shortnames⟩

146 \edef\@tempa{\pfg@strip#1..\relax.frag}
147 \InputIfFileExists\@tempa\relax\relax}

148 ⟨/package & shortnames⟩ 149 ⟨∗package⟩

\PsFragShowString \PsFragInfo \PsFrag PSFragmentLocs These macros lie inside every L^{A}TEX 2.09 .frag file, and are redefined to process things the L^{A}TEX 2ε way here. The grouping of the PSFragmentLocs environment is used to temporarily redefine \put and \psfrag to direct them to the proper behavior. It also calls \PSfragEnd upon completion, which isn’t necessary now, but may be in the future.

150 \def\PsFragShowString#1{\ignorespaces}
151 \def\PsFragInfo#1#2#3#4#5#6#7#8{%
152 \PSfragInfo{#1}(#2,#3)(#4,#5)(#6,#7){#8}{0}\\\ignorespaces}
153 \def\PsFrag#1{\PSfragNext#1\\\ignorespaces}
154 \def\pfg@put(#1)#2{\ignorespaces}
155 \newenvironment{PSFragmentLocs}
156 {\let\put\pfg@put\let\psfrag\PsFrag\ignorespaces}
157 {\PSfragEnd\\\global\@ignoretrue}
158 \fi

1.8 The PostScript interface

\pfg@hidestart \pfg@hideend These macros are called around any picture that requires PSfragprocessing in order to initialize the PostScript-based show filter. It is assumed that the token register \pfg@str contains the list of strings, in PostScript syntax, to filter out.

159 \def\pfg@hidestart{%
160 \ifpfg@specs\else\pfg@dospecs\pfg@specstrue\fi
161 \pfg@rawps{PSfragShowDict begin PSfragDict begin
162  /fragStrings [\the\pfg@str] def end}}
163 \def\pfg@hideend{\pfg@rawps{end}}

\ifpfg@specs This \if is true if the PSfrag PostScript dictionary has been inserted; false otherwise.

164 \newif\ifpfg@specs \pfg@specsfalse

\pfg@dospecs This macro is only called once, in order to insert the PostScript code necessary to perform the text-filtering operations. It is inserted only if any text to replace is actually found. So, for example, if \psfragshowtrue is called, then this will never make it to the PostScript file.

165 \def\pfg@dospecs{\pfg@rawps{
166 userdict begin /PSfragShowDict 6 dict def end
167 PSfragShowDict begin /PSfragDict 30 dict def
168  PSfragDict begin
169  /BD { bind def } bind def /ED { exch def } BD
170  /LD { load def } BD /CHAR { /c ED ( ) dup 0 c put } BD
171  /Oshow /show LD /Oashow /ashow LD /Okshow /kshow LD
172  /Owidthshow /widthshow LD /Oawidthshow /awidthshow LD
173  /Sshow { stringwidth rmoveto } BD
174  /Sashow { dup Sshow length /m ED exch
175  m mul exch m mul rmoveto } BD
176  /Swshow { /by ED /bx ED /chr ED { chr eq

177  { bx by rmoveto } forall } } BD
178  /Swidthshow { dup 5 1 roll Sshow Swshow } BD
179  /Sawidthshow { dup 7 1 roll Sashow Swshow } BD
180  /Skshow { exch /kern exch BD { dup CHAR
181  stringwidth rmoveto kern } forall } BD
182  /condShow{ /pfrag ED /ptext ED /s ED
183  s s (\string\\tex) anchorsearch { pop pop pfrag }{
184  pop false fragStrings {
185  s eq { pop true exit } if
186  } forall { pfrag } { ptext } ifelse
187  } ifelse flush } BD
188  /fragStrings [] def
189  end
190  /show { PSfragDict begin {Oshow} {Sshow} condShow end } bind def
191  /ashow { PSfragDict begin {Oashow} {Sashow} condShow end } bind def
192  /kshow { PSfragDict begin {Okshow} {Skshow} condShow end } bind def
193  /widthshow { PSfragDict begin {Owidthshow} {Swidthshow}
194  condShow end } bind def
195  /awidthshow { PSfragDict begin {Oawidthshow} {Sawidthshow}
196  condShow end } bind def
197 end}}

198 ⟨/package⟩

2 epsf.sty

To insure seamless back-compatibility, an epsf.sty file has been created which simply uses the proper \ProvidesPackage command and loads epsfig.sty. 199 ⟨∗epsf⟩

200 \DeclareOption*{\PassOptionsToPackage{epsfig}{\CurrentOption}}
201 \ProcessOptions
202 \RequirePackage{epsfig}

203 ⟨/epsf⟩

3 ps2frag.ps

Thanks to the fact that PostScript shares the same comment characters with TEX, I can maintain and document the ps2frag.ps script in exactly the same fashion as I maintain the other TEX files.

ps2frag.ps redefines the text display operators in PostScript in order to generate and output information about each piece of text that is displayed. This information is collected by the ps2frag script and placed back into the EPS fill as a set of Document Structuring comments. These comments affect the figure in no visible way, but provide information to the PSfrag L^{A}TEX macros that allow it to properly place the replacement text.

Ghostscript calls this code in the following manner:

     gs -ddx=dx -ddy=dy -dNODISPLAY -dQUIET file

dx and dy are the horizontal and vertical offsets, respectively, to add to the locations determined. They default to 0 if not supplied.

The only changes made to the global namespace are to define /PSfragDict and to re-define the show commands to output bounding-box information. Therefore, virtually any picture should be processable; if you find a reasonable file that is not, let me know.

3.1 Intialization

Here we initialize the dictionary, grab dx and dy if they have been defined, and make some miscellaneous abbreviations. 204 ⟨∗psdriver⟩

205 /PSfragDict 40 dict def PSfragDict begin
206 /DX /dx where { /dx get } { 0 } ifelse def
207 /DY /dy where { /dy get } { 0 } ifelse def
208 /BD { bind def } bind def /ED { exch def } BD
209 /LD { load def } BD
210 /STR 16 string def /w /print LD

3.2 Output commands

This section defines the commands that write out the PSfrag information, in the

form of \PSfragInfo and \PSfragNext commands.

We must be careful when writing out numbers that GhostScript not use scientific notation, because that breaks the L^{A}TEX interpreter.

wFrag This macro expects the global variables str, llx, lly, dxx, dxy, dyx, dyy, rot, and base to be filled with information about the current piece of text. This macro writes a single \PSfragInfo line to the output file. If the string begins with \tex, it is an embedded PSfrag command, so a \PSfragNext command is also written out.

The debugging version of the wFrag operator also draws bounding box information on the page to aid in debugging. To include this feature in your version of the file, add the debug flag to the docstrip extraction command. Normally, it should be omitted from ps2frag.ps, in part because it will not work with kshow.

211 /wa { ({) w w (}) w } BD
212 /wnum { 1e4 mul round 1e4 div dup dup cvi eq { cvi } if STR cvs w } BD
213 /wn { ({) w wnum (}) w } BD
214 /wc { (\() w wnum (,) w wnum (\)) w } BD
215 /wFrag {
216 str (\\tex) anchorsearch
217 { pop (%PSfragNext) w w (\n%PSfragInfo{}) w }
218 { pop (%PSfragInfo) w str wa } ifelse
219 lly DY add llx DX add wc dxy dxx wc dyy dyx wc
220 rot wn base wn (\n) w

221 ⟨/psdriver⟩ 222 ⟨∗psdriver & debug⟩

223 gsave str myCharPath fill grestore gsave
224 initgraphics newpath
225 llx lly moveto dxx dxy rlineto dyx dyy rlineto
226 dxx neg dxy neg rlineto closepath stroke
227 llx lly moveto dyx base mul dyy base mul rmoveto
228 dxx dxy rlineto stroke
229 llx lly 2 0 360 arc fill

230 grestore

231 ⟨/psdriver & debug⟩ 232 ⟨∗psdriver⟩

233 } BD

3.3 Bounding-box determination

myCharPath This command is called in replacement of the show operators and its kin. It needs to completely emulate the behavior of the original show operator; so proc is called after each character has been placed to effect any additional movements (kerns).

aproc wproc awproc These kerning commands emulate the behavior of ashow, widthshow, and awidthshow, respectively. show uses pop because it performs no kerning, and kshow uses its own user-defined operator. See the PostScript manual for details.

234 /myCharPath { { ( ) dup 0 3 index put false charpath proc } forall } BD
235 /aproc { pop x y rmoveto } BD
236 /wproc { c eq { x y rmoveto } if } BD
237 /awproc { c eq { x y rmoveto } if x2 y2 rmoveto } BD
findbbox usage: str proc findbbox

This command accepts a string str and a kerning procedure kern, and determines the positioning and bounding box information that PSfrag requires. Normal pathbbox calculations will not work for rotated text, so this procedure is somewhat complex. It proceeds as follows:

1. Determines the amount of rotation that the baseline of the text will experience due to the current FontMatrix and transformation matrix.

2. Inserts a negative rotation between defaultmatrix and currentmatrix so that the text will come out horizontal.

3. Now that the bounding box will give tight bounds, uses myCharPath to trace out the text, and pathbbox to determine its bounding box.

4. Transforms the box corners to default coordinates and determines the lowerleft and upper-right corners.

5. Determines the baseline height as a fraction of the total height.

6. Rotates (llx,lly), (dxx,dxy), and (dyx,dyy) back to the original coordinate system, and calls wFrag.

7. Moves currentpoint to where it would be had the original show command had been called.

238 /findbbox {
239 /proc ED /str ED
240 str () ne {
241  gsave currentpoint translate
242  % Step 1.
243  /CTM matrix currentmatrix matrix defaultmatrix
244  matrix invertmatrix matrix concatmatrix def
245  /rot 1 0 currentfont /FontMatrix get dtransform
246  CTM dtransform exch atan def

247  % Step 2.
248  rot 0 ne {
249  /rm rot neg matrix rotate def
250  CTM rm CTM concatmatrix initmatrix concat
251  } if
252  % Step 3.
253  newpath 0 0 moveto str myCharPath flattenpath pathbbox
254  currentpoint /nety ED /netx ED
255  % Step 4.
256  CTM transform 4 2 roll CTM transform
257  /lly ED /llx ED /ury ED /urx ED
258  lly ury gt { /ury lly /lly ury def def } if
259  llx urx gt { /urx llx /llx urx def def } if
260  % Step 5.
261  /base ury lly eq { 0 } { CTM 5 get lly sub ury lly sub div
262  dup abs add 0.5 mul } ifelse def
263  % Step 6.
264  rot 0 eq { 0 ury lly sub urx llx sub 0 llx lly }{
265  0 ury lly sub rm idtransform
266  urx llx sub 0 rm idtransform
267  llx lly rm itransform
268  } ifelse [/lly /llx /dxy /dxx /dyy /dyx] {ED} forall
269  grestore wFrag
270  % Step 7.
271  netx nety rmoveto
272 } if
273 } BD
274 end

3.4 Redefining the show operators

show ashow widthshow awidthshow kshow The text display operators must be redefined so that they calculate boundingbox information instead of actually displaying text. These definitions, and the PSfragDict dictionary, are the only modifications made to PostScript’s global namespace. Therefore, any Encapsulated PostScript code that uses these standard operators should run though this processor without problems.

275 /show { PSfragDict begin
276 /pop load findbbox
277 end } bind def
278 /ashow { PSfragDict begin
279 3 1 roll /y exch def /x exch def
280 /aproc load findbbox
281 end } bind def
282 /widthshow { PSfragDict begin
283 4 1 roll /c exch def /y exch def /x exch def
284 /wproc load findbbox
285 end } bind def
286 /awidthshow { PSfragDict begin
287 6 1 roll /y2 exch def /x2 exch def /c exch def /y exch def
288 /x exch def /awproc load findbbox
289 } bind def
290 /kshow { PSfragDict begin
291 exch findbbox

292 end } bind def

293 ⟨/psdriver⟩