summaryrefslogtreecommitdiff
path: root/macros/generic/misc/letterspacing.tex
blob: d73d9ed533c63b30a686ab1a3cfbaf984d0f9533 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
% Self-documenting file

\ifx  \Comment \Undefined
      \long \def \Comment #1\EndComment {}
\else
      \ifx  \EndComment \Undefined
            \def \EndComment
                {\message {Error: \noexpand \EndComment encountered
                                without preceding \noexpand \Comment}}
      \else %%% both defined; let's hope they're the right definitions!
      \fi
\fi

\Comment
This file, "letterspacing.tex", created by Philip Taylor of
Royal Holloway, University of London (P.Taylor@Rhul.Ac.Uk) for
Kaveh Bazargan of Focal Image (Kaveh@Focal.Demon.Co.Uk) may be
freely distributed provided that the following licence conditions
are honoured :
\EndComment

\Comment % The Licence
 Unlimited copying and redistribution of this file
 is permitted so long as the file is not modified
 in any way.

 Modifications may be made for private purposes (though
 this is discouraged, as it could result in documents
 typesetting differently on different systems) but if
 such modifications are re-distributed, the modified
 file must not be capable of being confused with the
 original.  In particular, this means

(a) the filename (the portion before the extension, if any)
    must not match any of :

        LETTERSPACE             LETTER-SPACE
        LETTERSPACING           LETTER-SPACING

   regardless of case, and

(b) the file must contain conditions identical to these,
 except that the modifier/distributor may, if he or she
 wishes, augment the list of proscribed filenames.
\EndComment

\Comment
As far as is possible, `inaccessible' control sequences (i.e.~control sequences
containing one or more commercial-at$\,$s) are used to minimise the risk of
accidental collision with user-defined macros; however, the control sequences
used to access the natural dimensions of the text are required to be user
accessible, and therefore must contain only letters.
\EndComment

%%% Open control sequences

\newdimen \naturalwidth
\newdimen \naturaldepth
\newdimen \naturalheight

\Comment
All control sequences defined hereafter are inaccessible to the casual user,
including the control sequence used to store the category code of commercial-at;
this catcode must be saved in order to be able to re-instate it at end of
module, as we have no idea in what context the module will be |\input|. Having
saved the catcode of commercial-at, we then change it to |11| (i.e.~that of a
letter) in order to allow it to be used in the cs-names which follow.
\EndComment

%%% Concealed control sequences

\expandafter \chardef
        \csname \string @code\endcsname =
                \the \catcode `\@
\catcode `\@ = 11

\Comment
We will need a a box register in order to measure the natural dimensions of
the text, and a token-list register in which to save the tokens to be
letter-spaced.
\EndComment

\newbox \l@tterspacebox
\newtoks \l@tterspacetoks

\Comment
We will need to test whether a particular macro expands to a space, so we will
need another macro which does so expand with which to compare it; we will
also need a `more infinite' variant of |\hss|.
\EndComment

\def \sp@ce { }
\def \hsss
    {\hskip 0 pt plus 1 fill minus 1 fill\relax}

\Comment
Many of the macros will used delimited parameter structures, typically
terminated by the reserved control sequence |\@nd|; we make this a synonym
for the empty macro (which expands to nothing), so that should it accidentally
get expanded there will be no side effects.  We also define a brief synonym
for |\expandafter|, just to keep the individual lines of code reasonably short.
\EndComment

\let \@nd = \empty
\let \@x = \expandafter

\Comment
We will also need to compare a token which has been peeked at by |\futurelet|
with a space token; because of the difficulty of accessing such a space
token (which would usually be absorbed by a preceding control word), we
establish |\sp@cetoken| as a synonym.  The code to achieve this is messy,
because of the very difficulty just outlined, and so we `waste' a control
sequence |\t@mp|; we then return this to the pool of undefined tokens.
\EndComment

\let \sp@cetoken = \relax
\edef \t@mp {\let \sp@cetoken = \sp@ce}
\t@mp \let \t@mp = \undefined

\Comment
The user-level macro |\letterspace| has exactly the same syntax as that for
|\hbox| and |\vbox|, as explained in the introduction; the delimited parameter
structure for this macro ensures that everything up to the open brace which
delimits the beginning of the text to be letter-spaced is absorbed as parameter
to the macro, and the brace-delimited text which follows is then assigned to the
token-list register |\l@tterspacetoks|; |\afterassignment| is used to regain
control once the assignment is complete.
\EndComment

%%% Primary (user-level) macro

\def \letterspace #1#%
    {\def \hb@xmodifier {#1}%
     \afterassignment \l@tterspace
     \l@tterspacetoks =
    }

\Comment
Control then passes to |\l@tterspace|, which starts by setting an |\hbox|
containing the text to be typeset; the dimensions of this box (and therefore
of the text) are then saved in the open control sequences previously declared,
and the |\hbox| becomes of no further interest.

A new |\hbox| is now created, in which the same text, but this time
letter-spaced, will be set; the box starts and ends with |\hss| glue so that if
only a single character is to be letter-spaced, it will be centered in the box.
If two or more characters are to be letter-spaced, they will be separated by
|\hsss| glue, previously declared, which by virtue of its greater degree of
infinity will completely override the |\hss| glue at the beginning and end;
thus the first and last characters will be set flush with the edges of the box.

The actual mechanism by which letter-spacing takes place is not yet apparent,
but it will be seen that it is crucially dependent on the definition of
|\l@tt@rspace|, which is expanded as the box is being set; the |\@x|
(|\expandafter|) causes the actual tokens stored in |\l@tterspacetoks| to be
made available as parameter to |\l@tt@rspace| rather than the token-list
register itself, as it is these tokens on which |\l@tt@rspace| will operate.
The |\@nd| terminates the parameter list, and the |{}| which immediately
precedes it ensures that that there is always a null element at the end of the
list: without this, the braces which are needed to protect an accent/character
pair would be lost if such a pair formed the final element of the list, at the
point where they are passed as the second (delimited) parameter to |\p@rtiti@n|;
by definition, <TeX> removes the outermost pair of braces from both simple and
delimited parameters during parameter substitution if such braces form the first
and last tokens of the parameter, and thus if a brace-delimited group ever
becomes the second element of a two-element list, the braces will be irrevocably
lost.  The |{}| ensure that such a situation can never occur.
\EndComment

%%% Secondary (implementation-level) macro

\def \l@tterspace
    {\setbox \l@tterspacebox = \hbox
                {\the \l@tterspacetoks}%
     \naturalwidth =  \wd \l@tterspacebox
     \naturaldepth =  \dp \l@tterspacebox
     \naturalheight = \ht \l@tterspacebox
     \hbox \hb@xmodifier
     \bgroup
           \hss
           \@x \l@tt@rspace
           \the \l@tterspacetoks {}\@nd
           \hss
     \egroup
    }

\Comment
The next macro is |\l@tt@rspace|, which forms the crux of the entire operation.
The text to be letter-spaced is passed as parameter to this macro, and the
first step is to check whether there is, in fact, any such text; this is
necessary both to cope with pathological usage (e.g.~|\letterspace {}|), and to
provide an exit route, as the macro uses tail-recursion to apply itself
iteratively to the `tail' of the text to be letter-spaced; when no elements
remain, the macro must exit.

Once the presence of text has been ensured, the token-list representing
this text is partitioned into a head (the first element), and the tail
(the remainder);  at each iteration, only the head is considered, although
if the tail is empty (i.e.~the end of the list has been reached), special
action is taken.

If the first element is a space, it is treated specially by surrounding it
with two globs of |\hsss| glue, to provide extra stretchability when compared
to the single glob of |\hsss| glue which will separate consecutive non-space
tokens; otherwise, the element itself is yielded, followed by a single glob
of |\hsss| glue.  This glue is suppressed if the element is the last of the
list, to ensure that the last token aligns with the edge of the box.

When the first element has been dealt with, the macro uses tail
recursion to apply itself to the remaining elements (i.e.~to the tail);
|\@x| (|\expandafter|) is again used to ensure that the tail is expanded
into its component tokens before being passed as parameter.
\EndComment

%%% Tertiary (implementation-level) macro

\def \l@tt@rspace #1\@nd
    {\ifx  \@nd #1\@nd
           \let \n@xt = \relax
     \else
           \p@rtition #1\@nd
           \ifx \h@ad \sp@ce \hsss \h@ad \hsss
           \else
                \h@ad
                \ifx \t@il \@nd \else \hsss \fi
           \fi
           \@x \def \@x \n@xt \@x
                {\@x \l@tt@rspace \t@il \@nd}%
     \fi
     \n@xt
    }

\Comment
The operation of token-list partitioning is conceptually simple: one passes the
token list as parameter text to a macro defined to take two parameters, the
first simple and the second delimited; the first element of the list will be
split off as parameter-1, and the remaining material passed as parameter-2.
Unfortunately this na\"\i ve approach fails when the first element is a bare
space, as the semantics of <TeX> prevent such a space from ever being passed as
a simple parameter (it could be passed as a delimited parameter, but as one
does not know what token follows the space, defining an appropriate delimiter
structure would be tricky if not impossible).  The technique used here relies
upon the adjunct macro |\m@kespacexplicit|, which replaces a leading bare space
by a space protected by braces; such spaces may legitimately be passed as simple
parameters.  Once that operation has been completed, the re-constructed token
list is passed to |\p@rtiti@n|, which actually performs the partitioning as
above; again |\@x| (|\expandafter|) is used to expand |\b@dy| (the
re-constructed token list) into its component elements before being passed as
parameter text.
\EndComment

%%% Adjunct macros -- list partitioning

\def \p@rtition #1\@nd
    {\m@kespacexplicit #1\@nd
     \@x \p@rtiti@n \b@dy \@nd
    }

\def \p@rtiti@n #1#2\@nd
    {\def \h@ad {#1}%
     \def \t@il {#2}%
    }

\Comment
The operation of making a space explicit relies on prescience: the code needs
to know what token starts the token list before it knows how to proceed.
Prescience in <TeX> is usually accomplished by |\futurelet|, and this code
is no exception: |\futurelet| here causes |\h@ad| to be |\let| equal to
the leading token, and control is then ceded to |\m@kesp@cexplicit|.

The latter compares |\h@ad| with |\sp@cetoken| (remember the convolutions we
had to go through to get |\sp@cetoken| correctly defined in the first place),
and if they match (i.e.~if the leading token is a space), then |\b@dy|
(the control sequence through which the results of this operation will
be returned) is defined to expand to a protected space (a space surrounded by
braces), followed by the remainder of the elements; if they do not match
(i.e.~if the leading token is \stress {not} a space), then |\b@dy| is simply
defined to be the original token list, unmodified.  If the leading token
\stress {was} a space, it must be replaced by a protected space: this is
accomplished by |\pr@tectspace|.

The |\pr@tectspace| macro uses a delimited parameter structure, as do most
of the other macros in this suite, but the structure used here is unique,
in that the initial delimiter is a space.  Thus, when a token-list starting
with a space is passed as parameter text, that space is regarded as matching
the space in the delimiter structure and removed; the expansion of the macro
is therefore the tokens remaining once the leading space has been removed,
preceded by a protected space |{ }|.
\EndComment

%%% Adjunct macros -- <space>... -> {<space>}...

\def \m@kespacexplicit #1\@nd
    {\futurelet \h@ad \m@kesp@cexplicit #1\@nd}

\def \m@kesp@cexplicit #1\@nd
    {\ifx \h@ad \sp@cetoken
          \@x \def \@x \b@dy
                     \@x {\pr@tectspace #1\@nd}%
     \else
          \def \b@dy {#1}%
     \fi
    }%

\@x \def \@x \pr@tectspace \sp@ce #1\@nd {{ }#1}

\Comment
The final step is to re-instate the category code of commercial-at.
\EndComment

%%% re-instate category code of commercial-at

\catcode `\@ = \the \@code

\Comment
Thus letter-spacing is accomplished.  The author hopes both that the code will
be found useful and that the explanation which accompanies it will be found
informative and interesting.
\EndComment