1%
2% This file is part of the Omega project, which
3% is based in the web2c distribution of TeX.
4%
5% Copyright (c) 1994--2000 John Plaice and Yannis Haralambous
6% applies only to the changes to the original pltotf.web
7%
8% This program by D. E. Knuth is not copyrighted and can be used freely.
9% Version 0 was implemented in January 1982.
10% In February 1982 a new restriction on ligature steps was added.
11% In June 1982 the routines were divided into smaller pieces for IBM people.
12% Hex was added in September 1982, and the result became "Version 1".
13% Version 1.1 fixed a bug in section 28 (since eoln is undefined after eof).
14% Slight changes were made in October, 1982, for version 0.6 of TeX.
15% Version 1.2 fixed a bug in section 115 (TOP, MID, and BOT can be zero)
16% Version 1.3 (April 1983) blanked out unused BCPL header bytes
17% Version 2 (July 1983) was released with TeX version 0.999.
18% Version 2.1 (September 1983) changed TEXINFO to FONTDIMEN.
19% Version 2.2 (May 1985) added checksum computation to match METAFONT.
20% Version 2.3 (August 1985) introduced `backup' to fix a minor bug.
21% Version 3 (October 1989) introduced extended ligature features.
22% Version 3.1 (November 1989) fixed two bugs (notably min_nl:=0).
23% Version 3.2 (December 1989) improved `shorten', increased max_letters.
24% Version 3.3 (September 1990) fixed `nonexistent char 0' (John Gourlay).
25% Version 3.4 (March 1991) has more robust `out_scaled' (Wayne Sullivan).
26% Version 3.5 (March 1995) initialized lk_step_ended (Armin K\"ollner).
27% Version 3.6 (January 2014) corrected possible end-of-line glitch (Ken Nakano),
28%  and get_fix now treats -- as + (Peter Breitenlohner).
29
30% Version 1.0 of OPL2OFM (March 1997) allows one to read OPL files
31% and generate OFM files.
32% Version 1.11 (February 2000).
33% Version 1.12 (September 2009) various bug fixes by Peter Breitenlohner.
34% Version 1.13 (January 2014) more bug fixes.
35
36% Here is TeX material that gets inserted after \input webmac
37\def\hang{\hangindent 3em\indent\ignorespaces}
38\font\ninerm=cmr9
39\let\mc=\ninerm % medium caps for names like SAIL
40\def\PASCAL{Pascal}
41\font\logo=logo10 % for the METAFONT logo
42\def\MF{{\logo METAFONT}}
43
44\def\(#1){} % this is used to make section names sort themselves better
45\def\9#1{} % this is used for sort keys in the index
46
47\def\title{OPL2OFM}
48\def\contentspagenumber{301}
49\def\topofcontents{\null
50  \def\titlepage{F} % include headline on the contents page
51  \def\rheader{\mainfont\hfil \contentspagenumber}
52  \vfill
53  \centerline{\titlefont The {\ttitlefont OPL2OFM} processor}
54  \vskip 15pt
55  \centerline{(Version 1.13, January 2014)}
56  \vfill}
57\def\botofcontents{\vfill
58  \centerline{\hsize 5in\baselineskip9pt
59    \vbox{\ninerm\noindent
60    The preparation of the original report
61    was supported in part by the National Science
62    Foundation under grants IST-8201926 and MCS-8300984,
63    and by the System Development Foundation. `\TeX' is a
64    trademark of the American Mathematical Society.}}}
65\pageno=\contentspagenumber \advance\pageno by 1
66
67@* Introduction.
68The \.{OPL2OFM} utility program converts property-list (``\.{PL}''
69and (``\.{OPL}'') files into equivalent \TeX\ and $\Omega$ font metric
70(``\.{TFM}'' and ``\.{OFM}) files. It also makes a thorough check of
71the given \.{PL} or \.{OPL} file, so that the \.{TFM} or \.{OFM} file
72should be acceptable to \TeX\ or $\Omega$.
73
74The first \.{PLtoTF} program was designed by Leo Guibas in the summer of
751978. Contributions by Frank Liang, Doug Wyatt, and Lyle Ramshaw
76also had a significant effect on the evolution of the present code.
77
78Extensions for an enhanced ligature mechanism were added by D. E. Knuth
79in 1989.
80
81Extensions to handle extended font metric files (``\.{OFM}'') were
82added by John Plaice in December 1995 and January 1996, resulting in the
83new program \.{OPL2OFM}.  In the following documentation, all unchanged
84references to the \.{PLtoTF} program and to \.{TFM} and \.{PL} files also
85apply to the \.{OPL2OFM} program and to \.{OFM} and \.{OPL} files.
86
87The |banner| string defined here should be changed whenever \.{OPL2OFM}
88gets modified.
89
90@d banner=='This is OPL2OFM, Version 1.13' {printed when the program starts}
91
92@ This program is written entirely in standard \PASCAL, except that
93it has to do some slightly system-dependent character code conversion
94on input. Furthermore, lower case letters are used in error messages;
95they could be converted to upper case if necessary. The input is read
96from |pl_file|, and the output is written on |tfm_file|; error messages and
97other remarks are written on the |output| file, which the user may
98choose to assign to the terminal if the system permits it.
99@^system dependencies@>
100
101The term |print| is used instead of |write| when this program writes on
102the |output| file, so that all such output can be easily deflected.
103
104@d print(#)==write(#)
105@d print_ln(#)==write_ln(#)
106
107@p program OPL2OFM(@!pl_file,@!tfm_file,@!output);
108const @<Constants in the outer block@>@/
109type @<Types in the outer block@>@/
110var @<Globals in the outer block@>@/
111procedure initialize; {this procedure gets things started properly}
112  var @<Local variables for initialization@>@/
113  begin print_ln(banner);@/
114  @<Set initial values@>@/
115  end;
116
117@ The following parameters can be changed at compile time to extend or
118reduce \.{PLtoTF}'s capacity.
119
120@d char_max=@"FFFF
121@d xchar_max=char_max+1
122@d xxchar_max=xchar_max+1
123@d mem_max=xxchar_max+xxchar_max+xxchar_max+xxchar_max
124@#
125@d width_max=@"FFFF
126
127@<Constants...@>=
128@!buf_size=60; {length of lines displayed in error messages}
129@!max_header_bytes=100; {four times the maximum number of words allowed in
130  the \.{TFM} file header block, must be 1024 or less}
131@!max_param_words=100; {the maximum number of \.{fontdimen} parameters allowed}
132@!max_lig_steps=800000;
133  {maximum length of ligature program, must be at most $32767-257=32510$}
134@!xmax_label=800001; {must be greater than |max_lig_steps|}
135@!max_kerns=50000; {the maximum number of distinct kern values}
136@!hash_size=130003; {preferably a prime number, a bit larger than the number
137  of character pairs in lig/kern steps}
138@!hash_mult=16007; {another prime}
139@!lig_size=800000; {maximum length of |lig_kern| program, in words}
140@!max_char=char_max; {the largest character number in a font}
141@!xmax_char=xchar_max; {|max_char|+1}
142@!xxmax_char=xxchar_max;{|max_char|+2}
143@!mem_size=mem_max; {|max_char|*4+8}
144@!max_width=width_max; {the largest character width number}
145@!max_height=255; {the largest character height number}
146@!max_depth=255; {the largest character depth number}
147@!max_italic=255; {the largest character italic correction number}
148
149@ Here are some macros for common programming idioms.
150
151@d incr(#) == #:=#+1 {increase a variable by unity}
152@d decr(#) == #:=#-1 {decrease a variable by unity}
153@d do_nothing == {empty statement}
154
155@* Property list description of font metric data.
156The idea behind \.{PL} files is that precise details about fonts, i.e., the
157facts that are needed by typesetting routines like \TeX, sometimes have to
158be supplied by hand. The nested property-list format provides a reasonably
159convenient way to do this.
160
161A good deal of computation is necessary to parse and process a
162\.{PL} file, so it would be inappropriate for \TeX\ itself to do this
163every time it loads a font. \TeX\ deals only with the compact descriptions
164of font metric data that appear in \.{TFM} files. Such data is so compact,
165however, it is almost impossible for anybody but a computer to read it.
166The purpose of \.{PLtoTF} is to convert from a human-oriented file of text
167to a computer-oriented file of binary numbers.
168
169@<Glob...@>=
170@!pl_file:text;
171
172@ @<Set init...@>=
173reset(pl_file);
174
175@ A \.{PL} file is a list of entries of the form
176$$\.{(PROPERTYNAME VALUE)}$$
177where the property name is one of a finite set of names understood by
178this program, and the value may itself in turn be a property list.
179The idea is best understood by looking at an example, so let's consider
180a fragment of the \.{PL} file for a hypothetical font.
181$$\vbox{\halign{\.{#}\hfil\cr
182(FAMILY NOVA)\cr
183(FACE F MIE)\cr
184(CODINGSCHEME ASCII)\cr
185(DESIGNSIZE D 10)\cr
186(DESIGNUNITS D 18)\cr
187(COMMENT A COMMENT IS IGNORED)\cr
188(COMMENT (EXCEPT THIS ONE ISN'T))\cr
189(COMMENT (ACTUALLY IT IS, EVEN THOUGH\cr
190\qquad\qquad IT SAYS IT ISN'T))\cr
191(FONTDIMEN\cr
192\qquad   (SLANT R -.25)\cr
193\qquad   (SPACE D 6)\cr
194\qquad   (SHRINK D 2)\cr
195\qquad   (STRETCH D 3)\cr
196\qquad   (XHEIGHT R 10.55)\cr
197\qquad   (QUAD D 18)\cr
198\qquad   )\cr
199(LIGTABLE\cr
200\qquad   (LABEL C f)\cr
201\qquad   (LIG C f O 200)\cr
202\qquad   (SKIP D 1)\cr
203\qquad   (LABEL O 200)\cr
204\qquad   (LIG C i O 201)\cr
205\qquad   (KRN O 51 R 1.5)\cr
206\qquad   (/LIG C ? C f)\cr
207\qquad   (STOP)\cr
208\qquad   )\cr
209(CHARACTER C f\cr
210\qquad   (CHARWD D 6)\cr
211\qquad   (CHARHT R 13.5)\cr
212\qquad   (CHARIC R 1.5)\cr
213\qquad   )\cr}}$$
214This example says that the font whose metric information is being described
215belongs to the hypothetical
216\.{NOVA} family; its face code is medium italic extended;
217and the characters appear in ASCII code positions. The design size is 10 points,
218and all other sizes in this \.{PL} file are given in units such that 18 units
219equals the design size. The font is slanted with a slope of $-.25$ (hence the
220letters actually slant backward---perhaps that is why the family name is
221\.{NOVA}). The normal space between words is 6 units (i.e., one third of
222the 18-unit design size), with glue that shrinks by 2 units or stretches by 3.
223The letters for which accents don't need to be raised or lowered are 10.55
224units high, and one em equals 18 units.
225
226The example ligature table is a bit trickier. It specifies that the
227letter \.f followed by another \.f is changed to code @'200, while
228code @'200 followed by \.i is changed to @'201; presumably codes @'200
229and @'201 represent the ligatures `ff' and `ffi'.  Moreover, in both cases
230\.f and @'200, if the following character is the code @'51 (which is a
231right parenthesis), an additional 1.5 units of space should be inserted
232before the @'51.  (The `\.{SKIP}~\.D~\.1' skips over one \.{LIG} or
233\.{KRN} command, which in this case is the second \.{LIG}; in this way
234two different ligature/kern programs can come together.)
235Finally, if either \.f or @'200 is followed by a question mark,
236the question mark is replaced by \.f and the ligature program is
237started over. (Thus, the character pair `\.{f?}' would actually become
238the ligature `ff', and `\.{ff?}' or `\.{f?f}' would become `fff'. To
239avoid this restart procedure, the \.{/LIG} command could be replaced
240by \.{/LIG>}; then `\.{f?} would become `f\kern0ptf' and `\.{f?f}'
241would become `f\kern0ptff'.)
242
243Character \.f itself is 6 units wide and 13.5 units tall, in this example.
244Its depth is zero (since \.{CHARDP} is not given), and its italic correction
245is 1.5 units.
246
247@ The example above illustrates most of the features found in \.{PL} files.
248Note that some property names, like \.{FAMILY} or \.{COMMENT}, take a
249string as their value; this string continues until the first unmatched
250right parenthesis. But most property names, like \.{DESIGNSIZE} and \.{SLANT}
251and \.{LABEL}, take a number as their value. This number can be expressed in
252a variety of ways, indicated by a prefixed code; \.D stands for decimal,
253\.H for hexadecimal, \.O for octal, \.R for real, \.C for character, and
254\.F for ``face.''  Other property names, like \.{LIG}, take two numbers as
255their value.  And still other names, like \.{FONTDIMEN} and \.{LIGTABLE} and
256\.{CHARACTER}, have more complicated values that involve property lists.
257
258A property name is supposed to be used only in an appropriate property
259list.  For example, \.{CHARWD} shouldn't occur on the outer level or
260within \.{FONTDIMEN}.
261
262The individual property-and-value pairs in a property list can appear in
263any order. For instance, `\.{SHRINK}' precedes `\.{STRETCH}' in the above
264example, although the \.{TFM} file always puts the stretch parameter first.
265One could even give the information about characters like `\.f' before
266specifying the number of units in the design size, or before specifying the
267ligature and kerning table. However, the \.{LIGTABLE} itself is an exception
268to this rule; the individual elements of the \.{LIGTABLE} property list
269can be reordered only to a certain extent without changing the meaning
270of that table.
271
272If property-and-value pairs are omitted, a default value is used. For example,
273we have already noted that the default for \.{CHARDP} is zero. The default
274for {\sl every\/} numeric value is, in fact, zero, unless otherwise stated
275below.
276
277If the same property name is used more than once, \.{PLtoTF} will not notice
278the discrepancy; it simply uses the final value given. Once again, however, the
279\.{LIGTABLE} is an exception to this rule; \.{PLtoTF} will complain if there
280is more than one label for some character. And of course many of the
281entries in the \.{LIGTABLE} property list have the same property name.
282
283From these rules, you can guess (correctly) that \.{PLtoTF} operates in four
284main steps. First it assigns the default values to all properties; then it scans
285through the \.{PL} file, changing property values as new ones are seen; then
286it checks the information and corrects any problems; and finally it outputs
287the \.{TFM} file.
288
289@ Instead of relying on a hypothetical example, let's consider a complete
290grammar for \.{PL} files. At the outer level, the following property names
291are valid:
292
293\yskip\hang\.{CHECKSUM} (four-byte value). The value, which should be a
294nonnegative integer less than $2^{32}$, is used to identify a particular
295version of a font; it should match the check sum value stored with the font
296itself. An explicit check sum of zero is used to bypass
297check sum testing. If no checksum is specified in the \.{PL} file,
298\.{PLtoTF} will compute the checksum that \MF\ would compute from the
299same data.
300
301\yskip\hang\.{DESIGNSIZE} (numeric value, default is 10). The value, which
302should be a real number in the range |1.0<=x<2048|, represents the default
303amount by which all quantities will be scaled if the font is not loaded
304with an `\.{at}' specification. For example, if one says
305`\.{\\font\\A=cmr10 at 15pt}' in \TeX\ language, the design size in the \.{TFM}
306file is ignored and effectively replaced by 15 points; but if one simply
307says `\.{\\font\\A=cmr10}' the stated design size is used. This quantity is
308always in units of printer's points.
309
310\yskip\hang\.{DESIGNUNITS} (numeric value, default is 1). The value
311should be a positive real number; it says how many units equals the design
312size (or the eventual `\.{at}' size, if the font is being scaled). For
313example, suppose you have a font that has been digitized with 600 pixels per
314em, and the design size is one em; then you could say `\.{(DESIGNUNITS R 600)}'
315if you wanted to give all of your measurements in units of pixels.
316
317\yskip\hang\.{CODINGSCHEME} (string value, default is `\.{UNSPECIFIED}').
318The string should not contain parentheses, and its length must be less than 40.
319It identifies the correspondence between the numeric codes and font characters.
320(\TeX\ ignores this information, but other software programs make use of it.)
321
322\yskip\hang\.{FAMILY} (string value, default is `\.{UNSPECIFIED}').
323The string should not contain parentheses, and its length must be less than 20.
324It identifies the name of the family to which this font belongs, e.g.,
325`\.{HELVETICA}'.  (\TeX\ ignores this information; but it is needed, for
326example, when converting \.{DVI} files to \.{PRESS} files for Xerox
327equipment.)
328
329\yskip\hang\.{FACE} (one-byte value). This number, which must lie between
3300 and 255 inclusive, is a subsidiary ident\-ifi\-ca\-tion of the font within its
331family. For example, bold italic condensed fonts might have the same family name
332as light roman extended fonts, differing only in their face byte.  (\TeX\
333ignores this information; but it is needed, for example, when converting
334\.{DVI} files to \.{PRESS} files for Xerox equipment.)
335
336\yskip\hang\.{SEVENBITSAFEFLAG} (string value, default is `\.{FALSE}'). The
337value should start with either `\.T' (true) or `\.F' (false). If true, character
338codes less than 128 cannot lead to codes of 128 or more via ligatures or
339charlists or extensible characters. (\TeX82 ignores this flag, but older
340versions of \TeX\ would only accept \.{TFM} files that were seven-bit safe.)
341\.{PLtoTF} computes the correct value of this flag and gives an error message
342only if a claimed ``true'' value is incorrect.
343
344\yskip\hang\.{HEADER} (a one-byte value followed by a four-byte value).
345The one-byte value should be between 18 and a maximum limit that can be
346raised or lowered depending on the compile-time setting of |max_header_bytes|.
347The four-byte value goes into the header word whose index is the one-byte
348value; for example, to set |header[18]:=1|, one may write
349`\.{(HEADER D 18 O 1)}'. This notation is used for header information that
350is presently unnamed. (\TeX\ ignores it.)
351
352\yskip\hang\.{FONTDIMEN} (property list value). See below for the names
353allowed in this property list.
354
355\yskip\hang\.{LIGTABLE} (property list value). See below for the rules
356about this special kind of property list.
357
358\yskip\hang\.{BOUNDARYCHAR} (integer value). If this character appears in
359a \.{LIGTABLE} command, it matches ``end of word'' as well as itself.
360If no boundary character is given and no \.{LABEL} \.{BOUNDARYCHAR} occurs
361within \.{LIGTABLE}, word boundaries will not affect ligatures or kerning.
362
363\yskip\hang\.{CHARACTER}. The value is an integer followed by
364a property list. The integer represents the number of a character that is
365present in the font; the property list of a character is defined below.
366The default is an empty property list.
367
368@ Numeric property list values can be given in various forms identified by
369a prefixed letter.
370
371\yskip\hang\.C denotes an ASCII character, which should be a standard visible
372character that is not a parenthesis. The numeric value will therefore be
373between @'41 and @'176 but not @'50 or @'51.
374
375\yskip\hang\.D denotes a decimal integer, which must be nonnegative and
376less than 256. (Use \.R for larger values or for negative values.)
377
378\yskip\hang\.F denotes a three-letter Xerox face code; the admissible codes
379are \.{MRR}, \.{MIR}, \.{BRR}, \.{BIR}, \.{LRR}, \.{LIR}, \.{MRC}, \.{MIC},
380\.{BRC}, \.{BIC}, \.{LRC}, \.{LIC}, \.{MRE}, \.{MIE}, \.{BRE}, \.{BIE},
381\.{LRE}, and \.{LIE}, denoting the integers 0 to 17, respectively.
382
383\yskip\hang\.O denotes an unsigned octal integer, which must be less than
384$2^{32}$, i.e., at most `\.{O 37777777777}'.
385
386\yskip\hang\.H denotes an unsigned hexadecimal integer, which must be less than
387$2^{32}$, i.e., at most `\.{H FFFFFFFF}'.
388
389\yskip\hang\.R denotes a real number in decimal notation, optionally preceded
390by a `\.+' or `\.-' sign, and optionally including a decimal point. The
391absolute value must be less than 2048.
392
393@ The property names allowed in a \.{FONTDIMEN} property list correspond to
394various \TeX\ parameters, each of which has a (real) numeric value. All
395of the parameters except \.{SLANT} are in design units. The admissible
396names are \.{SLANT}, \.{SPACE}, \.{STRETCH}, \.{SHRINK}, \.{XHEIGHT},
397\.{QUAD}, \.{EXTRASPACE}, \.{NUM1}, \.{NUM2}, \.{NUM3}, \.{DENOM1},
398\.{DENOM2}, \.{SUP1}, \.{SUP2}, \.{SUP3}, \.{SUB1}, \.{SUB2}, \.{SUPDROP},
399\.{SUBDROP}, \.{DELIM1}, \.{DELIM2}, and \.{AXISHEIGHT}, for parameters
4001~to~22. The alternate names \.{DEFAULTRULETHICKNESS},
401\.{BIGOPSPACING1}, \.{BIGOPSPACING2}, \.{BIGOPSPACING3},
402\.{BIGOPSPACING4}, and \.{BIGOPSPACING5}, may also be used for parameters
4038 to 13.
404
405The notation `\.{PARAMETER} $n$' provides another way to specify the
406$n$th parameter; for example, `\.{(PARAMETER} \.{D 1 R -.25)}' is another way
407to specify that the \.{SLANT} is $-0.25$. The value of $n$ must be positive
408and less than |max_param_words|.
409
410@ The elements of a \.{CHARACTER} property list can be of six different types.
411
412\yskip\hang\.{CHARWD} (real value) denotes the character's width in
413design units.
414
415\yskip\hang\.{CHARHT} (real value) denotes the character's height in
416design units.
417
418\yskip\hang\.{CHARDP} (real value) denotes the character's depth in
419design units.
420
421\yskip\hang\.{CHARIC} (real value) denotes the character's italic correction in
422design units.
423
424\yskip\hang\.{NEXTLARGER} (integer value), specifies the character that
425follows the present one in a ``charlist.'' The value must be the number of a
426character in the font, and there must be no infinite cycles of supposedly
427larger and larger characters.
428
429\yskip\hang\.{VARCHAR} (property list value), specifies an extensible character.
430This option and \.{NEXTLARGER} are mutually exclusive; i.e., they cannot
431both be used within the same \.{CHARACTER} list.
432
433\yskip\noindent
434The elements of a \.{VARCHAR} property list are either \.{TOP}, \.{MID},
435\.{BOT} or \.{REP}; the values are integers, which must be zero or the number
436of a character in the font. A zero value for \.{TOP}, \.{MID}, or \.{BOT} means
437that the corresponding piece of the extensible character is absent. A nonzero
438value, or a \.{REP} value of zero, denotes the character code used to make
439up the top, middle, bottom, or replicated piece of an extensible character.
440
441@ A \.{LIGTABLE} property list contains elements of four kinds, specifying a
442program in a simple command language that \TeX\ uses for ligatures and kerns.
443If several \.{LIGTABLE} lists appear, they are effectively concatenated into
444a single list.
445
446\yskip\hang\.{LABEL} (integer value) means that the program for the
447stated character value starts here. The integer must be the number of a
448character in the font; its \.{CHARACTER} property list must not have a
449\.{NEXTLARGER} or \.{VARCHAR} field. At least one \.{LIG} or \.{KRN} step
450must follow.
451
452\yskip\hang\.{LABEL} \.{BOUNDARYCHAR} means that the program for
453beginning-of-word ligatures starts here.
454
455\yskip\hang\.{LIG} (two integer values). The instruction `\.{(LIG} $c$ $r$\.)'
456means, ``If the next character is $c$, then insert character~$r$ and
457possibly delete the current character and/or~$c$;
458otherwise go on to the next instruction.''
459Characters $r$ and $c$ must be present in the font. \.{LIG} may be immediately
460preceded or followed by a slash, and then immediately followed by \.>
461characters not exceeding the number of slashes. Thus there are eight
462possible forms:
463$$\hbox to .8\hsize{\.{LIG}\hfil\.{/LIG}\hfil\.{/LIG>}\hfil
464\.{LIG/}\hfil\.{LIG/>}\hfil\.{/LIG/}\hfil\.{/LIG/>}\hfil\.{/LIG/>>}}$$
465The slashes specify retention of the left or right original character; the
466\.> signs specify passing over the result without further ligature processing.
467
468\yskip\hang\.{KRN} (an integer value and a real value). The instruction
469`\.{(KRN} $c$ $r$\.)' means, ``If the next character is $c$, then insert
470a blank space of width $r$ between the current character character and $c$;
471otherwise go on to the next intruction.'' The value of $r$, which is in
472design units, is often negative. Character code $c$ must exist
473in the font.
474
475\yskip\hang\.{STOP} (no value). This instruction ends a ligature/kern program.
476It must follow either a \.{LIG} or \.{KRN} instruction, not a \.{LABEL}
477or \.{STOP} or \.{SKIP}.
478
479\yskip\hang\.{SKIP} (value in the range |0..127|). This instruction specifies
480continuation of a ligature/kern program after the specified number of \.{LIG}
481or \.{KRN} steps has been skipped over. The number of subsequent \.{LIG} and
482\.{KRN} instructions must therefore exceed this specified amount.
483
484@ In addition to all these possibilities, the property name \.{COMMENT} is
485allowed in any property list. Such comments are ignored.
486
487@ So that is what \.{PL} files hold. The next question is, ``What about
488\.{TFM} files?'' A complete answer to that question appears in the
489documentation of the companion program, \.{TFtoPL}, so it will not
490be repeated here. Suffice it to say that a \.{TFM} file stores all of the
491relevant font information in a sequence of 8-bit bytes. The number of
492bytes is always a multiple of 4, so we could regard the \.{TFM} file
493as a sequence of 32-bit words; but \TeX\ uses the byte interpretation,
494and so does \.{PLtoTF}. Note that the bytes are considered to be unsigned
495numbers.
496
497@<Glob...@>=
498@!tfm_file:packed file of 0..255;
499
500@ On some systems you may have to do something special to write a
501packed file of bytes. For example, the following code didn't work
502when it was first tried at Stanford, because packed files have to be
503opened with a special switch setting on the \PASCAL\ that was used.
504@^system dependencies@>
505
506@<Set init...@>=
507rewrite(tfm_file);
508
509@* Basic input routines.
510For the purposes of this program, a |byte| is an unsigned 16-bit quantity,
511and an |ASCII_code| is an integer between @'40 and @'177. Such ASCII codes
512correspond to one-character constants like \.{"A"} in \.{WEB} language.
513
514@<Types...@>=
515@!byte=0..65535; {unsigned sixteen-bit quantity}
516@!ASCII_code=@'40..@'177; {standard ASCII code numbers}
517
518@ One of the things \.{PLtoTF} has to do is convert characters of strings
519to ASCII form, since that is the code used for the family name and the
520coding scheme in a \.{TFM} file. An array |xord| is used to do the
521conversion from |char|; the method below should work with little or no change
522on most \PASCAL\ systems.
523@^system dependencies@>
524
525@d first_ord=0 {ordinal number of the smallest element of |char|}
526@d last_ord=127 {ordinal number of the largest element of |char|}
527
528@<Global...@>=
529@!xord:array[char] of ASCII_code; {conversion table}
530
531@ @<Local variables for init...@>=
532@!k:integer; {all-purpose initialization index}
533
534@ Characters that should not appear in \.{PL} files (except in comments)
535are mapped into @'177.
536
537@d invalid_code=@'177 {code deserving an error message}
538
539@<Set init...@>=
540for k:=first_ord to last_ord do xord[chr(k)]:=invalid_code;
541xord[' ']:=" "; xord['!']:="!"; xord['"']:=""""; xord['#']:="#";
542xord['$']:="$"; xord['%']:="%"; xord['&']:="&"; xord['''']:="'";
543xord['(']:="("; xord[')']:=")"; xord['*']:="*"; xord['+']:="+"; xord[',']:=",";
544xord['-']:="-"; xord['.']:="."; xord['/']:="/"; xord['0']:="0"; xord['1']:="1";
545xord['2']:="2"; xord['3']:="3"; xord['4']:="4"; xord['5']:="5"; xord['6']:="6";
546xord['7']:="7"; xord['8']:="8"; xord['9']:="9"; xord[':']:=":"; xord[';']:=";";
547xord['<']:="<"; xord['=']:="="; xord['>']:=">"; xord['?']:="?";
548xord['@@']:="@@"; xord['A']:="A"; xord['B']:="B"; xord['C']:="C";
549xord['D']:="D"; xord['E']:="E"; xord['F']:="F"; xord['G']:="G"; xord['H']:="H";
550xord['I']:="I"; xord['J']:="J"; xord['K']:="K"; xord['L']:="L"; xord['M']:="M";
551xord['N']:="N"; xord['O']:="O"; xord['P']:="P"; xord['Q']:="Q"; xord['R']:="R";
552xord['S']:="S"; xord['T']:="T"; xord['U']:="U"; xord['V']:="V"; xord['W']:="W";
553xord['X']:="X"; xord['Y']:="Y"; xord['Z']:="Z"; xord['[']:="["; xord['\']:="\";
554xord[']']:="]"; xord['^']:="^"; xord['_']:="_"; xord['`']:="`"; xord['a']:="a";
555xord['b']:="b"; xord['c']:="c"; xord['d']:="d"; xord['e']:="e"; xord['f']:="f";
556xord['g']:="g"; xord['h']:="h"; xord['i']:="i"; xord['j']:="j"; xord['k']:="k";
557xord['l']:="l"; xord['m']:="m"; xord['n']:="n"; xord['o']:="o"; xord['p']:="p";
558xord['q']:="q"; xord['r']:="r"; xord['s']:="s"; xord['t']:="t"; xord['u']:="u";
559xord['v']:="v"; xord['w']:="w"; xord['x']:="x"; xord['y']:="y"; xord['z']:="z";
560xord['{']:="{"; xord['|']:="|"; xord['}']:="}"; xord['~']:="~";
561
562@ In order to help catch errors of badly nested parentheses, \.{PLtoTF}
563assumes that the user will begin each line with a number of blank spaces equal
564to some constant times the number of open parentheses at the beginning of
565that line. However, the program doesn't know in advance what the constant
566is, nor does it want to print an error message on every line for a user
567who has followed no consistent pattern of indentation.
568
569Therefore the following strategy is adopted: If the user has been consistent
570with indentation for ten or more lines, an indentation error will be
571reported. The constant of indentation is reset on every line that should
572have nonzero indentation.
573
574@<Glob...@>=
575@!line:integer; {the number of the current line}
576@!good_indent:integer; {the number of lines since the last bad indentation}
577@!indent: integer; {the number of spaces per open parenthesis, zero if unknown}
578@!level: integer; {the current number of open parentheses}
579
580@ @<Set init...@>=
581line:=0; good_indent:=0; indent:=0; level:=0;
582
583@ The input need not really be broken into lines of any maximum length, and
584we could read it character by character without any buffering. But we shall
585place it into a small buffer so that offending lines can be displayed in error
586messages.
587
588@<Glob...@>=
589@!left_ln,@!right_ln:boolean; {are the left and right ends of the buffer
590  at end-of-line marks?}
591@!limit:0..buf_size; {position of the last character present in the buffer}
592@!loc:0..buf_size; {position of the last character read in the buffer}
593@!buffer:array[1..buf_size] of char;
594@!input_has_ended:boolean; {there is no more input to read}
595
596@ @<Set init...@>=
597limit:=0; loc:=0; left_ln:=true; right_ln:=true; input_has_ended:=false;
598
599@ Just before each  \.{CHARACTER} property list is evaluated, the character
600code is printed in octal notation. Up to eight such codes appear on a line;
601so we have a variable to keep track of how many are currently there.
602
603@<Glob...@>=
604@!chars_on_line:0..9; {the number of characters printed on the current line}
605
606@ @<Set init...@>=
607chars_on_line:=0;
608
609@ The following routine prints an error message and an indication of
610where the error was detected. The error message should not include any
611final punctuation, since this procedure supplies its own.
612
613@d err_print(#)==begin if chars_on_line>0 then print_ln(' ');
614  print(#); show_error_context;
615  end
616
617@p procedure show_error_context; {prints the current scanner location}
618var k:0..buf_size; {an index into |buffer|}
619begin print_ln(' (line ',line:1,').');
620if not left_ln then print('...');
621for k:=1 to loc do print(buffer[k]); {print the characters already scanned}
622print_ln(' ');
623if not left_ln then print('   ');
624for k:=1 to loc do print(' '); {space out the second line}
625for k:=loc+1 to limit do print(buffer[k]); {print the characters yet unseen}
626if right_ln then print_ln(' ')@+else print_ln('...');
627chars_on_line:=0;
628end;
629
630@ Here is a procedure that does the right thing when we are done
631reading the present contents of the buffer. It keeps |buffer[buf_size]|
632empty, in order to avoid range errors on certain \PASCAL\ compilers.
633
634An infinite sequence of right parentheses is placed at the end of the
635file, so that the program is sure to get out of whatever level of nesting
636it is in.
637
638On some systems it is desirable to modify this code so that tab marks
639in the buffer are replaced by blank spaces. (Simply setting
640|xord[chr(@'11)]:=" "| would not work; for example, two-line
641error messages would not come out properly aligned.)
642@^system dependencies@>
643
644@p procedure fill_buffer;
645begin left_ln:=right_ln; limit:=0; loc:=0;
646if left_ln then
647  begin if line>0 then read_ln(pl_file);
648  incr(line);
649  end;
650if eof(pl_file) then
651  begin limit:=1; buffer[1]:=')'; right_ln:=false; input_has_ended:=true;
652  end
653else  begin while (limit<buf_size-2)and(not eoln(pl_file)) do
654    begin incr(limit); read(pl_file,buffer[limit]);
655    end;
656  buffer[limit+1]:=' '; right_ln:=eoln(pl_file);
657  if right_ln then begin incr(limit); buffer[limit+1]:=' ';
658    end;
659  if left_ln then @<Set |loc| to the number of leading blanks in
660    the buffer, and check the indentation@>;
661  end;
662end;
663
664@ The interesting part about |fill_buffer| is the part that learns what
665indentation conventions the user is following, if any.
666
667@d bad_indent(#)==begin if good_indent>=10 then err_print(#);
668  good_indent:=0; indent:=0;
669  end
670
671@<Set |loc|...@>=
672begin while (loc<limit)and(buffer[loc+1]=' ') do incr(loc);
673if loc<limit then
674  begin if level=0 then
675    if loc=0 then incr(good_indent)
676    else bad_indent('Warning: Indented line occurred at level zero')
677@.Warning: Indented line...@>
678  else if indent=0 then
679    if loc mod level=0 then
680      begin indent:=loc div level; good_indent:=1;
681      end
682    else good_indent:=0
683  else if indent*level=loc then incr(good_indent)
684  else bad_indent('Warning: Inconsistent indentation; ',
685@.Warning: Inconsistent indentation...@>
686    'you are at parenthesis level ',level:1);
687  end;
688end
689
690@* Basic scanning routines.
691The global variable |cur_char| holds the ASCII code corresponding to the
692character most recently read from the input buffer, or to a character that
693has been substituted for the real one.
694
695@<Global...@>=
696@!cur_char:ASCII_code; {we have just read this}
697
698@ Here is a procedure that sets |cur_char| to an ASCII code for the
699next character of input, if that character is a letter or digit or slash
700or \.>. Otherwise
701it sets |cur_char:=" "|, and the input system will be poised to reread the
702character that was rejected, whether or not it was a space.
703Lower case letters are converted to upper case.
704
705@p procedure get_keyword_char;
706begin while (loc=limit)and(not right_ln) do fill_buffer;
707if loc=limit then cur_char:=" " {end-of-line counts as a delimiter}
708else  begin cur_char:=xord[buffer[loc+1]];
709  if cur_char>="a" then cur_char:=cur_char-@'40;
710  if ((cur_char>="0")and(cur_char<="9")) then incr(loc)
711  else if ((cur_char>="A")and(cur_char<="Z")) then incr(loc)
712  else if cur_char="/" then incr(loc)
713  else if cur_char=">" then incr(loc)
714  else cur_char:=" ";
715  end;
716end;
717
718@ The following procedure sets |cur_char| to the next character code,
719and converts lower case to upper case. If the character is a left or
720right parenthesis, it will not be ``digested''; the character will
721be read again and again, until the calling routine does something
722like `|incr(loc)|' to get past it. Such special treatment of parentheses
723insures that the structural information they contain won't be lost in
724the midst of other error recovery operations.
725
726@d backup==begin if (cur_char>")")or(cur_char<"(") then decr(loc);
727  end {undoes the effect of |get_next|}
728
729@p procedure get_next; {sets |cur_char| to next, balks at parentheses}
730begin while loc=limit do fill_buffer;
731incr(loc); cur_char:=xord[buffer[loc]];
732if cur_char>="a" then
733  if cur_char<="z" then cur_char:=cur_char-@'40 {uppercasify}
734  else  begin if cur_char=invalid_code then
735      begin err_print('Illegal character in the file');
736@.Illegal character...@>
737      cur_char:="?";
738      end;
739    end
740else if (cur_char<=")")and(cur_char>="(") then decr(loc);
741end;
742
743@ The next procedure is used to ignore the text of a comment, or to pass over
744erroneous material. As such, it has the privilege of passing parentheses.
745It stops after the first right parenthesis that drops the level below
746the level in force when the procedure was called.
747
748@p procedure skip_to_end_of_item;
749var l:integer; {initial value of |level|}
750begin l:=level;
751while level>=l do
752  begin while loc=limit do fill_buffer;
753  incr(loc);
754  if buffer[loc]=')' then decr(level)
755  else if buffer[loc]='(' then incr(level);
756  end;
757if input_has_ended then err_print('File ended unexpectedly: No closing ")"');
758@.File ended unexpectedly...@>
759cur_char:=" "; {now the right parenthesis has been read and digested}
760end;
761
762@ Sometimes we merely want to skip past characters in the input until we
763reach a left or a right parenthesis. For example, we do this whenever we
764have finished scanning a property value and we hope that a right parenthesis
765is next (except for possible blank spaces).
766
767@d skip_to_paren==repeat get_next@;@+ until (cur_char="(")or(cur_char=")")
768@d skip_error(#)==begin err_print(#); skip_to_paren;
769  end {this gets to the right parenthesis if something goes wrong}
770@d flush_error(#)==begin err_print(#); skip_to_end_of_item;
771  end {this gets past the right parenthesis if something goes wrong}
772
773@ After a property value has been scanned, we want to move just past the
774right parenthesis that should come next in the input (except for possible
775blank spaces).
776
777@p procedure finish_the_property; {do this when the value has been scanned}
778begin while cur_char=" " do get_next;
779if cur_char<>")" then err_print('Junk after property value will be ignored');
780@.Junk after property value...@>
781skip_to_end_of_item;
782end;
783
784@* Scanning property names.
785We have to figure out the meaning of names that appear in the \.{PL} file,
786by looking them up in a dictionary of known keywords. Keyword number $n$
787appears in locations |start[n]| through |start[n+1]-1| of an array called
788|dictionary|.
789
790@d max_name_index=300 {upper bound on the number of keywords}
791@d max_letters=3000 {upper bound on the total length of all keywords}
792
793@<Global...@>=
794@!start:array[1..max_name_index] of 0..max_letters;
795@!dictionary:array[0..max_letters] of ASCII_code;
796@!start_ptr:0..max_name_index; {the first available place in |start|}
797@!dict_ptr:0..max_letters; {the first available place in |dictionary|}
798
799@ @<Set init...@>=
800start_ptr:=1; start[1]:=0; dict_ptr:=0;
801
802@ When we are looking for a name, we put it into the |cur_name| array.
803When we have found it, the corresponding |start| index will go into
804the global variable |name_ptr|.
805
806@d longest_name=20 {length of \.{DEFAULTRULETHICKNESS}}
807
808@<Glob...@>=
809@!cur_name:array[1..longest_name] of ASCII_code; {a name to look up}
810@!name_length:0..longest_name; {its length}
811@!name_ptr:0..max_name_index; {its ordinal number in the dictionary}
812
813@ A conventional hash table with linear probing (cf.\ Algorithm 6.4L
814in {\sl The Art of Computer Pro\-gram\-ming\/}) is used for the dictionary
815operations. If |nhash[h]=0|, the table position is empty, otherwise |nhash[h]|
816points into the |start| array.
817
818@d hash_prime=307 {size of the hash table}
819
820@<Glob...@>=
821@!nhash:array[0..hash_prime-1] of 0..max_name_index;
822@!cur_hash:0..hash_prime-1; {current position in the hash table}
823
824@ @<Local...@>=
825@!h:0..hash_prime-1; {runs through the hash table}
826
827@ @<Set init...@>=
828for h:=0 to hash_prime-1 do nhash[h]:=0;
829
830@ Since there is no chance of the hash table overflowing, the procedure
831is very simple. After |lookup| has done its work, |cur_hash| will point
832to the place where the given name was found, or where it should be inserted.
833
834@p procedure lookup; {finds |cur_name| in the dictionary}
835var k:0..longest_name; {index into |cur_name|}
836@!j:0..max_letters; {index into |dictionary|}
837@!not_found:boolean; {clumsy thing necessary to avoid |goto| statement}
838@!cur_hash_reset:boolean;
839begin @<Compute the hash code, |cur_hash|, for |cur_name|@>;
840not_found:=true;
841cur_hash_reset:=false;
842while not_found do
843  begin if (cur_hash=0) and (cur_hash_reset) then
844    not_found:=false
845  else begin
846    if cur_hash=0 then begin
847      cur_hash:=hash_prime-1;
848      cur_hash_reset:=true
849      end
850    else decr(cur_hash);
851    if nhash[cur_hash]=0 then not_found:=false
852    else begin
853      j:=start[nhash[cur_hash]];
854      if start[nhash[cur_hash]+1]=j+name_length then begin
855        not_found:=false;
856        for k:=1 to name_length do
857          if dictionary[j+k-1]<>cur_name[k] then not_found:=true;
858        end
859      end
860    end
861  end;
862name_ptr:=nhash[cur_hash];
863end;
864
865@ @<Compute the hash...@>=
866cur_hash:=cur_name[1];
867for k:=2 to name_length do
868  cur_hash:=(cur_hash+cur_hash+cur_name[k]) mod hash_prime
869
870@ The ``meaning'' of the keyword that begins at |start[k]| in the
871dictionary is kept in |equiv[k]|. The numeric |equiv| codes are given
872symbolic meanings by the following definitions.
873
874@d comment_code=0
875@#{property names at the outer level}
876@d check_sum_code=1
877@d design_size_code=2
878@d design_units_code=3
879@d coding_scheme_code=4
880@d family_code=5
881@d face_code=6
882@d seven_bit_safe_flag_code=7
883@d header_code= 8
884@d font_dimen_code=9
885@d lig_table_code=10
886@d boundary_char_code=11
887@d character_code=14
888@d font_dir_code=15
889@d n_font_dir_code=16
890@d ofm_level_code=17
891@d font_rule_code=18
892@d font_glue_code=19
893@d font_penalty_code=20
894@d font_mvalue_code=21
895@d font_fvalue_code=22
896@d font_ivalue_code=23
897@d char_repeat_code=24
898@#{property names in a FONTDIMEN list}
899@d parameter_code=30
900@#{property names in a CHARACTER list}
901@d char_info_code=70
902@d width=1
903@d height=2
904@d depth=3
905@d italic=4
906@d sec_width=5
907@d sec_height=6
908@d sec_depth=7
909@d sec_italic=8
910@d accent=9
911@d prim_top_axis=10
912@d prim_top_axis_bis=11
913@d prim_bot_axis=12
914@d prim_bot_axis_bis=13
915@d prim_mid_hor=14
916@d prim_mid_vert=15
917@d prim_base_slant=16
918@d sec_top_axis=17
919@d sec_top_axis_bis=18
920@d sec_bot_axis=19
921@d sec_bot_axis_bis=20
922@d sec_mid_hor=21
923@d sec_mid_vert=22
924@d sec_base_slant=23
925@d char_wd_code=char_info_code+width
926@d char_ht_code=char_info_code+height
927@d char_dp_code=char_info_code+depth
928@d char_ic_code=char_info_code+italic
929@d sec_width_code=char_info_code+sec_width
930@d sec_height_code=char_info_code+sec_height
931@d sec_depth_code=char_info_code+sec_depth
932@d sec_italic_code=char_info_code+sec_italic
933@d accent_code=char_info_code+accent
934@d prim_top_axis_code=char_info_code+prim_top_axis
935@d prim_top_axis_bis_code=char_info_code+prim_top_axis_bis
936@d prim_bot_axis_code=char_info_code+prim_bot_axis
937@d prim_bot_axis_bis_code=char_info_code+prim_bot_axis_bis
938@d prim_mid_hor_code=char_info_code+prim_mid_hor
939@d prim_mid_vert_code=char_info_code+prim_mid_vert
940@d prim_base_slant_code=char_info_code+prim_base_slant
941@d sec_top_axis_code=char_info_code+sec_top_axis
942@d sec_top_axis_bis_code=char_info_code+sec_top_axis_bis
943@d sec_bot_axis_code=char_info_code+sec_bot_axis
944@d sec_bot_axis_bis_code=char_info_code+sec_bot_axis_bis
945@d sec_mid_hor_code=char_info_code+sec_mid_hor
946@d sec_mid_vert_code=char_info_code+sec_mid_vert
947@d sec_base_slant_code=char_info_code+sec_base_slant
948@d next_larger_code=100
949@d char_ivalue_code=111
950@d char_fvalue_code=112
951@d char_mvalue_code=113
952@d char_rule_code=114
953@d char_glue_code=115
954@d char_penalty_code=116
955@d var_char_code=117
956@#{property names in a LIGTABLE list}
957@d label_code=130
958@d stop_code=131
959@d skip_code=132
960@d krn_code=133
961@d lig_code=134
962@d clabel_code=150
963@d cpen_code=151
964@d cglue_code=152
965@d cpenglue_code=153
966@d ckrn_code=154
967@#{property names in a FONTRULE list}
968@d rule_code=161
969@d rule_width_code=162
970@d rule_height_code=163
971@d rule_depth_code=164
972@#{property names in a FONTGLUE list}
973@d glue_code=171
974@d glue_type_code=172
975@d glue_stretch_order_code=173
976@d glue_shrink_order_code=174
977@d glue_width_code=175
978@d glue_stretch_code=176
979@d glue_shrink_code=177
980@d glue_char_code=178
981@d glue_rule_code=179
982@#{property names in a FONTPENALTY list}
983@d penalty_code=191
984@d penalty_val_code=192
985@#{property names in a FONTMVALUE list}
986@d mvalue_code=193
987@d mvalue_val_code=194
988@#{property names in a FONTFVALUE list}
989@d fvalue_code=195
990@d fvalue_val_code=196
991@#{property names in a FONTIVALUE list}
992@d ivalue_code=197
993@d ivalue_val_code=198
994
995@<Glo...@>=
996@!equiv:array[0..max_name_index] of byte;
997@!cur_code:byte; {equivalent most recently found in |equiv|}
998
999@ We have to get the keywords into the hash table and into the dictionary in
1000the first place (sigh). The procedure that does this has the desired
1001|equiv| code as a parameter. In order to facilitate \.{WEB} macro writing
1002for the initialization, the keyword being initialized is placed into the
1003last positions of |cur_name|, instead of the first positions.
1004
1005@p procedure enter_name(v:byte); {|cur_name| goes into the dictionary}
1006var k:0..longest_name;
1007begin for k:=1 to name_length do
1008  cur_name[k]:=cur_name[k+longest_name-name_length];
1009{now the name has been shifted into the correct position}
1010lookup; {this sets |cur_hash| to the proper insertion place}
1011nhash[cur_hash]:=start_ptr; equiv[start_ptr]:=v;
1012for k:=1 to name_length do
1013  begin dictionary[dict_ptr]:=cur_name[k]; incr(dict_ptr);
1014  end;
1015incr(start_ptr); start[start_ptr]:=dict_ptr;
1016end;
1017
1018@ Here are the macros to load a name of up to 20 letters into the
1019dictionary. For example, the macro |load5| is used for five-letter keywords.
1020
1021@d tail(#)==enter_name(#)
1022@d t20(#)==cur_name[20]:=#;tail
1023@d t19(#)==cur_name[19]:=#;t20
1024@d t18(#)==cur_name[18]:=#;t19
1025@d t17(#)==cur_name[17]:=#;t18
1026@d t16(#)==cur_name[16]:=#;t17
1027@d t15(#)==cur_name[15]:=#;t16
1028@d t14(#)==cur_name[14]:=#;t15
1029@d t13(#)==cur_name[13]:=#;t14
1030@d t12(#)==cur_name[12]:=#;t13
1031@d t11(#)==cur_name[11]:=#;t12
1032@d t10(#)==cur_name[10]:=#;t11
1033@d t9(#)==cur_name[9]:=#;t10
1034@d t8(#)==cur_name[8]:=#;t9
1035@d t7(#)==cur_name[7]:=#;t8
1036@d t6(#)==cur_name[6]:=#;t7
1037@d t5(#)==cur_name[5]:=#;t6
1038@d t4(#)==cur_name[4]:=#;t5
1039@d t3(#)==cur_name[3]:=#;t4
1040@d t2(#)==cur_name[2]:=#;t3
1041@d t1(#)==cur_name[1]:=#;t2
1042@d load2==name_length:=2;t19
1043@d load3==name_length:=3;t18
1044@d load4==name_length:=4;t17
1045@d load5==name_length:=5;t16
1046@d load6==name_length:=6;t15
1047@d load7==name_length:=7;t14
1048@d load8==name_length:=8;t13
1049@d load9==name_length:=9;t12
1050@d load10==name_length:=10;t11
1051@d load11==name_length:=11;t10
1052@d load12==name_length:=12;t9
1053@d load13==name_length:=13;t8
1054@d load14==name_length:=14;t7
1055@d load15==name_length:=15;t6
1056@d load16==name_length:=16;t5
1057@d load17==name_length:=17;t4
1058@d load18==name_length:=18;t3
1059@d load19==name_length:=19;t2
1060@d load20==name_length:=20;t1
1061
1062@ (Thank goodness for keyboard macros in the text editor used to create this
1063\.{WEB} file.)
1064
1065@<Enter all of the names and their equivalents, except the parameter names@>=
1066equiv[0]:=comment_code; {this is used after unknown keywords}
1067load8("C")("H")("E")("C")("K")("S")("U")("M")(check_sum_code);@/
1068load10("D")("E")("S")("I")("G")("N")("S")("I")("Z")("E")(design_size_code);@/
1069load11("D")("E")("S")("I")("G")("N")
1070  ("U")("N")("I")("T")("S")(design_units_code);@/
1071load12("C")("O")("D")("I")("N")("G")
1072  ("S")("C")("H")("E")("M")("E")(coding_scheme_code);@/
1073load6("F")("A")("M")("I")("L")("Y")(family_code);@/
1074load4("F")("A")("C")("E")(face_code);@/
1075load16("S")("E")("V")("E")("N")("B")("I")("T")@/@t\hskip2em@>
1076  ("S")("A")("F")("E")("F")("L")("A")("G")(seven_bit_safe_flag_code);@/
1077load6("H")("E")("A")("D")("E")("R")(header_code);@/
1078load9("F")("O")("N")("T")("D")("I")("M")("E")("N")(font_dimen_code);@/
1079load8("L")("I")("G")("T")("A")("B")("L")("E")(lig_table_code);@/
1080load12("B")("O")("U")("N")("D")("A")("R")("Y")("C")("H")("A")("R")
1081  (boundary_char_code);@/
1082load9("C")("H")("A")("R")("A")("C")("T")("E")("R")(character_code);@/
1083load9("P")("A")("R")("A")("M")("E")("T")("E")("R")(parameter_code);@/
1084load6("C")("H")("A")("R")("W")("D")(char_wd_code);@/
1085load6("C")("H")("A")("R")("H")("T")(char_ht_code);@/
1086load6("C")("H")("A")("R")("D")("P")(char_dp_code);@/
1087load6("C")("H")("A")("R")("I")("C")(char_ic_code);@/
1088load5("S")("E")("C")("W")("D")(sec_width_code);@/
1089load5("S")("E")("C")("H")("T")(sec_height_code);@/
1090load5("S")("E")("C")("D")("P")(sec_depth_code);@/
1091load5("S")("E")("C")("I")("C")(sec_italic_code);@/
1092load6("A")("C")("C")("E")("N")("T")(accent_code);@/
1093load11("P")("R")("I")("M")("T")("O")("P")("A")("X")("I")("S")(prim_top_axis_code);@/
1094load14("P")("R")("I")("M")("T")("O")("P")("A")("X")("I")("S")("B")("I")("S")(prim_top_axis_bis_code);@/
1095load11("P")("R")("I")("M")("B")("O")("T")("A")("X")("I")("S")(prim_bot_axis_code);@/
1096load14("P")("R")("I")("M")("B")("O")("T")("A")("X")("I")("S")("B")("I")("S")(prim_bot_axis_bis_code);@/
1097load10("P")("R")("I")("M")("M")("I")("D")("H")("O")("R")(prim_mid_hor_code);@/
1098load10("P")("R")("I")("M")("M")("I")("D")("V")("E")("R")(prim_mid_vert_code);@/
1099load13("P")("R")("I")("M")("B")("A")("S")("E")("S")("L")("A")("N")("T")(prim_base_slant_code);@/
1100load10("S")("E")("C")("T")("O")("P")("A")("X")("I")("S")(sec_top_axis_code);@/
1101load13("S")("E")("C")("T")("O")("P")("A")("X")("I")("S")("B")("I")("S")(sec_top_axis_bis_code);@/
1102load10("S")("E")("C")("B")("O")("T")("A")("X")("I")("S")(sec_bot_axis_code);@/
1103load13("S")("E")("C")("B")("O")("T")("A")("X")("I")("S")("B")("I")("S")(sec_bot_axis_bis_code);@/
1104load9("S")("E")("C")("M")("I")("D")("H")("O")("R")(sec_mid_hor_code);@/
1105load9("S")("E")("C")("M")("I")("D")("V")("E")("R")(sec_mid_vert_code);@/
1106load12("S")("E")("C")("B")("A")("S")("E")("S")("L")("A")("N")("T")(sec_base_slant_code);@/
1107load10("N")("E")("X")("T")("L")("A")("R")("G")("E")("R")(next_larger_code);@/
1108load7("V")("A")("R")("C")("H")("A")("R")(var_char_code);@/
1109load3("T")("O")("P")(var_char_code+1);@/
1110load3("M")("I")("D")(var_char_code+2);@/
1111load3("B")("O")("T")(var_char_code+3);@/
1112load3("R")("E")("P")(var_char_code+4);@/
1113load3("E")("X")("T")(var_char_code+4); {compatibility with older \.{PL} format}
1114load7("C")("O")("M")("M")("E")("N")("T")(comment_code);@/
1115load5("L")("A")("B")("E")("L")(label_code);@/
1116load4("S")("T")("O")("P")(stop_code);@/
1117load4("S")("K")("I")("P")(skip_code);@/
1118load3("K")("R")("N")(krn_code);@/
1119load3("L")("I")("G")(lig_code);@/
1120load4("/")("L")("I")("G")(lig_code+2);@/
1121load5("/")("L")("I")("G")(">")(lig_code+6);@/
1122load4("L")("I")("G")("/")(lig_code+1);@/
1123load5("L")("I")("G")("/")(">")(lig_code+5);@/
1124load5("/")("L")("I")("G")("/")(lig_code+3);@/
1125load6("/")("L")("I")("G")("/")(">")(lig_code+7);@/
1126load7("/")("L")("I")("G")("/")(">")(">")(lig_code+11);@/
1127load6("C")("L")("A")("B")("E")("L")(clabel_code);@/
1128load4("C")("P")("E")("N")(cpen_code);@/
1129load5("C")("G")("L")("U")("E")(cglue_code);@/
1130load8("C")("P")("E")("N")("G")("L")("U")("E")(cpenglue_code);@/
1131load4("C")("K")("R")("N")(ckrn_code);@/
1132load8("O")("F")("M")("L")("E")("V")("E")("L")(ofm_level_code);@/
1133load7("F")("O")("N")("T")("D")("I")("R")(font_dir_code);@/
1134load8("N")("F")("O")("N")("T")("D")("I")("R")(n_font_dir_code);@/
1135load14("N")("A")("T")("U")("R")("A")("L")("F")("O")("N")("T")("D")("I")("R")(n_font_dir_code);
1136  {compatibility with \.{omegafonts} (\.{C}) version}
1137load10("C")("H")("A")("R")("R")("E")("P")("E")("A")("T")(char_repeat_code);@/
1138load10("C")("H")("A")("R")("I")("V")("A")("L")("U")("E")(char_ivalue_code);@/
1139load10("C")("H")("A")("R")("F")("V")("A")("L")("U")("E")(char_fvalue_code);@/
1140load10("C")("H")("A")("R")("M")("V")("A")("L")("U")("E")(char_mvalue_code);@/
1141load8("C")("H")("A")("R")("R")("U")("L")("E")(char_rule_code);@/
1142load8("C")("H")("A")("R")("G")("L")("U")("E")(char_glue_code);@/
1143load11("C")("H")("A")("R")("P")("E")("N")("A")("L")("T")("Y")(char_penalty_code);@/
1144load8("F")("O")("N")("T")("R")("U")("L")("E")(font_rule_code);@/
1145load4("R")("U")("L")("E")(rule_code);@/
1146load6("R")("U")("L")("E")("W")("D")(rule_width_code);@/
1147load6("R")("U")("L")("E")("H")("T")(rule_height_code);@/
1148load6("R")("U")("L")("E")("D")("P")(rule_depth_code);@/
1149load8("F")("O")("N")("T")("G")("L")("U")("E")(font_glue_code);@/
1150load4("G")("L")("U")("E")(glue_code);@/
1151load8("G")("L")("U")("E")("T")("Y")("P")("E")(glue_type_code);@/
1152load16("G")("L")("U")("E")("S")("T")("R")("E")("T")("C")("H")("O")("R")("D")("E")("R")(glue_stretch_order_code);@/
1153load15("G")("L")("U")("E")("S")("H")("R")("I")("N")("K")("O")("R")("D")("E")("R")(glue_shrink_order_code);@/
1154load8("G")("L")("U")("E")("R")("U")("L")("E")(glue_rule_code);@/
1155load8("G")("L")("U")("E")("C")("H")("A")("R")(glue_char_code);@/
1156load6("G")("L")("U")("E")("W")("D")(glue_width_code);@/
1157load11("G")("L")("U")("E")("S")("T")("R")("E")("T")("C")("H")(glue_stretch_code);@/
1158load10("G")("L")("U")("E")("S")("H")("R")("I")("N")("K")(glue_shrink_code);@/
1159load11("F")("O")("N")("T")("P")("E")("N")("A")("L")("T")("Y")(font_penalty_code);@/
1160load7("P")("E")("N")("A")("L")("T")("Y")(penalty_code);@/
1161load10("P")("E")("N")("A")("L")("T")("Y")("V")("A")("L")(penalty_val_code);@/
1162load10("F")("O")("N")("T")("M")("V")("A")("L")("U")("E")(font_mvalue_code);@/
1163load6("M")("V")("A")("L")("U")("E")(mvalue_code);@/
1164load9("M")("V")("A")("L")("U")("E")("V")("A")("L")(mvalue_val_code);@/
1165load10("F")("O")("N")("T")("F")("V")("A")("L")("U")("E")(font_fvalue_code);@/
1166load6("F")("V")("A")("L")("U")("E")(fvalue_code);@/
1167load9("F")("V")("A")("L")("U")("E")("V")("A")("L")(fvalue_val_code);@/
1168load10("F")("O")("N")("T")("I")("V")("A")("L")("U")("E")(font_ivalue_code);@/
1169load6("I")("V")("A")("L")("U")("E")(ivalue_code);@/
1170load9("I")("V")("A")("L")("U")("E")("V")("A")("L")(ivalue_val_code);@/
1171
1172@ @<Enter the parameter names@>=
1173load5("S")("L")("A")("N")("T")(parameter_code+1);@/
1174load5("S")("P")("A")("C")("E")(parameter_code+2);@/
1175load7("S")("T")("R")("E")("T")("C")("H")(parameter_code+3);@/
1176load6("S")("H")("R")("I")("N")("K")(parameter_code+4);@/
1177load7("X")("H")("E")("I")("G")("H")("T")(parameter_code+5);@/
1178load4("Q")("U")("A")("D")(parameter_code+6);@/
1179load10("E")("X")("T")("R")("A")("S")("P")("A")("C")("E")(parameter_code+7);@/
1180load4("N")("U")("M")("1")(parameter_code+8);@/
1181load4("N")("U")("M")("2")(parameter_code+9);@/
1182load4("N")("U")("M")("3")(parameter_code+10);@/
1183load6("D")("E")("N")("O")("M")("1")(parameter_code+11);@/
1184load6("D")("E")("N")("O")("M")("2")(parameter_code+12);@/
1185load4("S")("U")("P")("1")(parameter_code+13);@/
1186load4("S")("U")("P")("2")(parameter_code+14);@/
1187load4("S")("U")("P")("3")(parameter_code+15);@/
1188load4("S")("U")("B")("1")(parameter_code+16);@/
1189load4("S")("U")("B")("2")(parameter_code+17);@/
1190load7("S")("U")("P")("D")("R")("O")("P")(parameter_code+18);@/
1191load7("S")("U")("B")("D")("R")("O")("P")(parameter_code+19);@/
1192load6("D")("E")("L")("I")("M")("1")(parameter_code+20);@/
1193load6("D")("E")("L")("I")("M")("2")(parameter_code+21);@/
1194load10("A")("X")("I")("S")("H")("E")("I")("G")("H")("T")(parameter_code+22);@/
1195load20("D")("E")("F")("A")("U")("L")("T")("R")("U")("L")("E")@/@t\hskip2em@>
1196  ("T")("H")("I")("C")("K")("N")("E")("S")("S")(parameter_code+8);@/
1197load13("B")("I")("G")("O")("P")
1198  ("S")("P")("A")("C")("I")("N")("G")("1")(parameter_code+9);@/
1199load13("B")("I")("G")("O")("P")
1200  ("S")("P")("A")("C")("I")("N")("G")("2")(parameter_code+10);@/
1201load13("B")("I")("G")("O")("P")
1202  ("S")("P")("A")("C")("I")("N")("G")("3")(parameter_code+11);@/
1203load13("B")("I")("G")("O")("P")
1204  ("S")("P")("A")("C")("I")("N")("G")("4")(parameter_code+12);@/
1205load13("B")("I")("G")("O")("P")
1206  ("S")("P")("A")("C")("I")("N")("G")("5")(parameter_code+13);@/
1207
1208@ When a left parenthesis has been scanned, the following routine
1209is used to interpret the keyword that follows, and to store the
1210equivalent value in |cur_code|.
1211
1212@p procedure get_name;
1213begin incr(loc); incr(level); {pass the left parenthesis}
1214cur_char:=" ";
1215while cur_char=" " do get_next;
1216if (cur_char>")")or(cur_char<"(") then decr(loc); {back up one character}
1217name_length:=0; get_keyword_char; {prepare to scan the name}
1218while cur_char<>" " do
1219  begin if name_length=longest_name then cur_name[1]:="X" {force error}
1220  else incr(name_length);
1221  cur_name[name_length]:=cur_char;
1222  get_keyword_char;
1223  end;
1224lookup;
1225if name_ptr=0 then err_print('Sorry, I don''t know that property name');
1226@.Sorry, I don't know...@>
1227cur_code:=equiv[name_ptr];
1228end;
1229
1230@* Scanning numeric data.
1231The next thing we need is a trio of subroutines to read the one-byte,
1232four-byte, and real numbers that may appear as property values.
1233These subroutines are careful to stick to numbers between $-2^{31}$
1234and $2^{31}-1$, inclusive, so that a computer with two's complement
123532-bit arithmetic will not be interrupted by overflow.
1236
1237@ The first number scanner, which returns a one-byte value, surely has
1238no problems of arithmetic overflow.
1239
1240@p function get_byte:byte; {scans a one-byte property value}
1241var acc:integer; {an accumulator}
1242@!t:ASCII_code; {the type of value to be scanned}
1243begin repeat get_next;
1244until cur_char<>" "; {skip the blanks before the type code}
1245t:=cur_char; acc:=0;
1246repeat get_next;
1247until cur_char<>" "; {skip the blanks after the type code}
1248if t="C" then @<Scan an ASCII character code@>
1249else if t="D" then @<Scan a small decimal number@>
1250else if t="O" then @<Scan a small octal number@>
1251else if t="H" then @<Scan a small hexadecimal number@>
1252else if t="F" then @<Scan a face code@>
1253else skip_error('You need "C" or "D" or "O" or "H" or "F" here');
1254@.You need "C" or "D" ...here@>
1255cur_char:=" "; get_byte:=acc;
1256end;
1257
1258@ The |get_next| routine converts lower case to upper case, but it leaves
1259the character in the buffer, so we can unconvert it.
1260
1261@<Scan an ASCII...@>=
1262if (cur_char>=@'41)and(cur_char<=@'176)and
1263 ((cur_char<"(")or(cur_char>")")) then
1264  acc:=xord[buffer[loc]]
1265else skip_error('"C" value must be standard ASCII and not a paren')
1266@:C value}\.{"C" value must be...@>
1267
1268@ @<Scan a small dec...@>=
1269begin while (cur_char>="0")and(cur_char<="9") do
1270  begin acc:=acc*10+cur_char-"0";
1271  if acc>char_max then
1272    begin skip_error('This value shouldn''t exceed 65535');
1273@.This value shouldn't...@>
1274    acc:=0; cur_char:=" ";
1275    end
1276  else get_next;
1277  end;
1278backup;
1279end
1280
1281@ @<Scan a small oct...@>=
1282begin while (cur_char>="0")and(cur_char<="7") do
1283  begin acc:=acc*8+cur_char-"0";
1284  if acc>char_max then
1285    begin skip_error('This value shouldn''t exceed ''177777');
1286@.This value shouldn't...@>
1287    acc:=0; cur_char:=" ";
1288    end
1289  else get_next;
1290  end;
1291backup;
1292end
1293
1294@ @<Scan a small hex...@>=
1295begin while ((cur_char>="0")and(cur_char<="9"))or
1296   ((cur_char>="A")and(cur_char<="F")) do
1297  begin if cur_char>="A" then cur_char:=cur_char+"0"+10-"A";
1298  acc:=acc*16+cur_char-"0";
1299  if acc>char_max then
1300    begin skip_error('This value shouldn''t exceed "FFFF');
1301@.This value shouldn't...@>
1302    acc:=0; cur_char:=" ";
1303    end
1304  else get_next;
1305  end;
1306backup;
1307end
1308
1309@ @<Scan a face...@>=
1310begin if cur_char="B" then acc:=2
1311else if cur_char="L" then acc:=4
1312else if cur_char<>"M" then acc:=18;
1313get_next;
1314if cur_char="I" then incr(acc)
1315else if cur_char<>"R" then acc:=18;
1316get_next;
1317if cur_char="C" then acc:=acc+6
1318else if cur_char="E" then acc:=acc+12
1319else if cur_char<>"R" then acc:=18;
1320if acc>=18 then
1321  begin skip_error('Illegal face code, I changed it to MRR');
1322@.Illegal face code...@>
1323  acc:=0;
1324  end;
1325end
1326
1327@ The routine that scans a four-byte value puts its output into |cur_bytes|,
1328which is a record containing (yes, you guessed it) four bytes.
1329
1330@<Types...@>=
1331@!four_bytes=record @!b0:byte;@+@!b1:byte;@+@!b2:byte;@+@!b3:byte;@+end;
1332
1333@ @d c0==cur_bytes.b0
1334@d c1==cur_bytes.b1
1335@d c2==cur_bytes.b2
1336@d c3==cur_bytes.b3
1337
1338@<Glob...@>=
1339@!cur_bytes:four_bytes; {a four-byte accumulator}
1340@!zero_bytes:four_bytes; {four bytes all zero}
1341
1342@ @<Set init...@>=
1343zero_bytes.b0:=0; zero_bytes.b1:=0; zero_bytes.b2:=0; zero_bytes.b3:=0;
1344
1345@ Since the |get_four_bytes| routine is used very infrequently, no attempt
1346has been made to make it fast; we only want it to work.
1347This is no longer the case, but we hope that it is not too slow.
1348
1349@p procedure get_four_bytes; {scans an unsigned constant and sets |four_bytes|}
1350var c:integer; {leading byte}
1351@!r:integer; {radix}
1352begin repeat get_next;
1353until cur_char<>" "; {skip the blanks before the type code}
1354r:=0; cur_bytes:=zero_bytes; {start with the accumulator zero}
1355if cur_char="H" then r:=16
1356else if cur_char="O" then r:=8
1357else if cur_char="D" then r:=10
1358else skip_error('Decimal ("D"), octal ("O") or hex ("H") value is needed here');
1359@.Decimal ("D"), octal ("O") or hex ("H")...@>
1360if r>0 then begin
1361  repeat get_next;
1362  until cur_char<>" "; {skip the blanks after the type code}
1363  while ((cur_char>="0")and(cur_char<="9"))or@|
1364      ((cur_char>="A")and(cur_char<="F")) do
1365    @<Multiply by |r|, add |cur_char-"0"|, and |get_next|@>;
1366  end;
1367end;
1368
1369function get_integer:integer; {scans an integer property value}
1370var @!a:integer; {accumulator}
1371begin get_four_bytes;
1372a:=c0;
1373if a>=@"80 then a:=a-@"100;
1374get_integer:=(a*@"1000000)+(c1*@"10000)+(c2*@"100)+c3;
1375end;
1376
1377@ @<Multiply by |r|...@>=
1378begin if cur_char>="A" then cur_char:=cur_char+"0"+10-"A";
1379if cur_char>="0"+r then skip_error('Illegal digit')
1380@.Illegal digit@>
1381else begin
1382  c:=c3*r+cur_char-"0"; c3:=c mod 256;@/
1383  c:=c2*r+c div 256; c2:=c mod 256;@/
1384  c:=c1*r+c div 256; c1:=c mod 256;@/
1385  c:=c0*r+c div 256;
1386  if c<256 then c0:=c
1387  else begin
1388    cur_bytes:=zero_bytes;
1389    if r=8 then
1390      skip_error('Sorry, the maximum octal value is O 37777777777')
1391@.Sorry, the maximum...@>
1392    else if r=10 then
1393      skip_error('Sorry, the maximum decimal value is D 4294967295')
1394    else skip_error('Sorry, the maximum hex value is H FFFFFFFF');
1395    end;
1396  get_next;
1397  end;
1398end
1399
1400@ The remaining scanning routine is the most interesting. It scans a real
1401constant and returns the nearest |fix_word| approximation to that constant.
1402A |fix_word| is a 32-bit integer that represents a real value that
1403has been multiplied by $2^{20}$. Since \.{PLtoTF} restricts the magnitude
1404of reals to 2048, the |fix_word| will have a magnitude less than $2^{31}$.
1405
1406@d unity==@'4000000 {$2^{20}$, the |fix_word| 1.0}
1407
1408@<Types...@>=
1409@!fix_word=integer; {a scaled real value with 20 bits of fraction}
1410@!unsigned_integer=integer;
1411
1412@ When a real value is desired, we might as well treat `\.D' and `\.R'
1413formats as if they were identical.
1414
1415@p function get_fix:fix_word; {scans a real property value}
1416var negative:boolean; {was there a minus sign?}
1417@!acc:integer; {an accumulator}
1418@!int_part:integer; {the integer part}
1419@!j:0..7; {the number of decimal places stored}
1420begin repeat get_next;
1421until cur_char<>" "; {skip the blanks before the type code}
1422negative:=false; acc:=0; {start with the accumulators zero}
1423if (cur_char<>"R")and(cur_char<>"D") then
1424  skip_error('An "R" or "D" value is needed here')
1425@.An "R" or "D" ... needed here@>
1426else  begin @<Scan the blanks and/or signs after the type code@>;
1427  while (cur_char>="0") and (cur_char<="9") do
1428    @<Multiply by 10, add |cur_char-"0"|, and |get_next|@>;
1429  int_part:=acc; acc:=0;
1430  if cur_char="." then @<Scan the fraction part and put it in |acc|@>;
1431  if (acc>=unity)and(int_part=2047) then
1432    skip_error('Real constants must be less than 2048')
1433@.Real constants must be...@>
1434  else acc:=int_part*unity+acc;
1435  end;
1436if negative then get_fix:=-acc@+else get_fix:=acc;
1437end;
1438
1439@ @<Scan the blanks...@>=
1440repeat get_next;
1441if cur_char="-" then
1442  begin cur_char:=" "; negative:=not negative;
1443  end
1444else if cur_char="+" then cur_char:=" ";
1445until cur_char<>" "
1446
1447@ @<Multiply by 10...@>=
1448begin acc:=acc*10+cur_char-"0";
1449if acc>=2048 then
1450  begin skip_error('Real constants must be less than 2048');
1451@.Real constants must be...@>
1452  acc:=0; cur_char:=" ";
1453  end
1454else get_next;
1455end
1456
1457@ To scan the fraction $.d_1d_2\ldots\,$, we keep track of up to seven
1458of the digits $d_j$. A correct result is obtained if we first compute
1459$f^\prime=\lfloor 2^{21}(d_1\ldots d_j)/10^j\rfloor$, after which
1460$f=\lfloor(f^\prime+1)/2\rfloor$. It is possible to have $f=1.0$.
1461
1462@<Glob...@>=
1463@!fraction_digits:array[1..7] of integer; {$2^{21}$ times $d_j$}
1464
1465@ @<Scan the frac...@>=
1466begin j:=0; get_next;
1467while (cur_char>="0")and(cur_char<="9") do
1468  begin if j<7 then
1469    begin incr(j); fraction_digits[j]:=@'10000000*(cur_char-"0");
1470    end;
1471  get_next;
1472  end;
1473acc:=0;
1474while j>0 do
1475  begin acc:=fraction_digits[j]+(acc div 10); decr(j);
1476  end;
1477acc:=(acc+10) div 20;
1478end
1479
1480@* Storing the property values.
1481When property values have been found, they are squirreled away in a bunch
1482of arrays. The header information is unpacked into bytes in an array
1483called |header_bytes|. The ligature/kerning program is stored in an array
1484of type |four_bytes|.
1485Another |four_bytes| array holds the specifications of extensible characters.
1486The kerns and parameters are stored in separate arrays of |fix_word| values.
1487
1488Instead of storing the design size in the header array, we will keep it
1489in a |fix_word| variable until the last minute. The number of units in the
1490design size is also kept in a |fix_word|.
1491
1492@<Glob...@>=
1493@!header_bytes:array[header_index] of byte; {the header block}
1494@!header_ptr:header_index; {the number of header bytes in use}
1495@!design_size:fix_word; {the design size}
1496@!design_units:fix_word; {reciprocal of the scaling factor}
1497@!seven_bit_safe_flag:boolean; {does the file claim to be seven-bit-safe?}
1498@!lig_kern:array[0..max_lig_steps] of four_bytes; {the ligature program}
1499@!nl:unsigned_integer; {the number of ligature/kern instructions so far}
1500@!min_nl:unsigned_integer; {the final value of |nl| must be at least this}
1501@!kern:array[0..max_kerns] of fix_word; {the distinct kerning amounts}
1502@!nk:0..max_kerns; {the number of entries of |kern|}
1503@!exten:array[char_type] of four_bytes; {extensible character specs}
1504@!ne:xchar_type; {the number of extensible characters}
1505@!param:array[1..max_param_words] of fix_word; {\.{FONTDIMEN} parameters}
1506@!np:0..max_param_words; {the largest parameter set nonzero}
1507@!check_sum_specified:boolean; {did the user name the check sum?}
1508@!bchar:xchar_type; {the right boundary character, or 256 if unspecified}
1509@!font_dir:integer; {font direction}
1510
1511@ @<Types...@>=
1512@!char_type=0..max_char;
1513@!xchar_type=0..xmax_char;
1514@!xxchar_type=0..xxmax_char;
1515@!header_index=0..max_header_bytes;
1516@!indx=xxchar_type;
1517
1518@ @<Local...@>=
1519@!d:header_index; {an index into |header_bytes|}
1520
1521@ We start by setting up the default values.
1522
1523@d check_sum_loc=0
1524@d design_size_loc=4
1525@d coding_scheme_loc=8
1526@d family_loc=coding_scheme_loc+40
1527@d seven_flag_loc=family_loc+20
1528@d face_loc=seven_flag_loc+3
1529
1530@<Set init...@>=
1531for d:=0 to 18*4-1 do header_bytes[d]:=0;
1532header_bytes[8]:=11; header_bytes[9]:="U";
1533header_bytes[10]:="N";
1534header_bytes[11]:="S";
1535header_bytes[12]:="P";
1536header_bytes[13]:="E";
1537header_bytes[14]:="C";
1538header_bytes[15]:="I";
1539header_bytes[16]:="F";
1540header_bytes[17]:="I";
1541header_bytes[18]:="E";
1542header_bytes[19]:="D";
1543@.UNSPECIFIED@>
1544for d:=family_loc to family_loc+11 do header_bytes[d]:=header_bytes[d-40];
1545design_size:=10*unity; design_units:=unity; seven_bit_safe_flag:=false;@/
1546header_ptr:=18*4; nl:=0; min_nl:=0; nk:=0; ne:=0; np:=0;@/
1547check_sum_specified:=false; bchar:=xmax_char;
1548font_dir:=0;
1549
1550@ Most of the dimensions, however, go into the |memory| array. There are
1551at most |max_char+2| widths, |max_char+2| heights, |max_char+2| depths,
1552and |max_char+2| italic corrections,
1553since the value 0 is required but it need not be used. So |memory| has room
1554for |4*max_char+8| entries, each of which is a |fix_word|.  An auxiliary table called
1555|link| is used to link these words together in linear lists, so that
1556sorting and other operations can be done conveniently.
1557
1558We also add four ``list head'' words to the |memory| and |link| arrays;
1559these are in locations |width| through |italic|, i.e., 1 through 4.
1560For example, |link[height]| points to the smallest element in
1561the sorted list of distinct heights that have appeared so far, and
1562|memory[height]| is the number of distinct heights.
1563
1564@<Types...@>=
1565@!pointer=0..mem_size; {an index into memory}
1566
1567@ The arrays |char_wd|, |char_ht|, |char_dp|, and |char_ic| contain
1568pointers to the |memory| array entries where the corresponding dimensions
1569appear. Two other arrays, |char_tag| and |char_remainder|, hold
1570the other information that \.{TFM} files pack into a |char_info_word|.
1571
1572@d no_tag=0 {vanilla character}
1573@d lig_tag=1 {character has a ligature/kerning program}
1574@d list_tag=2 {character has a successor in a charlist}
1575@d ext_tag=3 {character is extensible}
1576@d bchar_label==char_remainder[xmax_char]
1577  {beginning of ligature program for left boundary}
1578
1579@<Glob...@>=
1580@!memory:array[pointer] of fix_word; {character dimensions and kerns}
1581@!mem_ptr:pointer; {largest |memory| word in use}
1582@!link:array[pointer] of pointer; {to make lists of |memory| items}
1583@!char_wd:array[char_type] of pointer; {pointers to the widths}
1584@!char_ht:array[char_type] of pointer; {pointers to the heights}
1585@!char_dp:array[char_type] of pointer; {pointers to the depths}
1586@!char_ic:array[char_type] of pointer; {pointers to italic corrections}
1587@!char_tag:array[char_type] of no_tag..ext_tag; {character tags}
1588@!char_remainder:array[xchar_type] of xchar_type; {pointers to ligature labels,
1589  next larger characters, or extensible characters}
1590@!top_width,@!top_height,@!top_depth,@!top_italic:integer;
1591
1592@ @<Local...@>=
1593@!c:integer; {runs through all character codes}
1594
1595@ @<Set init...@>=
1596bchar_label:=xmax_label;
1597for c:=0 to max_char do
1598  begin char_wd[c]:=0; char_ht[c]:=0; char_dp[c]:=0; char_ic[c]:=0;@/
1599  char_tag[c]:=no_tag; char_remainder[c]:=0;
1600  end;
1601memory[0]:=@'17777777777; {an ``infinite'' element at the end of the lists}
1602memory[width]:=0; link[width]:=0; {width list is empty}
1603memory[height]:=0; link[height]:=0; {height list is empty}
1604memory[depth]:=0; link[depth]:=0; {depth list is empty}
1605memory[italic]:=0; link[italic]:=0; {italic list is empty}
1606mem_ptr:=italic;
1607
1608@ As an example of these data structures, let us consider the simple
1609routine that inserts a potentially new element into one of the dimension
1610lists. The first parameter indicates the list head (i.e., |h=width| for
1611the width list, etc.); the second parameter is the value that is to be
1612inserted into the list if it is not already present.  The procedure
1613returns the value of the location where the dimension appears in |memory|.
1614The fact that |memory[0]| is larger than any legal dimension makes the
1615algorithm particularly short.
1616
1617We do have to handle two somewhat subtle situations. A width of zero must be
1618put into the list, so that a zero-width character in the font will not appear
1619to be nonexistent (i.e., so that its |char_wd| index will not be zero), but
1620this does not need to be done for heights, depths, or italic corrections.
1621Furthermore, it is necessary to test for memory overflow even though we
1622have provided room for the maximum number of different dimensions in any
1623legal font, since the \.{PL} file might foolishly give any number of
1624different sizes to the same character.
1625
1626@p function sort_in(@!h:pointer;@!d:fix_word):pointer; {inserts into list}
1627var p:pointer; {the current node of interest}
1628begin if (d=0)and(h<>width) then sort_in:=0
1629else begin p:=h;
1630  while d>=memory[link[p]] do p:=link[p];
1631  if (d=memory[p])and(p<>h) then sort_in:=p
1632  else if mem_ptr=mem_size then
1633    begin err_print('Memory overflow: too many widths, etc');
1634@.Memory overflow...@>
1635    print_ln('Congratulations! It''s hard to make this error.');
1636    sort_in:=p;
1637    end
1638  else  begin incr(mem_ptr); memory[mem_ptr]:=d;
1639    link[mem_ptr]:=link[p]; link[p]:=mem_ptr; incr(memory[h]);
1640    sort_in:=mem_ptr;
1641    end;
1642  end;
1643end;
1644
1645@ When these lists of dimensions are eventually written to the \.{OFM}
1646file, we may have to do some rounding of values, because the \.{OFM} file
1647allows at most 65536 widths, 256 heights, 256 depths, and 256 italic
1648corrections. The following procedure takes a given list head |h| and a
1649given dimension |d|, and returns the minimum $m$ such that the elements of
1650the list can be covered by $m$ intervals of width $d$.  It also sets
1651|next_d| to the smallest value $d^\prime>d$ such that the covering found
1652by this procedure would be different.  In particular, if $d=0$ it computes
1653the number of elements of the list, and sets |next_d| to the smallest
1654distance between two list elements. (The covering by intervals of width
1655|next_d| is not guaranteed to have fewer than $m$ elements, but in practice
1656this seems to happen most of the time.)
1657
1658@<Glob...@>=
1659@!next_d:fix_word; {the next larger interval that is worth trying}
1660
1661@ Once again we can make good use of the fact that |memory[0]| is ``infinite.''
1662
1663@p function min_cover(@!h:pointer;@!d:fix_word):integer;
1664var p:pointer; {the current node of interest}
1665@!l:fix_word; {the least element covered by the current interval}
1666@!m:integer; {the current size of the cover being generated}
1667begin m:=0; p:=link[h]; next_d:=memory[0];
1668while p<>0 do
1669  begin incr(m); l:=memory[p];
1670  while memory[link[p]]<=l+d do p:=link[p];
1671  p:=link[p];
1672  if memory[p]-l<next_d then next_d:=memory[p]-l;
1673  end;
1674min_cover:=m;
1675end;
1676
1677@ The following procedure uses |min_cover| to determine the smallest $d$
1678such that a given list can be covered with at most a given number of
1679intervals.
1680
1681@p function shorten(@!h:pointer;m:integer):fix_word; {finds best way to round}
1682var d:fix_word; {the current trial interval length}
1683@!k:integer; {the size of a minimum cover}
1684begin if memory[h]>m then begin
1685  excess:=memory[h]-m;
1686  k:=min_cover(h,0); d:=next_d; {now the answer is at least |d|}
1687  repeat d:=d+d; k:=min_cover(h,d);
1688  until k<=m; {first we ascend rapidly until finding the range}
1689  d:=d div 2; k:=min_cover(h,d); {now we run through the feasible steps}
1690  while k>m do
1691    begin d:=next_d; k:=min_cover(h,d);
1692    end;
1693  shorten:=d;
1694  end
1695else shorten:=0;
1696end;
1697
1698@ When we are nearly ready to output the \.{TFM} file, we will set
1699|index[p]:=k| if the dimension in |memory[p]| is being rounded to the
1700|k|th element of its list.
1701
1702@<Glob...@>=
1703@!index:array[pointer] of byte;
1704@!excess:byte; {number of words to remove, if list is being shortened}
1705
1706@ Here is the procedure that sets the |index| values. It also shortens
1707the list so that there is only one element per covering interval;
1708the remaining elements are the midpoints of their clusters.
1709
1710@p procedure set_indices(@!h:pointer;@!d:fix_word); {reduces and indexes a list}
1711var p:pointer; {the current node of interest}
1712@!q:pointer; {trails one step behind |p|}
1713@!m:byte; {index number of nodes in the current interval}
1714@!l:fix_word; {least value in the current interval}
1715begin q:=h; p:=link[q]; m:=0;
1716while p<>0 do
1717  begin incr(m); l:=memory[p]; index[p]:=m;
1718  while memory[link[p]]<=l+d do
1719    begin p:=link[p]; index[p]:=m; decr(excess);
1720    if excess=0 then d:=0;
1721    end;
1722  link[q]:=p; memory[p]:=l+(memory[p]-l) div 2; q:=p; p:=link[p];
1723  end;
1724memory[h]:=m;
1725end;
1726
1727@* The input phase.
1728We're ready now to read and parse the \.{PL} file, storing property
1729values as we go.
1730
1731@<Glob...@>=
1732@!c:integer; {the current character or byte being processed}
1733@!cprime:char_type; {Processing for several characters together}
1734@!crange:char_type; {ditto}
1735
1736@ @<Read all the input@>=
1737cur_char:=" ";
1738repeat while cur_char=" " do get_next;
1739if cur_char="(" then @<Read a font property value@>
1740else if (cur_char=")")and not input_has_ended then
1741  begin err_print('Extra right parenthesis');
1742  incr(loc); cur_char:=" ";
1743  end
1744@.Extra right parenthesis@>
1745else if not input_has_ended then junk_error;
1746until input_has_ended
1747
1748@ The |junk_error| routine just referred to is called when something
1749appears in the forbidden area between properties of a property list.
1750
1751@p procedure junk_error; {gets past no man's land}
1752begin err_print('There''s junk here that is not in parentheses');
1753@.There's junk here...@>
1754skip_to_paren;
1755end;
1756
1757@ For each font property, we are supposed to read the data from the
1758left parenthesis that is the current value of |cur_char| to the right
1759parenthesis that matches it in the input. The main complication is
1760to recover with reasonable grace from various error conditions that might arise.
1761
1762@<Read a font property value@>=
1763begin get_name;
1764if cur_code=comment_code then skip_to_end_of_item
1765else if cur_code>char_repeat_code then
1766  flush_error('This property name doesn''t belong on the outer level')
1767@.This property name doesn't belong...@>
1768else  begin @<Read the font property value specified by |cur_code|@>;
1769  finish_the_property;
1770  end;
1771end
1772
1773@ @<Read the font property value spec...@>=
1774case cur_code of
1775check_sum_code: begin check_sum_specified:=true; read_four_bytes(check_sum_loc);
1776  end;
1777design_size_code: @<Read the design size@>;
1778design_units_code: @<Read the design units@>;
1779coding_scheme_code: read_BCPL(coding_scheme_loc,40);
1780family_code: read_BCPL(family_loc,20);
1781face_code: begin c:=get_byte; if c>255 then
1782  begin err_print('FACE clipped to 255'); c:=255; end;
1783  header_bytes[face_loc]:=c;
1784  end;
1785seven_bit_safe_flag_code: @<Read the seven-bit-safe flag@>;
1786header_code: @<Read an indexed header word@>;
1787font_dimen_code: @<Read font parameter list@>;
1788lig_table_code: read_lig_kern;
1789boundary_char_code: bchar:=get_byte;
1790character_code: read_char_info;
1791ofm_level_code:    @<Read OFM level code@>;
1792font_dir_code:     @<Read font direction code@>;
1793n_font_dir_code:   @<Read natural font direction code@>;
1794char_repeat_code:  read_repeated_character_info;
1795font_rule_code:    read_font_rule_list;
1796font_glue_code:    read_font_glue_list;
1797font_penalty_code: read_font_penalty_list;
1798font_mvalue_code:  read_font_mvalue_list;
1799font_fvalue_code:  read_font_fvalue_list;
1800font_ivalue_code:  read_font_ivalue_list;
1801end
1802
1803@ The |case| statement just given makes use of two subroutines that we
1804haven't defined yet. The first of these puts a 32-bit octal quantity
1805into four specified bytes of the header block.
1806
1807@p procedure read_four_bytes(l:header_index);
1808begin get_four_bytes;
1809header_bytes[l]:=c0;
1810header_bytes[l+1]:=c1;
1811header_bytes[l+2]:=c2;
1812header_bytes[l+3]:=c3;
1813end;
1814
1815@ The second little procedure is used to scan a string and to store it in
1816the ``{\mc BCPL} format'' required by \.{TFM} files. The string is supposed
1817to contain at most |n| bytes, including the first byte (which holds the
1818length of the rest of the string).
1819
1820@p procedure read_BCPL(l:header_index;n:byte);
1821var k:header_index;
1822begin k:=l;
1823while cur_char=" " do get_next;
1824while (cur_char<>"(")and(cur_char<>")") do
1825  begin if k<l+n then incr(k);
1826  if k<l+n then header_bytes[k]:=cur_char;
1827  get_next;
1828  end;
1829if k=l+n then
1830  begin err_print('String is too long; its first ',n-1:1,
1831@.String is too long...@>
1832    ' characters will be kept'); decr(k);
1833  end;
1834header_bytes[l]:=k-l;
1835while k<l+n-1 do {tidy up the remaining bytes by setting them to nulls}
1836  begin incr(k); header_bytes[k]:=0;
1837  end;
1838end;
1839
1840@ @<Read the design size@>=
1841begin next_d:=get_fix;
1842if next_d<unity then
1843  err_print('The design size must be at least 1')
1844@.The design size must...@>
1845else design_size:=next_d;
1846end
1847
1848@ @<Read the design units@>=
1849begin next_d:=get_fix;
1850if next_d<=0 then
1851  err_print('The number of units per design size must be positive')
1852@.The number of units...@>
1853else design_units:=next_d;
1854end
1855
1856@ @<Read the seven-bit-safe...@>=
1857begin while cur_char=" " do get_next;
1858if cur_char="T" then seven_bit_safe_flag:=true
1859else if cur_char="F" then seven_bit_safe_flag:=false
1860else err_print('The flag value should be "TRUE" or "FALSE"');
1861@.The flag value should be...@>
1862skip_to_paren;
1863end
1864
1865@ @<Read an indexed header word@>=
1866begin c:=get_byte;
1867if c<18 then skip_error('HEADER indices should be 18 or more')
1868@.HEADER indices...@>
1869else if 4*c+4>max_header_bytes then
1870  skip_error('This HEADER index is too big for my present table size')
1871@.This HEADER index is too big...@>
1872else  begin while header_ptr<4*c+4 do
1873    begin header_bytes[header_ptr]:=0; incr(header_ptr);
1874    end;
1875  read_four_bytes(4*c);
1876  end;
1877end
1878
1879@ The remaining kinds of font property values that need to be read are
1880those that involve property lists on higher levels. Each of these has a
1881loop similar to the one that was used at level zero. Then we put the
1882right parenthesis back so that `|finish_the_property|' will be happy;
1883there is probably a more elegant way to do this.
1884
1885@d finish_inner_property_list==begin decr(loc); incr(level); cur_char:=")";
1886  end
1887
1888@<Read font parameter list@>=
1889begin while level=1 do
1890  begin while cur_char=" " do get_next;
1891  if cur_char="(" then @<Read a parameter value@>
1892  else if cur_char=")" then skip_to_end_of_item
1893  else junk_error;
1894  end;
1895finish_inner_property_list;
1896end
1897
1898@ @<Read a parameter value@>=
1899begin get_name;
1900if cur_code=comment_code then skip_to_end_of_item
1901else if (cur_code<parameter_code)or(cur_code>=char_wd_code) then
1902  flush_error('This property name doesn''t belong in a FONTDIMEN list')
1903@.This property name doesn't belong...@>
1904else  begin if cur_code=parameter_code then c:=get_integer
1905  else c:=cur_code-parameter_code;
1906  if c<1 then flush_error('PARAMETER index must be at least 1')
1907@.PARAMETER index must be...@>
1908  else if c>max_param_words then
1909    flush_error('This PARAMETER index is too big for my present table size')
1910@.This PARAMETER index is too big...@>
1911  else  begin while np<c do
1912      begin incr(np); param[np]:=0;
1913      end;
1914    param[c]:=get_fix;
1915    finish_the_property;
1916    end;
1917  end;
1918end
1919
1920@ @<Read ligature/kern list@>=
1921begin lk_step_ended:=false;
1922while level=1 do
1923  begin while cur_char=" " do get_next;
1924  if cur_char="(" then read_lig_kern_command
1925  else if cur_char=")" then skip_to_end_of_item
1926  else junk_error;
1927  end;
1928finish_inner_property_list;
1929end
1930
1931@ @<Read a ligature/kern command@>=
1932begin get_name;
1933if cur_code=comment_code then skip_to_end_of_item
1934else if (cur_code<label_code)or(cur_code>ckrn_code) then
1935  flush_error('This property name doesn''t belong in a LIGTABLE list')
1936@.This property name doesn't belong...@>
1937else  begin case cur_code of
1938  label_code:@<Read a label step@>;
1939  stop_code:@<Read a stop step@>;
1940  skip_code:@<Read a skip step@>;
1941  krn_code:@<Read a kerning step@>;
1942  lig_code,lig_code+1,lig_code+2,lig_code+3,lig_code+5,lig_code+6,lig_code+7,
1943    lig_code+11:@<Read a ligature step@>;
1944  clabel_code:@<Read an extended label step@>;
1945  cpen_code:@<Read an extended penalty step@>;
1946  cglue_code:@<Read an extended glue step@>;
1947  cpenglue_code:@<Read an extended penalty/glue step@>;
1948  ckrn_code:@<Read an extended kern step@>;
1949  end; {there are no other cases |>=label_code| and |<=ckrn_code|}
1950  finish_the_property;
1951  end;
1952end
1953
1954@ When a character is about to be tagged, we call the following
1955procedure so that an error message is given in case of multiple tags.
1956
1957@p procedure check_tag(c:integer); {print error if |c| already tagged}
1958begin case char_tag[c] of
1959no_tag: do_nothing;
1960lig_tag: err_print('This character already appeared in a LIGTABLE LABEL');
1961@.This character already...@>
1962list_tag: err_print('This character already has a NEXTLARGER spec');
1963ext_tag: err_print('This character already has a VARCHAR spec');
1964end;
1965end;
1966
1967@ @<Read a label step@>=
1968begin while cur_char=" " do get_next;
1969if cur_char="B" then
1970  begin bchar_label:=nl; skip_to_paren; {\.{LABEL BOUNDARYCHAR}}
1971  end
1972else begin backup; c:=get_byte;
1973  check_tag(c); char_tag[c]:=lig_tag; char_remainder[c]:=nl;
1974  end;
1975if min_nl<=nl then min_nl:=nl+1;
1976lk_step_ended:=false;
1977end
1978
1979@ @d stop_flag=128 {value indicating `\.{STOP}' in a lig/kern program}
1980@d kern_flag=128 {op code for a kern step}
1981
1982@<Globals...@>=
1983@!lk_step_ended:boolean;
1984  {was the last \.{LIGTABLE} property \.{LIG} or \.{KRN}?}
1985@!krn_ptr:0..max_kerns; {an index into |kern|}
1986
1987@ @<Read a stop step@>=
1988if not lk_step_ended then
1989  err_print('STOP must follow LIG or KRN')
1990@.STOP must follow LIG or KRN@>
1991else begin lig_kern[nl-1].b0:=lig_kern[nl-1].b0 div 256 * 256 + stop_flag;
1992  lk_step_ended:=false;
1993  end
1994
1995@ @<Read a skip step@>=
1996if not lk_step_ended then
1997  err_print('SKIP must follow LIG or KRN')
1998@.SKIP must follow LIG or KRN@>
1999else begin c:=get_byte;
2000  if c>=128 then err_print('Maximum SKIP amount is 127')
2001@.Maximum SKIP amount...@>
2002  else if nl+c>=max_lig_steps then
2003    err_print('Sorry, LIGTABLE too long for me to handle')
2004@.Sorry, LIGTABLE too long...@>
2005  else begin lig_kern[nl-1].b0:=c;
2006    if min_nl<=nl+c then min_nl:=nl+c+1;
2007    end;
2008  lk_step_ended:=false;
2009  end
2010
2011@ @<Read a ligature step@>=
2012begin lig_kern[nl].b0:=0;
2013lig_kern[nl].b2:=cur_code-lig_code;
2014lig_kern[nl].b1:=get_byte;
2015lig_kern[nl].b3:=get_byte;
2016if nl>=max_lig_steps-1 then
2017  err_print('Sorry, LIGTABLE too long for me to handle')
2018@.Sorry, LIGTABLE too long...@>
2019else incr(nl);
2020lk_step_ended:=true;
2021end
2022
2023@ @<Read a kerning step@>=
2024begin lig_kern[nl].b0:=0; lig_kern[nl].b1:=get_byte;
2025kern[nk]:=get_fix; krn_ptr:=0;
2026while kern[krn_ptr]<>kern[nk] do incr(krn_ptr);
2027if krn_ptr=nk then
2028  begin if nk<max_kerns then incr(nk)
2029  else  begin err_print('Sorry, too many different kerns for me to handle');
2030@.Sorry, too many different kerns...@>
2031    decr(krn_ptr);
2032    end;
2033  end;
2034if ofm_level=-1 then begin
2035  lig_kern[nl].b2:=kern_flag+(krn_ptr div 256);
2036  lig_kern[nl].b3:=krn_ptr mod 256;
2037  end
2038else begin
2039  lig_kern[nl].b2:=kern_flag+(krn_ptr div 65536);
2040  lig_kern[nl].b3:=krn_ptr mod 65536;
2041  end;
2042if nl>=max_lig_steps-1 then
2043  err_print('Sorry, LIGTABLE too long for me to handle')
2044@.Sorry, LIGTABLE too long...@>
2045else incr(nl);
2046lk_step_ended:=true;
2047end
2048
2049@ @<Global...@>=
2050@!category_remainders:array[0..256] of integer;
2051@!ivalue_category,@!max_ivalue_category:integer;
2052@!glue_category,@!max_glue_category:integer;
2053@!penalty_category,@!max_penalty_category:integer;
2054
2055@ @<Set init...@>=
2056for ivalue_category:=0 to 256 do begin
2057  category_remainders[ivalue_category]:=-1;
2058  end;
2059max_ivalue_category:=-1;
2060max_glue_category:=-1;
2061max_penalty_category:=-1;
2062
2063@ @<Read an extended label step@>=
2064begin c:=get_byte;
2065category_remainders[c]:=nl;
2066if max_ivalue_category<c then max_ivalue_category:=c;
2067if min_nl<=nl then min_nl:=nl+1;
2068lk_step_ended:=false;
2069end
2070
2071@ @<Read an extended penalty step@>=
2072begin lig_kern[nl].b0:=256; lig_kern[nl].b1:=get_byte;
2073lig_kern[nl].b2:=17;
2074penalty_category:=get_byte;
2075if max_penalty_category<penalty_category then
2076  max_penalty_category:=penalty_category;
2077lig_kern[nl].b3:=penalty_category;
2078if nl>=max_lig_steps-1 then
2079  err_print('Sorry, LIGTABLE too long for me to handle')
2080@.Sorry, LIGTABLE too long...@>
2081else incr(nl);
2082lk_step_ended:=true;
2083end
2084
2085@ @<Read an extended glue step@>=
2086begin lig_kern[nl].b0:=256; lig_kern[nl].b1:=get_byte;
2087lig_kern[nl].b2:=18;
2088glue_category:=get_byte;
2089if max_glue_category<glue_category then
2090  max_glue_category:=glue_category;
2091lig_kern[nl].b3:=glue_category;
2092if nl>=max_lig_steps-1 then
2093  err_print('Sorry, LIGTABLE too long for me to handle')
2094@.Sorry, LIGTABLE too long...@>
2095else incr(nl);
2096lk_step_ended:=true;
2097end
2098
2099@ @<Read an extended penalty/glue step@>=
2100begin lig_kern[nl].b0:=256; lig_kern[nl].b1:=get_byte;
2101lig_kern[nl].b2:=19;
2102penalty_category:=get_byte;
2103if max_penalty_category<penalty_category then
2104  max_penalty_category:=penalty_category;
2105glue_category:=get_byte;
2106if max_glue_category<glue_category then
2107  max_glue_category:=glue_category;
2108lig_kern[nl].b3:=penalty_category*256+glue_category;
2109if nl>=max_lig_steps-1 then
2110  err_print('Sorry, LIGTABLE too long for me to handle')
2111@.Sorry, LIGTABLE too long...@>
2112else incr(nl);
2113lk_step_ended:=true;
2114end
2115
2116@ @<Read an extended kern step@>=
2117begin lig_kern[nl].b0:=256; lig_kern[nl].b1:=get_byte;
2118lig_kern[nl].b2:=20;
2119kern[nk]:=get_fix; krn_ptr:=0;
2120while kern[krn_ptr]<>kern[nk] do incr(krn_ptr);
2121if krn_ptr=nk then begin
2122  if nk<max_kerns then incr(nk)
2123  else begin
2124    err_print('Sorry, too many different kerns for me to handle');
2125@.Sorry, too many different kerns...@>
2126    decr(krn_ptr);
2127    end;
2128  end;
2129if krn_ptr>65535 then
2130  err_print('Sorry, too many different kerns for me to handle');
2131lig_kern[nl].b3:=krn_ptr;
2132if nl>=max_lig_steps-1 then
2133  err_print('Sorry, LIGTABLE too long for me to handle')
2134@.Sorry, LIGTABLE too long...@>
2135else incr(nl);
2136lk_step_ended:=true;
2137end
2138
2139@ @<Globals...@>=
2140@!char_extended_tag:array [char_type] of boolean;
2141
2142@ @<Set init...@>=
2143for c:=0 to max_char do
2144  char_extended_tag[c]:=false;
2145
2146@ @<Finish up the extended font stuff@>=
2147begin
2148if max_penalty_category>0 then begin
2149  if nkp=0 then
2150    err_print('No PENALTY table')
2151  else if npp[0]<max_penalty_category then
2152    err_print('Not enough PENALTY entries');
2153  end;
2154if max_glue_category>0 then begin
2155  if nkg=0 then
2156    err_print('No GLUE table')
2157  else if npg[0]<max_glue_category then
2158    err_print('Not enough GLUE entries');
2159  end;
2160if max_ivalue_category>0 then begin
2161  if nki=0 then
2162    err_print('No IVALUE table')
2163  else if npi[0]<max_ivalue_category then
2164    err_print('Not enough IVALUE entries')
2165  else begin
2166    for c:=0 to max_char do begin
2167      if (char_wd[c]<>0) then begin
2168        for j:=0 to max_ivalue_category do
2169          if char_table[c,0]=j then begin
2170            if category_remainders[j]<>-1 then begin
2171              if char_tag[c]<>0 then
2172                err_print('Character already has a tag')
2173              else begin
2174                char_extended_tag[c]:=true;
2175        	char_remainder[c]:=category_remainders[j];
2176                end;
2177              end;
2178            end;
2179        end;
2180      end;
2181    end;
2182  end;
2183end
2184
2185@ @<Global...@>=
2186tables_read:boolean;
2187
2188@ @<Set init...@>=
2189tables_read:=false;
2190
2191@ Finally we come to the part of \.{PLtoTF}'s input mechanism
2192that is used most, the processing of individual character data.
2193
2194@<Read character info list@>=
2195begin if not tables_read then
2196  begin compute_new_header_ofm;
2197  tables_read:=true;
2198  end;
2199c:=get_byte; {read the character code that is being specified}
2200@<Print |c| in hex notation@>;
2201while level=1 do
2202  begin while cur_char=" " do get_next;
2203  if cur_char="(" then read_character_property
2204  else if cur_char=")" then skip_to_end_of_item
2205  else junk_error;
2206  end;
2207if char_wd[c]=0 then char_wd[c]:=sort_in(width,0); {legitimatize |c|}
2208finish_inner_property_list;
2209end
2210
2211@ @<Globals...@>=
2212@!char_original:array [0..max_char] of integer;
2213@!char_repeats:array [0..max_char] of integer;
2214@!diff:boolean;
2215@!needed_space,@!extra_bytes:integer;
2216
2217@ @<Set init...@>=
2218for ch_entry:=0 to max_char do begin
2219  char_original[ch_entry]:=ch_entry;
2220  char_repeats[ch_entry]:=0;
2221  end;
2222
2223@ @<Read repeated character info@>=
2224begin
2225if not tables_read then begin
2226  @<Compute the new header information for OFM files@>;
2227  tables_read:=true;
2228  end;
2229c:=get_byte; {read the character code that is being specified}
2230@<Print |c| in hex notation@>;
2231crange:=get_byte; {read how many characters are being defined}
2232if (crange<0) then begin
2233  err_print('Character ranges must be positive');
2234  crange:=0;
2235  end;
2236if ((c+crange)>max_char) then begin
2237  err_print('Character range too large');
2238  crange:=0;
2239  end;
2240print('-'); print_hex(c+crange); incr(chars_on_line);
2241while level=1 do begin
2242  while cur_char=" " do get_next;
2243  if cur_char="(" then read_character_property
2244  else if cur_char=")" then skip_to_end_of_item
2245  else junk_error;
2246  end;
2247if char_wd[c]=0 then char_wd[c]:=sort_in(width,0); {legitimatize |c|}
2248finish_inner_property_list;
2249cprime:=c;
2250for c:=(cprime+1) to (cprime+crange) do begin
2251  char_wd[c]:=char_wd[cprime];
2252  char_ht[c]:=char_ht[cprime];
2253  char_dp[c]:=char_dp[cprime];
2254  char_ic[c]:=char_ic[cprime];
2255  char_tag[c]:=char_tag[cprime];
2256  char_remainder[c]:=char_remainder[cprime];
2257  for tab:=0 to (nki+nkf+nkm+nkr+nkg+nkp-1) do begin
2258    char_table[c,tab]:= char_table[cprime,tab];
2259    end;
2260  end;
2261end
2262
2263@ Tables for character parameters
2264
2265@d char_param_tables==8
2266
2267@<Globals...@>=
2268@!char_table:array [0..max_char,0..char_param_tables] of integer;
2269@!ch_table,@!ch_entry:integer;
2270@!temp_value:integer;
2271
2272@ @<Set init...@>=
2273for c:=0 to max_char do
2274  for ch_table:=0 to char_param_tables do
2275    char_table[c,ch_table]:=0;
2276
2277@ @d get_char_table_entry(#)==
2278  begin ch_table:=get_integer;
2279  if (ch_table<0)or(ch_table>=#) then
2280    flush_error('Character property index out of range')
2281@.Character property index...@>
2282  else  begin get_char_table_entry_end
2283@d get_char_table_entry_end(#)==
2284    ch_table:=ch_table+#;
2285    if ch_table>char_param_tables then
2286      flush_error('Character property exceeds table size')
2287@.Character property exceeds...@>
2288    else  begin
2289      if ch_table>npc then npc:=ch_table;
2290      char_table[c,ch_table]:=get_integer;
2291      end;
2292    end;
2293  end
2294
2295@<Read a character property@>=
2296begin get_name;
2297if cur_code=comment_code then skip_to_end_of_item
2298else if (cur_code<char_wd_code)or(cur_code>var_char_code) then
2299  flush_error('This property name doesn''t belong in a CHARACTER list')
2300@.This property name doesn't belong...@>
2301else  begin case cur_code of
2302  char_wd_code:char_wd[c]:=sort_in(width,get_fix);
2303  char_ht_code:char_ht[c]:=sort_in(height,get_fix);
2304  char_dp_code:char_dp[c]:=sort_in(depth,get_fix);
2305  char_ic_code:char_ic[c]:=sort_in(italic,get_fix);
2306  sec_width_code,
2307  sec_height_code,
2308  sec_depth_code,
2309  sec_italic_code,
2310  accent_code,
2311  prim_top_axis_code,
2312  prim_top_axis_bis_code,
2313  prim_bot_axis_code,
2314  prim_bot_axis_bis_code,
2315  prim_mid_hor_code,
2316  prim_mid_vert_code,
2317  prim_base_slant_code,
2318  sec_top_axis_code,
2319  sec_top_axis_bis_code,
2320  sec_bot_axis_code,
2321  sec_bot_axis_bis_code,
2322  sec_mid_hor_code,
2323  sec_mid_vert_code,
2324  sec_base_slant_code:temp_value:=get_fix;
2325  next_larger_code:begin check_tag(c); char_tag[c]:=list_tag;
2326    char_remainder[c]:=get_byte;
2327    end;
2328  var_char_code:@<Read an extensible recipe for |c|@>;
2329  char_ivalue_code:  get_char_table_entry(nki)(0);
2330  char_fvalue_code:  get_char_table_entry(nkf)(nki);
2331  char_mvalue_code:  get_char_table_entry(nkm)(nki+nkf);
2332  char_rule_code:    get_char_table_entry(nkr)(nki+nkf+nkm);
2333  char_glue_code:    get_char_table_entry(nkg)(nki+nkf+nkm+nkr);
2334  char_penalty_code: get_char_table_entry(nkp)(nki+nkf+nkm+nkr+nkg);
2335  end;@/
2336  finish_the_property;
2337  end;
2338end
2339
2340@ @<Read an extensible r...@>=
2341begin if ne=xmax_char then
2342  err_print('Sorry, too many VARCHAR specs')
2343@.Sorry, too many VARCHAR specs@>
2344else  begin check_tag(c); char_tag[c]:=ext_tag; char_remainder[c]:=ne;@/
2345  exten[ne]:=zero_bytes;
2346  while level=2 do
2347    begin while cur_char=" " do get_next;
2348    if cur_char="(" then @<Read an extensible piece@>
2349    else if cur_char=")" then skip_to_end_of_item
2350    else junk_error;
2351    end;
2352  incr(ne);
2353  finish_inner_property_list;
2354  end;
2355end
2356
2357@ @<Read an extensible p...@>=
2358begin get_name;
2359if cur_code=comment_code then skip_to_end_of_item
2360else if (cur_code<var_char_code+1)or(cur_code>var_char_code+4) then
2361  flush_error('This property name doesn''t belong in a VARCHAR list')
2362@.This property name doesn't belong...@>
2363else  begin case cur_code-(var_char_code+1) of
2364  0:exten[ne].b0:=get_byte;
2365  1:exten[ne].b1:=get_byte;
2366  2:exten[ne].b2:=get_byte;
2367  3:exten[ne].b3:=get_byte;
2368  end;@/
2369  finish_the_property;
2370  end;
2371end
2372
2373@ The input routine is now complete except for the following code,
2374which prints a progress report as the file is being read.
2375
2376@<Glob...@>=
2377@!HEX: packed array [1..16] of char;
2378
2379@ @<Set init...@>=
2380HEX:='0123456789ABCDEF';@/
2381
2382@ The array |dig| will hold a sequence of digits to be output.
2383
2384@<Glob...@>=
2385@!dig:array[0..32] of integer;
2386
2387@ Here, in fact, is a procedure that prints
2388|dig[j-1]|$\,\ldots\,$|dig[0]|, given $j>0$.
2389
2390@p procedure print_digs(j:integer); {prints |j| digits}
2391begin repeat decr(j); print(HEX[1+dig[j]]);
2392until j=0;
2393end;
2394
2395
2396@ The |print_number| procedure indicates how |print_digs| can be used.
2397This procedure can print in octal, decimal or hex notation.
2398
2399@d print_hex(#)==print_number(#,16)
2400@d print_octal(#)==print_number(#,8)
2401@d print_decimal(#)==print_number(#,10)
2402
2403@p procedure print_number(c:integer; form:integer); {prints value of |c|}
2404var j:0..32; {index into |dig|}
2405begin
2406j:=0;
2407if (c<0) then begin
2408   print_ln('Internal error: print_number (negative value)');
2409   c:=0
2410   end;
2411if form=8 then
2412   print('''') {an apostrophe indicates the octal notation}
2413else if form=16 then
2414   print('"')  { a double apostrophe indicates the hexadecimal notation}
2415else if form<>10 then begin
2416   print_ln('Internal error: print_number');
2417   form:=10
2418   end;
2419while (c>0) or (j=0) do begin
2420  dig[j]:=c mod form; c:=c div form;
2421  j:=j+1;
2422  end;
2423print_digs(j);
2424end;
2425
2426@ @<Print |c| in hex...@>=
2427begin if chars_on_line>=8 then
2428  begin print_ln(' '); chars_on_line:=1;
2429  end
2430else  begin if chars_on_line>0 then print(' ');
2431  incr(chars_on_line);
2432  end;
2433print_hex(c); {progress report}
2434end
2435
2436@* The checking and massaging phase.
2437Once the whole \.{PL} file has been read in, we must check it for consistency
2438and correct any errors. This process consists mainly of running through
2439the characters that exist and seeing if they refer to characters that
2440don't exist. We also compute the true value of |seven_unsafe|; we make sure
2441that the charlists and ligature programs contain no loops; and we
2442shorten the lists of widths, heights, depths, and italic corrections,
2443if necessary, to keep from exceeding the required maximum sizes.
2444
2445@<Glob...@>=
2446@!seven_unsafe:boolean; {do seven-bit characters generate eight-bit ones?}
2447
2448@ @<Correct and check the information@>=
2449if nl>0 then @<Make sure the ligature/kerning program ends appropriately@>;
2450seven_unsafe:=false;
2451for c:=0 to max_char do if char_wd[c]<>0 then
2452    @<For all characters |g| generated by |c|,
2453    make sure that |char_wd[g]| is nonzero, and
2454    set |seven_unsafe| if |c<128<=g|@>;
2455if bchar_label<xmax_label then
2456  begin c:=xmax_char; @<Check ligature program of |c|@>;
2457  end;
2458if seven_bit_safe_flag and seven_unsafe then
2459  print_ln('The font is not really seven-bit-safe!');
2460@.The font is not...safe@>
2461@<Check for infinite ligature loops@>;
2462@<Doublecheck the lig/kern commands and the extensible recipes@>;
2463finish_extended_font;
2464for c:=0 to max_char do
2465  @<Make sure that |c| is not the largest element of a charlist cycle@>;
2466@<Put the width, height, depth, and italic lists into final form@>
2467
2468@ The checking that we need in several places is accomplished by three
2469macros that are only slightly tricky.
2470
2471@d existence_tail(#)==begin char_wd[g]:=sort_in(width,0);
2472    print(#,' '); print_hex(c);
2473    print_ln(' had no CHARACTER spec.');
2474    end;
2475  end
2476@d check_existence_and_safety(#)==begin g:=#;
2477  if (g>=128)and(c<128) then seven_unsafe:=true;
2478  if char_wd[g]=0 then existence_tail
2479@d check_existence(#)==begin g:=#;
2480  if char_wd[g]=0 then existence_tail
2481
2482@<For all characters |g| generated by |c|...@>=
2483case char_tag[c] of
2484no_tag: do_nothing;
2485lig_tag: @<Check ligature program of |c|@>;
2486list_tag: check_existence_and_safety(char_remainder[c])
2487  ('The character NEXTLARGER than');
2488@.The character NEXTLARGER...@>
2489ext_tag:@<Check the pieces of |exten[c]|@>;
2490end
2491
2492@ @<Check the pieces...@>=
2493begin if exten[char_remainder[c]].b0>0 then
2494  check_existence_and_safety(exten[char_remainder[c]].b0)
2495    ('TOP piece of character');
2496@.TOP piece of character...@>
2497if exten[char_remainder[c]].b1>0 then
2498  check_existence_and_safety(exten[char_remainder[c]].b1)
2499    ('MID piece of character');
2500@.MID piece of character...@>
2501if exten[char_remainder[c]].b2>0 then
2502  check_existence_and_safety(exten[char_remainder[c]].b2)
2503    ('BOT piece of character');
2504@.BOT piece of character...@>
2505check_existence_and_safety(exten[char_remainder[c]].b3)
2506    ('REP piece of character');
2507@.REP piece of character...@>
2508end
2509
2510@ @<Make sure that |c| is not the largest element of a charlist cycle@>=
2511if char_tag[c]=list_tag then
2512  begin g:=char_remainder[c];
2513  while (g<c)and(char_tag[g]=list_tag) do g:=char_remainder[g];
2514  if g=c then
2515    begin char_tag[c]:=no_tag;
2516    print('A cycle of NEXTLARGER characters has been broken at ');
2517@.A cycle of NEXTLARGER...@>
2518    print_hex(c); print_ln('.');
2519    end;
2520  end
2521
2522@ @<Glob...@>=
2523@!delta:fix_word; {size of the intervals needed for rounding}
2524
2525@ @d round_message(#)==if delta>0 then print_ln('I had to round some ',
2526@.I had to round...@>
2527  #,'s by ',(((delta+1) div 2)/@'4000000):1:7,' units.')
2528
2529@<Put the width, height, depth, and italic lists into final form@>=
2530case ofm_level of
2531  -1: begin
2532    top_width:=255; top_depth:=15; top_height:=15; top_italic:=63;
2533    end;
2534  0: begin
2535    top_width:=65535; top_depth:=255; top_height:=255; top_italic:=255;
2536    end;
2537  1: begin
2538    top_width:=65535; top_depth:=255; top_height:=255; top_italic:=255;
2539    end;
2540  end;
2541delta:=shorten(width,top_width); set_indices(width,delta);
2542  round_message('width');@/
2543delta:=shorten(height,top_height); set_indices(height,delta);
2544  round_message('height');@/
2545delta:=shorten(depth,top_depth); set_indices(depth,delta);
2546  round_message('depth');@/
2547delta:=shorten(italic,top_italic); set_indices(italic,delta);
2548  round_message('italic correction');
2549
2550@ @d clear_lig_kern_entry== {make an unconditional \.{STOP}}
2551  lig_kern[nl].b0:=255; lig_kern[nl].b1:=0;
2552  lig_kern[nl].b2:=0; lig_kern[nl].b3:=0
2553
2554@<Make sure the ligature/kerning program ends...@>=
2555begin if bchar_label<xmax_label then {make room for it}
2556  begin clear_lig_kern_entry; incr(nl);
2557  end; {|bchar_label| will be stored later}
2558while min_nl>nl do
2559  begin clear_lig_kern_entry; incr(nl);
2560  end;
2561if (lig_kern[nl-1].b0 mod 256)=0 then
2562   lig_kern[nl-1].b0:=lig_kern[nl-1].b0 div 256 * 256 + stop_flag;
2563end
2564
2565@ It's not trivial to check for infinite loops generated by repeated
2566insertion of ligature characters. But fortunately there is a nice
2567algorithm for such testing, copied here from the program \.{TFtoPL}
2568where it is explained further.
2569
2570@d simple=0 {$f(x,y)=z$}
2571@d left_z=1 {$f(x,y)=f(z,y)$}
2572@d right_z=2 {$f(x,y)=f(x,z)$}
2573@d both_z=3 {$f(x,y)=f(f(x,z),y)$}
2574@d pending=4 {$f(x,y)$ is being evaluated}
2575
2576
2577@ @<Glo...@>=
2578@!lig_ptr:0..max_lig_steps; {an index into |lig_kern|}
2579@!hash:array[0..hash_size] of integer64;
2580@!class:array[0..hash_size] of simple..pending;
2581@!lig_z:array[0..hash_size] of xxchar_type;
2582@!hash_ptr:0..hash_size; {the number of nonzero entries in |hash|}
2583@!hash_list:array[0..hash_size] of 0..hash_size; {list of those nonzero entries}
2584@!h,@!hh:0..hash_size; {indices into the hash table}
2585@!tt:indx; {temporary register}
2586@!x_lig_cycle,@!y_lig_cycle:xchar_type; {problematic ligature pair}
2587
2588@ @<Set init...@>=
2589hash_ptr:=0; y_lig_cycle:=xmax_char;
2590for k:=0 to hash_size do hash[k]:=0;
2591
2592@ @d lig_exam==lig_kern[lig_ptr].b1
2593@d lig_gen==lig_kern[lig_ptr].b3
2594
2595@<Check lig...@>=
2596begin lig_ptr:=char_remainder[c];
2597if lig_kern[lig_ptr].b0<256 then
2598begin
2599repeat if hash_input(lig_ptr,c) then
2600  begin if lig_kern[lig_ptr].b2<kern_flag then
2601    begin if lig_exam<>bchar then
2602      check_existence(lig_exam)('LIG character examined by');
2603@.LIG character examined...@>
2604    check_existence(lig_gen)('LIG character generated by');
2605@.LIG character generated...@>
2606    if lig_gen>=128 then if(c<128)or(c=bchar) then
2607      if(lig_exam<128)or(lig_exam=bchar) then seven_unsafe:=true;
2608    end
2609  else if lig_exam<>bchar then
2610    check_existence(lig_exam)('KRN character examined by');
2611@.KRN character examined...@>
2612  end;
2613if lig_kern[lig_ptr].b0>=stop_flag then lig_ptr:=nl
2614else lig_ptr:=lig_ptr+1+lig_kern[lig_ptr].b0;
2615until lig_ptr>=nl;
2616end;
2617end
2618
2619@ The |hash_input| procedure is copied from \.{TFtoPL}, but it is made
2620into a boolean function that returns |false| if the ligature command
2621was masked by a previous one.
2622
2623@p function hash_input(@!p,@!c:indx):boolean;
2624 {enter data for character |c| and command in location |p|, unless it isn't new}
2625label 30; {go here for a quick exit}
2626var @!cc:simple..both_z; {class of data being entered}
2627@!zz:char_type; {function value or ligature character being entered}
2628@!y:char_type; {the character after the cursor}
2629@!key:integer64; {value to be stored in |hash|}
2630@!t64:integer64; {temporary register for swapping}
2631@!t:integer; {temporary register for swapping}
2632begin if hash_ptr=hash_size then
2633  begin hash_input:=false; goto 30;@+end;
2634@<Compute the command parameters |y|, |cc|, and |zz|@>;
2635key:=int64cast(xmax_char)*c+y+1; h:=(hash_mult*key) mod hash_size;
2636while hash[h]>0 do
2637  begin if hash[h]<=key then
2638    begin if hash[h]=key then
2639      begin hash_input:=false; goto 30; {unused ligature command}
2640      end;
2641    t64:=hash[h]; hash[h]:=key; key:=t64; {do ordered-hash-table insertion}
2642    t:=class[h]; class[h]:=cc; cc:=t; {namely, do a swap}
2643    t:=lig_z[h]; lig_z[h]:=zz; zz:=t;
2644    end;
2645  if h>0 then decr(h)@+else h:=hash_size;
2646  end;
2647hash[h]:=key; class[h]:=cc; lig_z[h]:=zz;
2648incr(hash_ptr); hash_list[hash_ptr]:=h;
2649hash_input:=true;
265030:end;
2651
2652@ @<Compute the command param...@>=
2653y:=lig_kern[p].b1; t:=lig_kern[p].b2; cc:=simple;
2654zz:=lig_kern[p].b3;
2655if t>=kern_flag then zz:=y
2656else begin case t of
2657  0,6:do_nothing; {\.{LIG},\.{/LIG>}}
2658  5,11:zz:=y; {\.{LIG/>}, \.{/LIG/>>}}
2659  1,7:cc:=left_z; {\.{LIG/}, \.{/LIG/>}}
2660  2:cc:=right_z; {\.{/LIG}}
2661  3:cc:=both_z; {\.{/LIG/}}
2662  end; {there are no other cases}
2663  end
2664
2665@ (More good stuff from \.{TFtoPL}.)
2666
2667@p function f(@!h,@!x,@!y:indx):indx; forward;@t\2@>
2668  {compute $f$ for arguments known to be in |hash[h]|}
2669function eval(@!x,@!y:indx):indx; {compute $f(x,y)$ with hashtable lookup}
2670var @!key:integer64; {value sought in hash table}
2671begin key:=int64cast(xmax_char)*x+y+1; h:=(hash_mult*key) mod hash_size;
2672while hash[h]>key do
2673  if h>0 then decr(h)@+else h:=hash_size;
2674if hash[h]<key then eval:=y {not in ordered hash table}
2675else eval:=f(h,x,y);
2676end;
2677
2678@ Pascal's beastly convention for |forward| declarations prevents us from
2679saying |function f(h,x,y:indx):indx| here.
2680
2681@p function f;
2682begin case class[h] of
2683simple: do_nothing;
2684left_z: begin class[h]:=pending; lig_z[h]:=eval(lig_z[h],y); class[h]:=simple;
2685  end;
2686right_z: begin class[h]:=pending; lig_z[h]:=eval(x,lig_z[h]); class[h]:=simple;
2687  end;
2688both_z: begin class[h]:=pending; lig_z[h]:=eval(eval(x,lig_z[h]),y);
2689  class[h]:=simple;
2690  end;
2691pending: begin x_lig_cycle:=x; y_lig_cycle:=y; lig_z[h]:=xxmax_char; class[h]:=simple;
2692  end; {the value |xxmax_char| will break all cycles, since it's not in |hash|}
2693end; {there are no other cases}
2694f:=lig_z[h];
2695end;
2696
2697@ @<Check for infinite...@>=
2698if hash_ptr<hash_size then for hh:=1 to hash_ptr do
2699  begin tt:=hash_list[hh];
2700  if class[tt]>simple then {make sure $f$ is well defined}
2701  tt:=f(tt,(hash[tt]-1)div xmax_char,(hash[tt]-1)mod xmax_char);
2702  end;
2703if(hash_ptr=hash_size)or(y_lig_cycle<xmax_char) then
2704  begin if hash_ptr<hash_size then
2705    begin print('Infinite ligature loop starting with ');
2706@.Infinite ligature loop...@>
2707    if x_lig_cycle=xmax_char then print('boundary')@+else print_hex(x_lig_cycle);
2708    print(' and '); print_hex(y_lig_cycle); print_ln('!');
2709    end
2710  else print_ln('Sorry, I haven''t room for so many ligature/kern pairs!');
2711@.Sorry, I haven't room...@>
2712  print_ln('All ligatures will be cleared.');
2713  for c:=0 to max_char do if char_tag[c]=lig_tag then
2714    begin char_tag[c]:=no_tag; char_remainder[c]:=0;
2715    end;
2716  nl:=0; bchar:=xmax_char; bchar_label:=xmax_label;
2717  end
2718
2719@ The lig/kern program may still contain references to nonexistent characters,
2720if parts of that program are never used. Similarly, there may be extensible
2721characters that are never used, because they were overridden by
2722\.{NEXTLARGER}, say. This would produce an invalid \.{TFM} file; so we
2723must fix such errors.
2724
2725@d double_check_tail(#)==@t\1@>if char_wd[0]=0
2726      then char_wd[0]:=sort_in(width,0);
2727    print('Unused ',#,' refers to nonexistent character ');
2728    print_hex(c); print_ln('!');
2729    end;
2730  end
2731@d double_check_lig(#)==begin c:=lig_kern[lig_ptr].#;
2732  if char_wd[c]=0 then if c<>bchar then
2733    begin lig_kern[lig_ptr].#:=0; double_check_tail
2734@d double_check_ext(#)==begin c:=exten[g].#;
2735  if c>0 then if char_wd[c]=0 then
2736    begin exten[g].#:=0; double_check_tail
2737@d double_check_rep(#)==begin c:=exten[g].#;
2738  if char_wd[c]=0 then
2739    begin exten[g].#:=0; double_check_tail
2740
2741@<Doublecheck...@>=
2742if nl>0 then for lig_ptr:=0 to nl-1 do
2743  if (lig_kern[lig_ptr].b0 div 256)=0 then begin
2744  if lig_kern[lig_ptr].b2<kern_flag then
2745    begin if lig_kern[lig_ptr].b0<255 then
2746      begin double_check_lig(b1)('LIG step'); double_check_lig(b3)('LIG step');
2747      end;
2748    end
2749  else double_check_lig(b1)('KRN step');
2750  end;
2751@.Unused LIG step...@>
2752@.Unused KRN step...@>
2753if ne>0 then for g:=0 to ne-1 do
2754  begin double_check_ext(b0)('VARCHAR TOP');
2755  double_check_ext(b1)('VARCHAR MID');
2756  double_check_ext(b2)('VARCHAR BOT');
2757  double_check_rep(b3)('VARCHAR REP');
2758@.Unused VARCHAR...@>
2759  end
2760
2761@* The output phase.
2762Now that we know how to get all of the font data correctly stored in
2763\.{PLtoTF}'s memory, it only remains to write the answers out.
2764
2765First of all, it is convenient to have an abbreviation for output to the
2766\.{TFM} file:
2767
2768@d out(#)==write(tfm_file,#)
2769
2770@p procedure out_int(@!x:integer); {output a possibly negative value}
2771begin if x<0 then  begin
2772  x:=x+@"40000000;
2773  x:=x+@"40000000;
2774  out((x div @"1000000)+@"80);
2775  end
2776else out(x div @"1000000);
2777out((x mod @"1000000) div @"10000);
2778out((x mod @"10000) div @"100);
2779out(x mod @"100);
2780end;
2781
2782@ The general plan for producing \.{TFM} files is long but simple:
2783
2784@<Do the font metric output@>=
2785compute_subfile_sizes;
2786output_subfile_sizes;
2787@<Output the header block@>;
2788output_new_information_ofm;
2789output_character_info;
2790@<Output the dimensions themselves@>;
2791@<Output the ligature/kern program@>;
2792@<Output the extensible character recipes@>;
2793@<Output the parameters@>
2794
2795@ A \.{TFM} file begins with 12 numbers that tell how big its subfiles are.
2796We already know most of these numbers; for example, the number of distinct
2797widths is |memory[width]+1|, where the $+1$ accounts for the zero width that
2798is always supposed to be present. But we still should compute the beginning
2799and ending character codes (|bc| and |ec|), the number of header words (|lh|),
2800and the total number of words in the \.{TFM} file (|lf|).
2801
2802@<Gl...@>=
2803@!bc:char_type; {the smallest character code in the font}
2804@!ec:char_type; {the largest character code in the font}
2805@!lh:char_type; {the number of words in the header block}
2806@!lf:unsigned_integer; {the number of words in the entire \.{TFM} file}
2807@!not_found:boolean; {has a font character been found?}
2808@!temp_width:fix_word; {width being used to compute a check sum}
2809@!ncw,@!nco,@!npc:integer;
2810
2811@ @<Set init...@>=
2812npc:=-1;
2813
2814@ It might turn out that no characters exist at all. But \.{PLtoTF} keeps
2815going and writes the \.{TFM} anyway. In this case |ec| will be~0 and |bc|
2816will be~1.
2817
2818@<Compute the subfile sizes@>=
2819lh:=header_ptr div 4;@/
2820not_found:=true; bc:=0;
2821if (ofm_level=-1) then ec:=255 @+ else ec:=max_char;
2822while not_found do
2823  if (char_wd[bc]>0)or(bc=ec) then not_found:=false
2824  else incr(bc);
2825not_found:=true;
2826while not_found do
2827  if (char_wd[ec]>0)or(ec=0) then not_found:=false
2828  else decr(ec);
2829if bc>ec then bc:=1;
2830incr(memory[width]); incr(memory[height]); incr(memory[depth]);
2831incr(memory[italic]);@/
2832@<Compute the ligature/kern program offset@>;
2833case ofm_level of
2834  -1: begin
2835    lf:=6+lh+(ec-bc+1)+memory[width]+memory[height]+memory[depth]+
2836    memory[italic]+nl+lk_offset+nk+ne+np;
2837    end;
2838  0: begin
2839    lf:=14+lh+2*(ec-bc+1)+memory[width]+memory[height]+memory[depth]+
2840    memory[italic]+2*(nl+lk_offset)+nk+2*ne+np;
2841    end;
2842  1: begin
2843    @<Compute the character info size@>;
2844    lf:=29+lh+ncw+memory[width]+memory[height]+memory[depth]+
2845    memory[italic]+2*(nl+lk_offset)+nk+2*ne+np+
2846    nki+nwi+nkf+nwf+nkm+nwm+nkr+nwr+nkg+nwg+nkp+nwp;
2847    nco:=29+lh+nki+nwi+nkf+nwf+nkm+nwm+nkr+nwr+nkg+nwg+nkp+nwp;
2848    end;
2849  end;
2850
2851@ @d out_size(#)==out((#) div 256); out((#) mod 256)
2852  @d out_integer(#)==out((#) div @"1000000);
2853                     out(((#) mod @"1000000) div @"10000);
2854                     out(((#) mod @"10000) div @"100);
2855                     out((#) mod @"100)
2856
2857@<Output the subfile sizes@>=
2858case ofm_level of
2859  -1: begin
2860    out_size(lf); out_size(lh); out_size(bc); out_size(ec);
2861    out_size(memory[width]); out_size(memory[height]);
2862    out_size(memory[depth]); out_size(memory[italic]);
2863    out_size(nl+lk_offset); out_size(nk); out_size(ne); out_size(np);
2864    end;
2865  0: begin
2866    out_integer(0);
2867    out_integer(lf); out_integer(lh); out_integer(bc); out_integer(ec);
2868    out_integer(memory[width]); out_integer(memory[height]);
2869    out_integer(memory[depth]); out_integer(memory[italic]);
2870    out_integer(nl+lk_offset); out_integer(nk);
2871    out_integer(ne); out_integer(np); out_integer(font_dir);
2872    end;
2873  1: begin
2874    out_integer(1);
2875    out_integer(lf); out_integer(lh);
2876    out_integer(bc); out_integer(ec);
2877    out_integer(memory[width]); out_integer(memory[height]);
2878    out_integer(memory[depth]); out_integer(memory[italic]);
2879    out_integer(nl+lk_offset); out_integer(nk);
2880    out_integer(ne); out_integer(np); out_integer(font_dir);
2881    out_integer(nco); out_integer(ncw); out_integer(npc);
2882    out_integer(nki); out_integer(nwi); out_integer(nkf); out_integer(nwf);
2883    out_integer(nkm); out_integer(nwm); out_integer(nkr); out_integer(nwr);
2884    out_integer(nkg); out_integer(nwg); out_integer(nkp); out_integer(nwp);
2885    end;
2886  end;
2887
2888@ The routines that follow need a few temporary variables of different types.
2889
2890@<Gl...@>=
2891@!j:0..max_header_bytes; {index into |header_bytes|}
2892@!p:pointer; {index into |memory|}
2893@!q:width..italic; {runs through the list heads for dimensions}
2894@!par_ptr:0..max_param_words; {runs through the parameters}
2895
2896@ The header block follows the subfile sizes. The necessary information all
2897appears in |header_bytes|, except that the design size and the seven-bit-safe
2898flag must still be set.
2899
2900@<Output the header block@>=
2901if not check_sum_specified then @<Compute the check sum@>;
2902header_bytes[design_size_loc]:=design_size div @'100000000;
2903  {this works since |design_size>0|}
2904header_bytes[design_size_loc+1]:=(design_size div @'200000) mod 256;
2905header_bytes[design_size_loc+2]:=(design_size div 256) mod 256;
2906header_bytes[design_size_loc+3]:=design_size  mod 256;
2907if (not seven_unsafe)and(ofm_level=-1) then header_bytes[seven_flag_loc]:=128;
2908for j:=0 to header_ptr-1 do out(header_bytes[j]);
2909
2910@ @<Compute the check sum@>=
2911begin c0:=bc; c1:=ec; c2:=bc; c3:=ec;
2912for c:=bc to ec do if char_wd[c]>0 then
2913  begin temp_width:=memory[char_wd[c]];
2914  if design_units<>unity then
2915    temp_width:=round((temp_width/design_units)*1048576.0);
2916  temp_width:=temp_width + (c+4)*@'20000000; {this should be positive}
2917  c0:=(c0+c0+temp_width) mod 255;
2918  c1:=(c1+c1+temp_width) mod 253;
2919  c2:=(c2+c2+temp_width) mod 251;
2920  c3:=(c3+c3+temp_width) mod 247;
2921  end;
2922header_bytes[check_sum_loc]:=c0;
2923header_bytes[check_sum_loc+1]:=c1;
2924header_bytes[check_sum_loc+2]:=c2;
2925header_bytes[check_sum_loc+3]:=c3;
2926end
2927
2928@ @<Global...@>=
2929@!tab:integer;
2930
2931@
2932@<Compute the character info size@>=
2933if ofm_level=1 then begin
2934  ncw:=0;
2935  incr(npc);
2936  needed_space:=(12+npc*2) div 4;
2937  extra_bytes:=(needed_space*4) - (10+npc*2);
2938  for c:=bc to ec do begin
2939    if char_original[c]=c then begin
2940      cprime:=c+1;
2941      diff:=false;
2942      while (not diff) and (cprime<=ec) do begin
2943        if index[char_wd[c]]<>index[char_wd[cprime]] then diff:=true;
2944        if index[char_ht[c]]<>index[char_ht[cprime]] then diff:=true;
2945        if index[char_dp[c]]<>index[char_dp[cprime]] then diff:=true;
2946        if index[char_ic[c]]<>index[char_ic[cprime]] then diff:=true;
2947        if char_tag[c]<>char_tag[cprime] then diff:=true;
2948        if char_remainder[c]<>char_remainder[cprime] then diff:=true;
2949        for tab:=0 to npc-1 do begin
2950          if char_table[c,tab]<>char_table[cprime,tab] then diff:=true;
2951          end;
2952        if not diff then begin
2953          char_original[cprime]:=c;
2954          cprime:=cprime+1;
2955          end;
2956        end;
2957      if cprime>(c+1) then begin
2958        char_repeats[c]:=cprime-c-1;
2959        end;
2960      ncw:=ncw+needed_space;
2961      end;
2962    end;
2963  end;
2964
2965@ The next block contains packed |char_info|.
2966
2967@<Output the character info@>=
2968index[0]:=0;
2969for c:=bc to ec do
2970case ofm_level of
2971  -1: begin
2972    out(index[char_wd[c]]);
2973    out(index[char_ht[c]]*16+index[char_dp[c]]);
2974    out(index[char_ic[c]]*4+char_tag[c]);
2975    out(char_remainder[c]);
2976    end;
2977  0: begin
2978    out(index[char_wd[c]] div 256); out(index[char_wd[c]] mod 256);
2979    out(index[char_ht[c]]); out(index[char_dp[c]]);
2980    out(index[char_ic[c]]); out(char_tag[c]);
2981    out(char_remainder[c] div 256); out(char_remainder[c] mod 256);
2982    end;
2983  1: begin
2984    if c=char_original[c] then begin
2985      out(index[char_wd[c]] div 256); out(index[char_wd[c]] mod 256);
2986      out(index[char_ht[c]]); out(index[char_dp[c]]);
2987      out(index[char_ic[c]]);
2988      tab:=char_tag[c];
2989      if char_extended_tag[c] then begin
2990        tab:=5;
2991        end;
2992      out(tab);
2993      out(char_remainder[c] div 256); out(char_remainder[c] mod 256);
2994      out_size(char_repeats[c]);
2995      for tab:=0 to npc-1 do begin
2996        out(char_table[c,tab] div 256); out(char_table[c,tab] mod 256);
2997        end;
2998      for tab:=1 to extra_bytes do begin
2999        out(0);
3000        end;
3001      end;
3002    end;
3003  end;
3004
3005@ When a scaled quantity is output, we may need to divide it by |design_units|.
3006The following subroutine takes care of this, using floating point arithmetic
3007only if |design_units<>1.0|.
3008
3009@p procedure out_scaled(x:fix_word); {outputs a scaled |fix_word|}
3010var @!n:byte; {the first byte after the sign}
3011@!m:0..65535; {the two least significant bytes}
3012begin if abs(x/design_units)>=16.0 then
3013  begin print_ln('The relative dimension ',x/@'4000000:1:3,
3014    ' is too large.');
3015@.The relative dimension...@>
3016  print('  (Must be less than 16*designsize');
3017  if design_units<>unity then print(' =',design_units/@'200000:1:3,
3018      ' designunits');
3019  print_ln(')'); x:=0;
3020  end;
3021if design_units<>unity then x:=round((x/design_units)*1048576.0);
3022if x<0 then
3023  begin out(255); x:=x+@'100000000;
3024  if x<=0 then x:=1;
3025  end
3026else begin out(0);
3027  if x>=@'100000000 then x:=@'77777777;
3028  end;
3029n:=x div @'200000; m:=x mod @'200000;
3030out(n); out(m div 256); out(m mod 256);
3031end;
3032
3033@ We have output the packed indices for individual characters.
3034The scaled widths, heights, depths, and italic corrections are next.
3035
3036@<Output the dimensions themselves@>=
3037for q:=width to italic do
3038  begin out(0); out(0); out(0); out(0); {output the zero word}
3039  p:=link[q]; {head of list}
3040  while p>0 do
3041    begin out_scaled(memory[p]);
3042    p:=link[p];
3043    end;
3044  end;
3045
3046@ One embarrassing problem remains: The ligature/kern program might be very
3047long, but the starting addresses in |char_remainder| can be at most~65535.
3048Therefore we need to output some indirect address information; we want to
3049compute |lk_offset| so that addition of |lk_offset| to all remainders makes
3050all but |lk_offset| distinct remainders less than~65535.
3051
3052For this we need a sorted table of all relevant remainders.
3053
3054@<Glob...@>=
3055@!label_table:array[xchar_type] of record
3056  @!rr: -1..xmax_label; {sorted label values}
3057  @!cc: integer; {associated characters}
3058  end;
3059@!label_ptr:xchar_type; {index of highest entry in |label_table|}
3060@!sort_ptr:xchar_type; {index into |label_table|}
3061@!lk_offset:xchar_type; {smallest offset value that might work}
3062@!t:0..xmax_label; {label value that is being redirected}
3063@!extra_loc_needed:boolean; {do we need a special word for |bchar|?}
3064
3065@ @<Compute the ligature/kern program offset@>=
3066@<Insert all labels into |label_table|@>;
3067if bchar<xmax_char then
3068  begin extra_loc_needed:=true; lk_offset:=1;
3069  end
3070else begin extra_loc_needed:=false; lk_offset:=0;
3071  end;
3072@<Find the minimum |lk_offset| and adjust all remainders@>;
3073if bchar_label<xmax_label then begin
3074  if ofm_level=-1 then begin
3075    lig_kern[nl-1].b2:=(bchar_label+lk_offset)div 256;
3076    lig_kern[nl-1].b3:=(bchar_label+lk_offset)mod 256;
3077    end
3078  else begin
3079    lig_kern[nl-1].b2:=(bchar_label+lk_offset)div 65536;
3080    lig_kern[nl-1].b3:=(bchar_label+lk_offset)mod 65536;
3081    end
3082  end
3083
3084@ @<Insert all labels...@>=
3085label_ptr:=0; label_table[0].rr:=-1; {sentinel}
3086for c:=bc to ec do if char_tag[c]=lig_tag then
3087  begin sort_ptr:=label_ptr; {there's a hole at position |sort_ptr+1|}
3088  while label_table[sort_ptr].rr>char_remainder[c] do
3089    begin label_table[sort_ptr+1]:=label_table[sort_ptr];
3090    decr(sort_ptr); {move the hole}
3091    end;
3092  label_table[sort_ptr+1].cc:=c;
3093  label_table[sort_ptr+1].rr:=char_remainder[c];
3094  incr(label_ptr);
3095  end
3096
3097@ @<Find the minimum |lk_offset| and adjust all remainders@>=
3098begin sort_ptr:=label_ptr; {the largest unallocated label}
3099if ofm_level=-1 then begin
3100  if label_table[sort_ptr].rr+lk_offset > 255 then begin
3101    lk_offset:=0; extra_loc_needed:=false; {location 0 can do double duty}
3102    repeat char_remainder[label_table[sort_ptr].cc]:=lk_offset;
3103    while label_table[sort_ptr-1].rr=label_table[sort_ptr].rr do begin
3104      decr(sort_ptr); char_remainder[label_table[sort_ptr].cc]:=lk_offset;
3105      end;
3106    incr(lk_offset); decr(sort_ptr);
3107    until lk_offset+label_table[sort_ptr].rr<256;
3108      {N.B.: |lk_offset=256| satisfies this when |sort_ptr=0|}
3109    end;
3110  end
3111else begin
3112  if label_table[sort_ptr].rr+lk_offset > 65535 then begin
3113    lk_offset:=0; extra_loc_needed:=false; {location 0 can do double duty}
3114    repeat char_remainder[label_table[sort_ptr].cc]:=lk_offset;
3115    while label_table[sort_ptr-1].rr=label_table[sort_ptr].rr do begin
3116      decr(sort_ptr); char_remainder[label_table[sort_ptr].cc]:=lk_offset;
3117      end;
3118    incr(lk_offset); decr(sort_ptr);
3119    until lk_offset+label_table[sort_ptr].rr<65536;
3120      {N.B.: |lk_offset=65536| satisfies this when |sort_ptr=0|}
3121    end;
3122  end;
3123if lk_offset>0 then while sort_ptr>0 do
3124  begin char_remainder[label_table[sort_ptr].cc]:=
3125    char_remainder[label_table[sort_ptr].cc]+lk_offset;
3126  decr(sort_ptr);
3127  end;
3128end
3129
3130@ @<Output the ligature/kern program@>=
3131if ofm_level=-1 then begin
3132  if extra_loc_needed then begin {|lk_offset=1|}
3133    out(255); out(bchar); out(0); out(0);
3134    end
3135  else for sort_ptr:=1 to lk_offset do begin {output the redirection specs}
3136    t:=label_table[label_ptr].rr;
3137    if bchar<256 then begin
3138      out(255); out(bchar);
3139      end
3140    else begin
3141      out(254); out(0);
3142      end;
3143    out_size(t+lk_offset);
3144    repeat decr(label_ptr); until label_table[label_ptr].rr<t;
3145    end;
3146  if nl>0 then for lig_ptr:=0 to nl-1 do begin
3147    out(lig_kern[lig_ptr].b0);
3148    out(lig_kern[lig_ptr].b1);
3149    out(lig_kern[lig_ptr].b2);
3150    out(lig_kern[lig_ptr].b3);
3151    end;
3152  if nk>0 then for krn_ptr:=0 to nk-1 do out_scaled(kern[krn_ptr])
3153  end
3154else begin
3155  if extra_loc_needed then {|lk_offset=1|} begin
3156    out_size(255); out_size(bchar); out_size(0); out_size(0);
3157    end
3158  else
3159    for sort_ptr:=1 to lk_offset do {output the redirection specs} begin
3160    t:=label_table[label_ptr].rr;
3161    if bchar<xmax_char then begin
3162      out_size(255); out_size(bchar);
3163      end
3164    else begin
3165      out_size(254); out_size(0);
3166      end;
3167    out_size((t+lk_offset) div 256);
3168    out_size((t+lk_offset) mod 256);
3169    repeat decr(label_ptr); until label_table[label_ptr].rr<t;
3170    end;
3171  if nl>0 then for lig_ptr:=0 to nl-1 do begin
3172    out_size(lig_kern[lig_ptr].b0);
3173    out_size(lig_kern[lig_ptr].b1);
3174    out_size(lig_kern[lig_ptr].b2);
3175    out_size(lig_kern[lig_ptr].b3);
3176    end;
3177  if nk>0 then for krn_ptr:=0 to nk-1 do out_scaled(kern[krn_ptr])
3178  end
3179
3180@ @<Output the extensible character recipes@>=
3181if ofm_level=-1 then begin
3182  if ne>0 then for c:=0 to ne-1 do begin
3183    out(exten[c].b0);
3184    out(exten[c].b1);
3185    out(exten[c].b2);
3186    out(exten[c].b3);
3187    end;
3188  end
3189else begin
3190  if ne>0 then for c:=0 to ne-1 do begin
3191    out_size(exten[c].b0);
3192    out_size(exten[c].b1);
3193    out_size(exten[c].b2);
3194    out_size(exten[c].b3);
3195    end;
3196  end;
3197
3198@ For our grand finale, we wind everything up by outputting the parameters.
3199
3200@<Output the parameters@>=
3201for par_ptr:=1 to np do
3202  begin if par_ptr=1 then
3203    @<Output the slant (|param[1]|) without scaling@>
3204  else out_scaled(param[par_ptr]);
3205  end
3206
3207@ @<Output the slant...@>=
3208begin if param[1]<0 then
3209  begin param[1]:=param[1]+@'10000000000;
3210  out((param[1] div @'100000000)+256-64);
3211  end
3212else out(param[1] div @'100000000);
3213out((param[1] div @'200000) mod 256);
3214out((param[1] div 256) mod 256);
3215out(param[1] mod 256);
3216end
3217
3218@* The main program.
3219The routines sketched out so far need to be packaged into separate procedures,
3220on some systems, since some \PASCAL\ compilers place a strict limit on the
3221size of a routine. The packaging is done here in an attempt to avoid some
3222system-dependent changes.
3223
3224@p procedure param_enter;
3225begin @<Enter the parameter names@>;
3226end;
3227@#
3228procedure name_enter; {enter all names and their equivalents}
3229begin @<Enter all of the names...@>;
3230param_enter;
3231end;
3232@#
3233procedure read_lig_kern;
3234var @!krn_ptr:0..max_kerns; {an index into |kern|}
3235@!c:integer; {runs through all character codes}
3236begin @<Read ligature/kern list@>;
3237end;
3238@#
3239procedure output_new_information_ofm;
3240begin @<Output the new information for OFM files@>;
3241end;
3242@#
3243procedure compute_new_header_ofm;
3244begin @<Compute the new header information for OFM files@>;
3245end;
3246@#
3247procedure finish_extended_font;
3248begin @<Finish up the extended font stuff@>;
3249end;
3250@#
3251procedure output_subfile_sizes;
3252begin @<Output the subfile sizes@>;
3253end;
3254@#
3255procedure compute_subfile_sizes;
3256begin @<Compute the subfile sizes@>;
3257end;
3258@#
3259procedure output_character_info;
3260begin @<Output the character info@>;
3261end;
3262@#
3263procedure read_font_rule_list;
3264begin @<Read font rule list@>;
3265end;
3266@#
3267procedure read_font_glue_list;
3268begin @<Read font glue list@>;
3269end;
3270@#
3271procedure read_font_penalty_list;
3272begin @<Read font penalty list@>;
3273end;
3274@#
3275procedure read_font_mvalue_list;
3276begin @<Read font mvalue list@>;
3277end;
3278@#
3279procedure read_font_fvalue_list;
3280begin @<Read font fvalue list@>;
3281end;
3282@#
3283procedure read_font_ivalue_list;
3284begin @<Read font ivalue list@>;
3285end;
3286@#
3287procedure read_repeated_character_info;
3288begin @<Read repeated character info@>;
3289end;
3290@#
3291procedure read_lig_kern_command;
3292begin @<Read a ligature/kern command@>;
3293end;
3294@#
3295procedure read_character_property;
3296begin @<Read a character property@>;
3297end;
3298@#
3299procedure read_char_info;
3300begin @<Read character info list@>;
3301end;
3302@#
3303procedure read_input;
3304var @!c:integer; {header or parameter index}
3305begin @<Read all the input@>;
3306end;
3307@#
3308procedure corr_and_check;
3309var @!c:integer; {runs through all character codes}
3310@!hh:0..hash_size; {an index into |hash_list|}
3311@!lig_ptr:0..max_lig_steps; {an index into |lig_kern|}
3312@!g:byte; {a character generated by the current character |c|}
3313begin @<Correct and check the information@>
3314end;
3315
3316@ Here is where \.{PLtoTF} begins and ends.
3317
3318@p begin initialize;@/
3319name_enter;@/
3320read_input; print('.');@/
3321corr_and_check;@/
3322@<Do the font metric output@>;
3323end.
3324
3325@ @<Global...@>=
3326@!ofm_level:integer;
3327
3328@ @<Set init...@>=
3329ofm_level:=-1; {Suppose that it is a TFM file}
3330
3331@ @<Read OFM level code@>=
3332begin
3333ofm_level:=get_integer;
3334if (ofm_level<0) or (ofm_level>1) then begin
3335  flush_error('OFMLEVEL must be 0 or 1 -- 1 assumed');
3336  ofm_level:=1;
3337  end;
3338end
3339
3340@ @<Read font direction code@>=
3341begin
3342font_dir:=-1;
3343repeat get_next;
3344until cur_char<>" ";
3345case cur_char of
3346  "T": begin get_next;
3347    if cur_char="L" then font_dir:=0
3348    else if cur_char="R" then font_dir:=2;
3349    end;
3350  "B": begin get_next;
3351    if cur_char="L" then font_dir:=4
3352    else if cur_char="R" then font_dir:=6;
3353    end;
3354  "R": begin get_next;
3355    if cur_char="T" then font_dir:=5
3356    else if cur_char="B" then font_dir:=7;
3357    end;
3358  "L": begin get_next;
3359    if cur_char="T" then font_dir:=1
3360    else if cur_char="B" then font_dir:=3;
3361    end;
3362  end;
3363while cur_char<>")" do get_next;
3364if font_dir = -1 then begin
3365  flush_error('FONTDIR must be valid direction, -- TL assumed');
3366  font_dir:=0;
3367  end;
3368end
3369
3370@ @<Read natural font direction code@>=
3371begin
3372font_dir:=-1;
3373repeat get_next;
3374until cur_char<>" ";
3375case cur_char of
3376  "T": begin get_next;
3377    if cur_char="L" then font_dir:=8
3378    else if cur_char="R" then font_dir:=10;
3379    end;
3380  "B": begin get_next;
3381    if cur_char="L" then font_dir:=12
3382    else if cur_char="R" then font_dir:=14;
3383    end;
3384  "R": begin get_next;
3385    if cur_char="T" then font_dir:=13
3386    else if cur_char="B" then font_dir:=15;
3387    end;
3388  "L": begin get_next;
3389    if cur_char="T" then font_dir:=9
3390    else if cur_char="B" then font_dir:=11;
3391    end;
3392  end;
3393while cur_char<>")" do get_next;
3394if font_dir = -1 then begin
3395  flush_error('NFONTDIR must be valid direction, -- TL assumed');
3396  font_dir:=8;
3397  end;
3398end
3399
3400@
3401Here are some general values for the various entries.
3402They can all be changed.
3403
3404@d arrays_per_kind==20
3405@d entries_per_array==200
3406
3407@ @<Constants...@>=
3408@!rule_arrays=arrays_per_kind;
3409@!rule_entries=entries_per_array;
3410
3411@ @<Types...@>=
3412rule_array_type=0..rule_arrays;
3413rule_entry_type=0..rule_entries;
3414rule_node=
3415record
3416  rn_width:     fix_word;
3417  rn_height:    fix_word;
3418  rn_depth:     fix_word;
3419end;
3420
3421@ @<Global...@>=
3422@!rules:array[rule_array_type,rule_entry_type] of rule_node;
3423@!npr:array[rule_array_type] of integer;
3424@!nkr:integer;
3425@!nwr:integer;
3426@!r_array:integer;
3427@!r_number:integer;
3428
3429@ @<Set init...@>=
3430for r_array := 0 to rule_arrays do begin
3431  npr[r_array]:=0;
3432  @<Null out the rule@>;
3433  end;
3434nkr:=-1;
3435
3436@ @<Read font rule list@>=
3437begin
3438if tables_read then
3439  flush_error('All parameter tables must appear before character info');
3440r_array:=get_integer;
3441if r_array>rule_arrays then
3442  flush_error('This FONTRULE table index is too big for my present size')
3443else if r_array<0 then
3444  flush_error('This FONTRULE index is negative')
3445else begin
3446  if r_array>nkr then nkr:=r_array;
3447  while level=1 do begin
3448    while cur_char=" " do get_next;
3449    if cur_char="(" then @<Read a rule@>
3450    else if cur_char=")" then skip_to_end_of_item
3451    else junk_error;
3452    end;
3453  finish_inner_property_list;
3454  end;
3455end
3456
3457@ @<Read a rule@>=
3458begin
3459get_name;
3460if cur_code=comment_code then skip_to_end_of_item
3461else if cur_code<>rule_code then
3462  flush_error('This property name doesn''t belong in a FONTRULE list')
3463else begin
3464  r_number:=get_integer;
3465  if r_number>rule_entries then
3466    flush_error('This RULE index is too big for my present table size')
3467  else if r_number<0 then
3468    flush_error('This RULE index is negative')
3469  else begin
3470    while npr[r_array]<r_number do begin
3471      incr(npr[r_array]); @<Null out the rule@>;
3472      end;
3473    @<Read all of a rule's values@>;
3474    finish_the_property;
3475    end;
3476  end;
3477end
3478
3479@ @<Null out the rule@>=
3480begin
3481rules[r_array,npr[r_array]].rn_width:=0;
3482rules[r_array,npr[r_array]].rn_depth:=0;
3483rules[r_array,npr[r_array]].rn_height:=0;
3484end
3485
3486@ @<Read all of a rule's values@>=
3487begin
3488while level=2 do begin
3489  while cur_char=" " do get_next;
3490  if cur_char="(" then @<Read a single rule value@>
3491  else if cur_char=")" then skip_to_end_of_item
3492  else junk_error;
3493  end;
3494finish_inner_property_list;
3495end
3496
3497@ @<Read a single rule value@>=
3498begin
3499get_name;
3500if cur_code=comment_code then skip_to_end_of_item
3501else if (cur_code<rule_width_code)or(cur_code>rule_depth_code) then
3502  flush_error('This property name doesn''t belong in a RULE list')
3503else begin
3504  case cur_code of
3505    rule_width_code:
3506      rules[r_array,r_number].rn_width:=get_fix;
3507    rule_height_code:
3508      rules[r_array,r_number].rn_height:=get_fix;
3509    rule_depth_code:
3510      rules[r_array,r_number].rn_depth:=get_fix;
3511    end;
3512  finish_the_property;
3513  end;
3514end
3515
3516@ @<Header information for rules@>=
3517begin
3518nwr:=0;
3519for r_array := 0 to nkr do begin
3520  incr(npr[r_array]);
3521  nwr := nwr + 3*npr[r_array];
3522  end;
3523incr(nkr);
3524end
3525
3526@ @<Output the rules@>=
3527begin
3528for r_array:= 0 to nkr-1 do
3529  for r_number:=0 to npr[r_array]-1 do begin
3530    out_scaled(rules[r_array,r_number].rn_width);
3531    out_scaled(rules[r_array,r_number].rn_height);
3532    out_scaled(rules[r_array,r_number].rn_depth);
3533    end;
3534end
3535
3536@ @<Output the rule headers@>=
3537begin
3538for r_array:= 0 to nkr-1 do begin
3539  out_integer(npr[r_array]);
3540  end;
3541end
3542
3543@ @<Constants...@>=
3544@!glue_arrays=arrays_per_kind;
3545@!glue_entries=entries_per_array;
3546
3547@
3548@d t_normal==0
3549@d t_aleaders==1
3550@d t_cleaders==2
3551@d t_xleaders==3
3552
3553@d o_unit==0
3554@d o_fi==1
3555@d o_fil==2
3556@d o_fill==3
3557@d o_filll==4
3558
3559@d g_space==0
3560@d g_rule==1
3561@d g_char==2
3562
3563@<Types...@>=
3564glue_array_type=0..glue_arrays;
3565glue_entry_type=0..glue_entries;
3566glue_node=
3567record
3568  gn_width:             fix_word;
3569  gn_stretch:           fix_word;
3570  gn_shrink:            fix_word;
3571  gn_type:              integer;
3572  gn_arg_type:          g_space..g_char;
3573  gn_stretch_order:     integer;
3574  gn_shrink_order:      integer;
3575  gn_argument:          integer;
3576end;
3577
3578@ @<Global...@>=
3579@!glues:array[glue_array_type,glue_entry_type] of glue_node;
3580@!npg:array[glue_array_type] of integer;
3581@!nkg:integer;
3582@!nwg:integer;
3583@!g_array:integer;
3584@!g_byte:integer;
3585@!g_number:integer;
3586
3587@ @<Set init...@>=
3588for g_array := 0 to glue_arrays do
3589begin
3590  npg[g_array]:=0;
3591  @<Null out the glue@>;
3592end;
3593nkg:=-1;
3594
3595@ @<Read font glue list@>=
3596begin
3597if tables_read then
3598  flush_error('All parameter tables must appear before character info');
3599g_array:=get_integer;
3600if g_array>glue_arrays then
3601  flush_error('This FONTGLUE table index is too big for my present size')
3602else if g_array<0 then
3603  flush_error('This FONTGLUE index is negative')
3604else begin
3605  if g_array>nkg then nkg:=g_array;
3606  while level=1 do begin
3607    while cur_char=" " do get_next;
3608    if cur_char="(" then @<Read a glue@>
3609    else if cur_char=")" then skip_to_end_of_item
3610    else junk_error;
3611    end;
3612  finish_inner_property_list;
3613  end;
3614end
3615
3616@ @<Read a glue@>=
3617begin
3618get_name;
3619if cur_code=comment_code then skip_to_end_of_item
3620else if cur_code<>glue_code then
3621  flush_error('This property name doesn''t belong in a FONTGLUE list')
3622else begin
3623  g_number:=get_integer;
3624  if g_number>glue_entries then
3625    flush_error('This GLUE index is too big for my present table size')
3626  else if g_number<0 then
3627    flush_error('This GLUE index is negative')
3628  else begin
3629    while npg[g_array]<g_number do begin
3630      incr(npg[g_array]); @<Null out the glue@>;
3631      end;
3632    @<Read all of a glue's values@>;
3633    finish_the_property;
3634    end;
3635  end;
3636end
3637
3638@ @<Null out the glue@>=
3639begin
3640glues[g_array,npg[g_array]].gn_width:=0;
3641glues[g_array,npg[g_array]].gn_stretch:=0;
3642glues[g_array,npg[g_array]].gn_shrink:=0;
3643glues[g_array,npg[g_array]].gn_type:=0;
3644glues[g_array,npg[g_array]].gn_arg_type:=0;
3645glues[g_array,npg[g_array]].gn_stretch_order:=0;
3646glues[g_array,npg[g_array]].gn_shrink_order:=0;
3647glues[g_array,npg[g_array]].gn_argument:=0;
3648end
3649
3650@ @<Read all of a glue's values@>=
3651begin
3652while level=2 do begin
3653  while cur_char=" " do get_next;
3654  if cur_char="(" then @<Read a single glue value@>
3655  else if cur_char=")" then skip_to_end_of_item
3656  else junk_error;
3657  end;
3658finish_inner_property_list;
3659end
3660
3661@ @<Read a single glue value@>=
3662begin
3663get_name;
3664if cur_code=comment_code then skip_to_end_of_item
3665else if (cur_code<glue_type_code)or(cur_code>glue_rule_code) then
3666  flush_error('This property name doesn''t belong in a GLUE list')
3667else begin
3668  case cur_code of
3669    glue_width_code:
3670      glues[g_array,g_number].gn_width:=get_fix;
3671    glue_stretch_code:
3672      glues[g_array,g_number].gn_stretch:=get_fix;
3673    glue_shrink_code:
3674      glues[g_array,g_number].gn_shrink:=get_fix;
3675    glue_type_code: begin
3676      g_byte:=get_integer;
3677      if (g_byte<0) or (g_byte>3) then begin
3678        g_byte:=0;
3679        end;
3680      glues[g_array,g_number].gn_type:=g_byte;
3681      end;
3682    glue_stretch_order_code: begin
3683      g_byte:=get_integer;
3684      if (g_byte<0) or (g_byte>4) then begin
3685        g_byte:=0;
3686        end;
3687      glues[g_array,g_number].gn_stretch_order:=g_byte;
3688      end;
3689    glue_shrink_order_code: begin
3690      g_byte:=get_integer;
3691      if (g_byte<0) or (g_byte>4) then begin
3692        g_byte:=0;
3693        end;
3694      glues[g_array,g_number].gn_shrink_order:=g_byte;
3695      end;
3696    glue_char_code: begin
3697      glues[g_array,g_number].gn_argument:=get_integer;
3698      glues[g_array,g_number].gn_arg_type:=g_char;
3699      end;
3700    glue_rule_code: begin
3701      glues[g_array,g_number].gn_argument:=get_integer;
3702      glues[g_array,g_number].gn_arg_type:=g_rule;
3703      end;
3704    end;
3705  finish_the_property;
3706  end;
3707end
3708
3709@ @<Header information for glues@>=
3710begin
3711nwg:=0;
3712for g_array := 0 to nkg do begin
3713  incr(npg[g_array]);
3714  nwg := nwg + 4*npg[g_array];
3715  end;
3716incr(nkg);
3717end
3718
3719@ @<Output the glues@>=
3720begin
3721for g_array:= 0 to nkg-1 do
3722  for g_number:=0 to npg[g_array]-1 do begin
3723    g_byte:=glues[g_array,g_number].gn_type*16+
3724            glues[g_array,g_number].gn_arg_type;
3725    out(g_byte);
3726    g_byte:=glues[g_array,g_number].gn_stretch_order*16+
3727            glues[g_array,g_number].gn_shrink_order;
3728    out(g_byte);
3729    g_byte:=glues[g_array,g_number].gn_argument div 256;
3730    out(g_byte);
3731    g_byte:=glues[g_array,g_number].gn_argument mod 256;
3732    out(g_byte);
3733    out_scaled(glues[g_array,g_number].gn_width);
3734    out_scaled(glues[g_array,g_number].gn_stretch);
3735    out_scaled(glues[g_array,g_number].gn_shrink);
3736  end;
3737end
3738
3739@ @<Output the glue headers@>=
3740begin
3741for g_array:= 0 to nkg-1 do begin
3742  out_integer(npg[g_array]);
3743  end;
3744end
3745
3746@ @<Constants...@>=
3747@!penalty_arrays=arrays_per_kind;
3748@!penalty_entries=entries_per_array;
3749
3750@ @<Types...@>=
3751penalty_array_type=0..penalty_arrays;
3752penalty_entry_type=0..penalty_entries;
3753penalty_node=
3754record
3755  pn_val: integer;
3756end;
3757
3758@ @<Global...@>=
3759@!penalties:array[penalty_array_type,penalty_entry_type] of penalty_node;
3760@!npp:array[penalty_array_type] of integer;
3761@!nkp:integer;
3762@!nwp:integer;
3763@!p_array:integer;
3764@!p_number:integer;
3765
3766@ @<Set init...@>=
3767for p_array := 0 to penalty_arrays do begin
3768  npp[p_array]:=0;
3769  @<Null out the penalty@>;
3770  end;
3771nkp:=-1;
3772
3773@ @<Read font penalty list@>=
3774begin
3775if tables_read then
3776  flush_error('All parameter tables must appear before character info');
3777p_array:=get_integer;
3778if p_array>penalty_arrays then
3779  flush_error('This FONTPENALTY table index is too big for my present size')
3780else if p_array<0 then
3781  flush_error('This FONTPENALTY index is negative')
3782else begin
3783  if p_array>nkp then nkp:=p_array;
3784  while level=1 do begin
3785    while cur_char=" " do get_next;
3786    if cur_char="(" then @<Read a penalty@>
3787    else if cur_char=")" then skip_to_end_of_item
3788    else junk_error;
3789    end;
3790  finish_inner_property_list;
3791  end;
3792end
3793
3794@ @<Read a penalty@>=
3795begin
3796get_name;
3797if cur_code=comment_code then skip_to_end_of_item
3798else if cur_code<>penalty_code then
3799  flush_error('This property name doesn''t belong in a FONTPENALTY list')
3800else begin
3801  p_number:=get_integer;
3802  if p_number>penalty_entries then
3803    flush_error('This PENALTY index is too big for my present table size')
3804  else if p_number<0 then
3805    flush_error('This PENALTY index is negative')
3806  else begin
3807    while npp[p_array]<p_number do begin
3808      incr(npp[p_array]); @<Null out the penalty@>;
3809      end;
3810    @<Read all of a penalty's values@>;
3811    finish_the_property;
3812    end;
3813  end;
3814end
3815
3816@ @<Null out the penalty@>=
3817begin
3818penalties[p_array,npp[p_array]].pn_val:=0;
3819end
3820
3821@ @<Read all of a penalty's values@>=
3822begin
3823while level=2 do begin
3824  while cur_char=" " do get_next;
3825  if cur_char="(" then @<Read a single penalty value@>
3826  else if cur_char=")" then skip_to_end_of_item
3827  else junk_error;
3828  end;
3829finish_inner_property_list;
3830end
3831
3832@ @<Read a single penalty value@>=
3833begin
3834get_name;
3835if cur_code=comment_code then skip_to_end_of_item
3836else if cur_code<>penalty_val_code then
3837  flush_error('This property name doesn''t belong in a PENALTY list')
3838else  begin
3839  penalties[p_array,p_number].pn_val:=get_integer;
3840  finish_the_property;
3841  end;
3842end
3843
3844@ @<Header information for penalties@>=
3845begin
3846nwp:=0;
3847for p_array := 0 to nkp do begin
3848  incr(npp[p_array]);
3849  nwp := nwp + npp[p_array];
3850  end;
3851incr(nkp);
3852end
3853
3854@ @<Output the penalties@>=
3855begin
3856for p_array:= 0 to nkp-1 do
3857  for p_number:=0 to npp[p_array]-1 do begin
3858    out_int(penalties[p_array,p_number].pn_val);
3859    end;
3860end
3861
3862@ @<Output the penalty headers@>=
3863begin
3864for p_array:= 0 to nkp-1 do begin
3865  out_integer(npp[p_array]);
3866  end;
3867end
3868
3869@ @<Constants...@>=
3870@!mvalue_arrays=arrays_per_kind;
3871@!mvalue_entries=entries_per_array;
3872
3873@ @<Types...@>=
3874mvalue_array_type=0..mvalue_arrays;
3875mvalue_entry_type=0..mvalue_entries;
3876mvalue_node=
3877record
3878  fn_val:     fix_word;
3879end;
3880
3881@ @<Global...@>=
3882@!mvalues:array[mvalue_array_type,mvalue_entry_type] of mvalue_node;
3883@!npm:array[mvalue_array_type] of integer;
3884@!nkm:integer;
3885@!nwm:integer;
3886@!m_array:integer;
3887@!m_number:integer;
3888
3889@ @<Set init...@>=
3890for m_array := 0 to mvalue_arrays do begin
3891  npm[m_array]:=0;
3892  @<Null out the mvalue@>;
3893  end;
3894nkm:=-1;
3895
3896@ @<Read font mvalue list@>=
3897begin
3898if tables_read then
3899  flush_error('All parameter tables must appear before character info');
3900m_array:=get_integer;
3901if m_array>mvalue_arrays then
3902  flush_error('This FONTMVALUE table index is too big for my present size')
3903else if m_array<0 then
3904  flush_error('This FONTMVALUE index is negative')
3905else begin
3906  if m_array>nkm then nkm:=m_array;
3907  while level=1 do begin
3908    while cur_char=" " do get_next;
3909    if cur_char="(" then @<Read an mvalue@>
3910    else if cur_char=")" then skip_to_end_of_item
3911    else junk_error;
3912    end;
3913  finish_inner_property_list;
3914  end;
3915end
3916
3917@ @<Read an mvalue@>=
3918begin
3919get_name;
3920if cur_code=comment_code then skip_to_end_of_item
3921else if cur_code<>mvalue_code then
3922  flush_error('This property name doesn''t belong in an FONTMVALUE list')
3923else begin
3924  m_number:=get_integer;
3925  if m_number>mvalue_entries then
3926    flush_error('This MVALUE index is too big for my present table size')
3927  else if m_number<0 then
3928    flush_error('This MVALUE index is negative')
3929  else begin
3930    while npm[m_array]<m_number do begin
3931      incr(npm[m_array]); @<Null out the mvalue@>;
3932      end;
3933    @<Read all of an mvalue's values@>;
3934    finish_the_property;
3935    end;
3936  end;
3937end
3938
3939@ @<Null out the mvalue@>=
3940begin
3941mvalues[m_array,npm[m_array]].fn_val:=0;
3942end
3943
3944@ @<Read all of an mvalue's values@>=
3945begin
3946while level=2 do begin
3947  while cur_char=" " do get_next;
3948  if cur_char="(" then @<Read a single mvalue value@>
3949  else if cur_char=")" then skip_to_end_of_item
3950  else junk_error;
3951  end;
3952finish_inner_property_list;
3953end
3954
3955@ @<Read a single mvalue value@>=
3956begin
3957get_name;
3958if cur_code=comment_code then skip_to_end_of_item
3959else if cur_code<>mvalue_val_code then
3960  flush_error('This property name doesn''t belong in a MVALUE list')
3961else  begin
3962  mvalues[m_array,m_number].fn_val:=get_fix;
3963  finish_the_property;
3964  end;
3965end
3966
3967@ @<Header information for mvalues@>=
3968begin
3969nwm:=0;
3970for m_array := 0 to nkm do begin
3971  incr(npm[m_array]);
3972  nwm := nwm + npm[m_array];
3973  end;
3974incr(nkm);
3975end
3976
3977@ @<Output the mvalues@>=
3978begin
3979for m_array:= 0 to nkm-1 do
3980  for m_number:=0 to npm[m_array]-1 do begin
3981    out_scaled(mvalues[m_array,m_number].fn_val);
3982    end;
3983end
3984
3985@ @<Output the mvalue headers@>=
3986begin
3987for m_array:= 0 to nkm-1 do begin
3988  out_integer(npm[m_array]);
3989  end;
3990end
3991
3992@ @<Constants...@>=
3993@!fvalue_arrays=arrays_per_kind;
3994@!fvalue_entries=entries_per_array;
3995
3996@ @<Types...@>=
3997fvalue_array_type=0..fvalue_arrays;
3998fvalue_entry_type=0..fvalue_entries;
3999fvalue_node=
4000record
4001  fn_val:     fix_word;
4002end;
4003
4004@ @<Global...@>=
4005@!fvalues:array[fvalue_array_type,fvalue_entry_type] of fvalue_node;
4006@!npf:array[fvalue_array_type] of integer;
4007@!nkf:integer;
4008@!nwf:integer;
4009@!f_array:integer;
4010@!f_number:integer;
4011
4012@ @<Set init...@>=
4013for f_array := 0 to fvalue_arrays do begin
4014  npf[f_array]:=0;
4015  @<Null out the fvalue@>;
4016  end;
4017nkf:=-1;
4018
4019@ @<Read font fvalue list@>=
4020begin
4021if tables_read then
4022  flush_error('All parameter tables must appear before character info');
4023f_array:=get_integer;
4024if f_array>fvalue_arrays then
4025  flush_error('This FONTFVALUE table index is too big for my present size')
4026else if f_array<0 then
4027  flush_error('This FONTFVALUE index is negative')
4028else begin
4029  if f_array>nkf then nkf:=f_array;
4030  while level=1 do begin
4031    while cur_char=" " do get_next;
4032    if cur_char="(" then @<Read an fvalue@>
4033    else if cur_char=")" then skip_to_end_of_item
4034    else junk_error;
4035    end;
4036  finish_inner_property_list;
4037  end;
4038end
4039
4040@ @<Read an fvalue@>=
4041begin
4042get_name;
4043if cur_code=comment_code then skip_to_end_of_item
4044else if cur_code<>fvalue_code then
4045  flush_error('This property name doesn''t belong in an FONTFVALUE list')
4046else begin
4047  f_number:=get_integer;
4048  if f_number>fvalue_entries then
4049    flush_error('This FVALUE index is too big for my present table size')
4050  else if f_number<0 then
4051    flush_error('This FVALUE index is negative')
4052  else begin
4053    while npf[f_array]<f_number do begin
4054      incr(npf[f_array]); @<Null out the fvalue@>;
4055      end;
4056    @<Read all of an fvalue's values@>;
4057    finish_the_property;
4058    end;
4059  end;
4060end
4061
4062@ @<Null out the fvalue@>=
4063begin
4064fvalues[f_array,npf[f_array]].fn_val:=0;
4065end
4066
4067@ @<Read all of an fvalue's values@>=
4068begin
4069while level=2 do begin
4070  while cur_char=" " do get_next;
4071  if cur_char="(" then @<Read a single fvalue value@>
4072  else if cur_char=")" then skip_to_end_of_item
4073  else junk_error;
4074  end;
4075finish_inner_property_list;
4076end
4077
4078@ @<Read a single fvalue value@>=
4079begin
4080get_name;
4081if cur_code=comment_code then skip_to_end_of_item
4082else if cur_code<>fvalue_val_code then
4083  flush_error('This property name doesn''t belong in a FVALUE list')
4084else  begin
4085  fvalues[f_array,f_number].fn_val:=get_fix;
4086  finish_the_property;
4087  end;
4088end
4089
4090@ @<Header information for fvalues@>=
4091begin
4092nwf:=0;
4093for f_array := 0 to nkf do begin
4094  incr(npf[f_array]);
4095  nwf := nwf + npf[f_array];
4096  end;
4097incr(nkf);
4098end
4099
4100@ @<Output the fvalues@>=
4101begin
4102for f_array:= 0 to nkf-1 do
4103  for f_number:=0 to npf[f_array]-1 do begin
4104    out_scaled(fvalues[f_array,f_number].fn_val);
4105    end;
4106end
4107
4108@ @<Output the fvalue headers@>=
4109begin
4110for f_array:= 0 to nkf-1 do begin
4111  out_integer(npf[f_array]);
4112  end;
4113end
4114
4115@ @<Constants...@>=
4116@!ivalue_arrays=arrays_per_kind;
4117@!ivalue_entries=entries_per_array;
4118
4119@ @<Types...@>=
4120ivalue_array_type=0..ivalue_arrays;
4121ivalue_entry_type=0..ivalue_entries;
4122ivalue_node=
4123record
4124  in_val:	integer;
4125end;
4126
4127@ @<Global...@>=
4128@!ivalues:array[ivalue_array_type,ivalue_entry_type] of ivalue_node;
4129@!npi:array[ivalue_array_type] of integer;
4130@!font_i_array:boolean;
4131@!nki:integer;
4132@!nwi:integer;
4133@!i_array:integer;
4134@!i_number:integer;
4135
4136@ @<Set init...@>=
4137for i_array := 0 to ivalue_arrays do begin
4138  npi[i_array]:=0;
4139  @<Null out the ivalue@>;
4140  end;
4141nki:=-1;
4142
4143@ @<Read font ivalue list@>=
4144begin
4145if tables_read then
4146  flush_error('All parameter tables must appear before character info');
4147i_array:=get_integer;
4148if i_array>ivalue_arrays then
4149  flush_error('This FONTIVALUE table index is too big for my present size')
4150else if i_array<0 then
4151  flush_error('This FONTIVALUE index is negative')
4152else begin
4153  if i_array>nki then nki:=i_array;
4154  while level=1 do begin
4155    while cur_char=" " do get_next;
4156    if cur_char="(" then @<Read an ivalue@>
4157    else if cur_char=")" then skip_to_end_of_item
4158    else junk_error;
4159    end;
4160  finish_inner_property_list;
4161  end;
4162end
4163
4164@ @<Read an ivalue@>=
4165begin
4166get_name;
4167if cur_code=comment_code then skip_to_end_of_item
4168else if cur_code<>ivalue_code then
4169  flush_error('This property name doesn''t belong in an FONTIVALUE list')
4170else begin
4171  i_number:=get_integer;
4172  if i_number>ivalue_entries then
4173    flush_error('This IVALUE index is too big for my present table size')
4174  else if i_number<0 then
4175    flush_error('This IVALUE index is negative')
4176  else begin
4177    while npi[i_array]<i_number do begin
4178      incr(npi[i_array]); @<Null out the ivalue@>;
4179      end;
4180    @<Read all of an ivalue's values@>;
4181    finish_the_property;
4182    end;
4183  end;
4184end
4185
4186@ @<Null out the ivalue@>=
4187begin
4188ivalues[i_array,npi[i_array]].in_val:=0;
4189end
4190
4191@ @<Read all of an ivalue's values@>=
4192begin
4193while level=2 do begin
4194  while cur_char=" " do get_next;
4195  if cur_char="(" then @<Read a single ivalue value@>
4196  else if cur_char=")" then skip_to_end_of_item
4197  else junk_error;
4198  end;
4199finish_inner_property_list;
4200end
4201
4202@ @<Read a single ivalue value@>=
4203begin
4204get_name;
4205if cur_code=comment_code then skip_to_end_of_item
4206else if cur_code<>ivalue_val_code then
4207  flush_error('This property name doesn''t belong in a IVALUE list')
4208else  begin
4209  ivalues[i_array,i_number].in_val:=get_integer;
4210  finish_the_property;
4211  end;
4212end
4213
4214@ @<Header information for ivalues@>=
4215begin
4216nwi:=0;
4217for i_array := 0 to nki do begin
4218  incr(npi[i_array]);
4219  nwi := nwi + npi[i_array];
4220  end;
4221incr(nki);
4222end
4223
4224@ @<Output the ivalues@>=
4225begin
4226for i_array:= 0 to nki-1 do
4227  for i_number:=0 to npi[i_array]-1 do begin
4228    out_int(ivalues[i_array,i_number].in_val);
4229    end;
4230end
4231
4232@ @<Output the ivalue headers@>=
4233begin
4234for i_array:= 0 to nki-1 do begin
4235  out_integer(npi[i_array]);
4236  end;
4237end
4238
4239@ @<Compute the new header information for OFM files@>=
4240begin
4241@<Header information for ivalues@>;
4242@<Header information for fvalues@>;
4243@<Header information for mvalues@>;
4244@<Header information for rules@>;
4245@<Header information for glues@>;
4246@<Header information for penalties@>;
4247end
4248
4249@ @<Output the new information for OFM files@>=
4250begin
4251@<Output the ivalue headers@>;
4252@<Output the fvalue headers@>;
4253@<Output the mvalue headers@>;
4254@<Output the rule headers@>;
4255@<Output the glue headers@>;
4256@<Output the penalty headers@>;
4257@<Output the ivalues@>;
4258@<Output the fvalues@>;
4259@<Output the mvalues@>;
4260@<Output the rules@>;
4261@<Output the glues@>;
4262@<Output the penalties@>;
4263end
4264
4265@* System-dependent changes.
4266This section should be replaced, if necessary, by changes to the program
4267that are necessary to make \.{PLtoTF} work at a particular installation.
4268It is usually best to design your change file so that all changes to
4269previous sections preserve the section numbering; then everybody's version
4270will be consistent with the printed program. More extensive changes,
4271which introduce new sections, can be inserted here; then only the index
4272itself will get a new section number.
4273@^system dependencies@>
4274
4275@* Index.
4276Pointers to error messages appear here together with the section numbers
4277where each ident\-i\-fier is used.
4278