diff options
author | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
commit | 487ca4806cc046076293cf6cc5fbba0db282bac7 (patch) | |
tree | 847b412ab5158dd7bdd7ed7e5a4cc3fbca94be32 /Build/source/texk/web2c/omegaware/ovf2ovp.web | |
parent | a3d3111bfe26b8e5f5bc6049dfb2a4ca2edc7881 (diff) |
texk 1
git-svn-id: svn://tug.org/texlive/trunk@1485 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/web2c/omegaware/ovf2ovp.web')
-rw-r--r-- | Build/source/texk/web2c/omegaware/ovf2ovp.web | 3138 |
1 files changed, 3138 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/omegaware/ovf2ovp.web b/Build/source/texk/web2c/omegaware/ovf2ovp.web new file mode 100644 index 00000000000..2e2e374dca5 --- /dev/null +++ b/Build/source/texk/web2c/omegaware/ovf2ovp.web @@ -0,0 +1,3138 @@ +% +% This file is part of the Omega project, which +% is based in the web2c distribution of TeX. +% +% Copyright (c) 1994--2000 John Plaice and Yannis Haralambous +% applies only to the changes to the original vftovp.web. +% +% This program by D. E. Knuth is not copyrighted and can be used freely. +% Version 1 was implemented in December 1989. +% Version 1.1 fixed problems of strict Pascal (April 1990). +% Version 1.2 fixed various bugs found by Peter Breitenlohner (September 1990). + +% Here is TeX material that gets inserted after \input webmac +\def\hang{\hangindent 3em\indent\ignorespaces} +\font\ninerm=cmr9 +\let\mc=\ninerm % medium caps for names like SAIL +\def\PASCAL{Pascal} + +\def\(#1){} % this is used to make section names sort themselves better +\def\9#1{} % this is used for sort keys in the index + +\def\title{OVF2OVP} +\def\contentspagenumber{101} +\def\topofcontents{\null + \def\titlepage{F} % include headline on the contents page + \def\rheader{\mainfont\hfil \contentspagenumber} + \vfill + \centerline{\titlefont The {\ttitlefont OVF2OVP} processor} + \vskip 15pt + \centerline{(Version 1.11, February 2000)} + \vfill} +\def\botofcontents{\vfill + \centerline{\hsize 5in\baselineskip9pt + \vbox{\ninerm\noindent + The preparation of this program + was supported in part by the National Science + Foundation and by the System Development Foundation. `\TeX' is a + trademark of the American Mathematical Society.}}} +\pageno=\contentspagenumber \advance\pageno by 1 + +@* Introduction. +The \.{VFtoVP} utility program converts a virtual font (``\.{VF}'') file +and its associated \TeX\ font metric (``\.{TFM}'') +file into an equivalent virtual-property-list (``\.{VPL}'') file. It also +makes a thorough check of the given files, using algorithms that are +essentially the same as those used by +\.{DVI} device drivers and by \TeX. Thus if \TeX\ or a \.{DVI} driver +complains that a \.{TFM} or \.{VF} +file is ``bad,'' this program will pinpoint the source or sources of +badness. A \.{VPL} file output by this program can be edited with +a normal text editor, and the result can be converted back to \.{VF} and \.{TFM} +format using the companion program \.{VPtoVF}. + +\indent\.{VFtoVP} is an extended version of the program \.{TFtoPL}, which +is part of the standard \TeX ware library. +The idea of a virtual font was inspired by the work of David R. Fuchs +@^Fuchs, David Raymond@> +who designed a similar set of conventions in 1984 while developing a +device driver for ArborText, Inc. He wrote a somewhat similar program +called \.{AMFtoXPL}. + +The |banner| string defined here should be changed whenever \.{VFtoVP} +gets modified. + +@d banner=='This is OVF2OVP, Version 1.11' {printed when the program starts} + +@ This program is written entirely in standard \PASCAL, except that +it occasionally has lower case letters in strings that are output. +Such letters can be converted to upper case if necessary. The input is read +from |vf_file| and |tfm_file|; the output is written on |vpl_file|. +Error messages and +other remarks are written on the |output| file, which the user may +choose to assign to the terminal if the system permits it. +@^system dependencies@> + +The term |print| is used instead of |write| when this program writes on +the |output| file, so that all such output can be easily deflected. + +@d print(#)==write(#) +@d print_ln(#)==write_ln(#) + +@p program OVF2OVP(@!vf_file,@!tfm_file,@!vpl_file,@!output); +label @<Labels in the outer block@>@/ +const @<Constants in the outer block@>@/ +type @<Types in the outer block@>@/ +var @<Globals in the outer block@>@/ +procedure initialize; {this procedure gets things started properly} + var @!k:integer; {all-purpose index for initialization} + begin print_ln(banner);@/ + @<Set initial values@>@/ + end; + +@ If the program has to stop prematurely, it goes to the +`|final_end|'. + +@d final_end=9999 {label for the end of it all} + +@<Labels...@>=final_end; + +@ The following parameters can be changed at compile time to extend or +reduce \.{VFtoVP}'s capacity. + +@<Constants...@>= +@!tfm_size=2000000; {maximum length of |tfm| data, in bytes} +@!vf_size=600000; {maximum length of |vf| data, in bytes} +@!max_fonts=300; {maximum number of local fonts in the |vf| file} +@!lig_size=800000; {maximum length of |lig_kern| program, in words} +@!hash_size=130003; {preferably a prime number, a bit larger than the number + of character pairs in lig/kern steps} +@!hash_mult=16007; {another prime} +@!max_char=65535; {the largest character number in a font} +@!xmax_char=65536; {|max_char|+1} +@!xxmax_char=65537;{|max_char|+2} +@!xmax_label=80001;{must be greater than |max_lig_steps|} +@!mem_size=40008; {|max_char|*4+8} +@!name_length=50; {a file name shouldn't be longer than this} +@!max_stack=50; {maximum depth of \.{DVI} stack in character packets} + +@ Here are some macros for common programming idioms. + +@d incr(#) == #:=#+1 {increase a variable by unity} +@d decr(#) == #:=#-1 {decrease a variable by unity} +@d do_nothing == {empty statement} +@d exit=10 {go here to leave a procedure} +@d not_found=45 {go here when you've found nothing} +@d return==goto exit {terminate a procedure call} +@f return==nil + +@<Types...@>= +@!byte=0..255; {unsigned eight-bit quantity} + +@* Virtual fonts. The idea behind \.{VF} files is that a general +interface mechanism is needed to switch between the myriad font +layouts provided by different suppliers of typesetting equipment. +Without such a mechanism, people must go to great lengths writing +inscrutable macros whenever they want to use typesetting conventions +based on one font layout in connection with actual fonts that have +another layout. This puts an extra burden on the typesetting system, +interfering with the other things it needs to do (like kerning, +hyphenation, and ligature formation). + +These difficulties go away when we have a ``virtual font,'' +i.e., a font that exists in a logical sense but not a physical sense. +A typesetting system like \TeX\ can do its job without knowing where the +actual characters come from; a device driver can then do its job by +letting a \.{VF} file tell what actual characters correspond to the +characters \TeX\ imagined were present. The actual characters +can be shifted and/or magnified and/or combined with other characters +from many different fonts. A virtual font can even make use of characters +from virtual fonts, including itself. + +Virtual fonts also allow convenient character substitutions for proofreading +purposes, when fonts designed for one output device are unavailable on another. + +@ A \.{VF} file is organized as a stream of 8-bit bytes, using conventions +borrowed from \.{DVI} and \.{PK} files. Thus, a device driver that knows +about \.{DVI} and \.{PK} format will already +contain most of the mechanisms necessary to process \.{VF} files. +We shall assume that \.{DVI} format is understood; the conventions in the +\.{DVI} documentation (see, for example, {\sl \TeX: The Program}, part 31) +are adopted here to define \.{VF} format. + +A preamble +appears at the beginning, followed by a sequence of character definitions, +followed by a postamble. More precisely, the first byte of every \.{VF} file +must be the first byte of the following ``preamble command'': + +\yskip\hang|pre| 247 |i[1]| |k[1]| |x[k]| |cs[4]| |ds[4]|. +Here |i| is the identification byte of \.{VF}, currently 202. The string +|x| is merely a comment, usually indicating the source of the \.{VF} file. +Parameters |cs| and |ds| are respectively the check sum and the design size +of the virtual font; they should match the first two words in the header of +the \.{TFM} file, as described below. + +\yskip +After the |pre| command, the preamble continues with font definitions; +every font needed to specify ``actual'' characters in later +\\{set\_char} commands is defined here. The font definitions are +exactly the same in \.{VF} files as they are in \.{DVI} files, except +that the scaled size |s| is relative and the design size |d| is absolute: + +\yskip\hang|fnt_def1| 243 |k[1]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. +Define font |k|, where |0<=k<256|. + +\yskip\hang|@!fnt_def2| 244 |k[2]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. +Define font |k|, where |0<=k<65536|. + +\yskip\hang|@!fnt_def3| 245 |k[3]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. +Define font |k|, where |0<=k<@t$2^{24}$@>|. + +\yskip\hang|@!fnt_def4| 246 |k[4]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. +Define font |k|, where |@t$-2^{31}$@><=k<@t$2^{31}$@>|. + +\yskip\noindent +These font numbers |k| are ``local''; they have no relation to font numbers +defined in the \.{DVI} file that uses this virtual font. The dimension~|s|, +which represents the scaled size of the local font being defined, +is a |fix_word| relative to the design size of the virtual font. +Thus if the local font is to be used at the same size +as the design size of the virtual font itself, |s| will be the +integer value $2^{20}$. The value of |s| must be positive and less than +$2^{24}$ (thus less than 16 when considered as a |fix_word|). +The dimension~|d| is a |fix_word| in units of printer's points; hence it +is identical to the design size found in the corresponding \.{TFM} file. + +@d id_byte=202 + +@<Glob...@>= +@!vf_file:packed file of byte; + +@ The preamble is followed by zero or more character packets, where each +character packet begins with a byte that is $<243$. Character packets have +two formats, one long and one short: + +\yskip\hang|long_char| 242 |pl[4]| |cc[4]| |tfm[4]| |dvi[pl]|. This long form +specifies a virtual character in the general case. + +\yskip\hang|short_char0..short_char241| +|pl[1]| |cc[1]| |tfm[3]| |dvi[pl]|. This short form specifies a +virtual character in the common case +when |0<=pl<242| and |0<=cc<256| and $0\le|tfm|<2^{24}$. + +\yskip\noindent +Here |pl| denotes the packet length following the |tfm| value; |cc| is +the character code; and |tfm| is the character width copied from the +\.{TFM} file for this virtual font. There should be at most one character +packet having any given |cc| code. + +The |dvi| bytes are a sequence of complete \.{DVI} commands, properly +nested with respect to |push| and |pop|. All \.{DVI} operations are +permitted except |bop|, |eop|, and commands with opcodes |>=243|. +Font selection commands (|fnt_num0| through |fnt4|) must refer to fonts +defined in the preamble. + +Dimensions that appear in the \.{DVI} instructions are analogous to +|fix_word| quantities; i.e., they are integer multiples of $2^{-20}$ times +the design size of the virtual font. For example, if the virtual font +has design size $10\,$pt, the \.{DVI} command to move down $5\,$pt +would be a \\{down} instruction with parameter $2^{19}$. The virtual font +itself might be used at a different size, say $12\,$pt; then that +\\{down} instruction would move down $6\,$pt instead. Each dimension +must be less than $2^{24}$ in absolute value. + +Device drivers processing \.{VF} files treat the sequences of |dvi| bytes +as subroutines or macros, implicitly enclosing them with |push| and |pop|. +Each subroutine begins with |w=x=y=z=0|, and with current font~|f| the +number of the first-defined in the preamble (undefined if there's no +such font). After the |dvi| commands have been +performed, the |h| and~|v| position registers of \.{DVI} format and the +current font~|f| are restored to their former values; +then, if the subroutine has been invoked by a \\{set\_char} or \\{set} +command, |h|~is increased by the \.{TFM} width +(properly scaled)---just as if a simple character had been typeset. + +@d long_char=242 {\.{VF} command for general character packet} +@d set_char_0=0 {\.{DVI} command to typeset character 0 and move right} +@d set1=128 {typeset a character and move right} +@d set_rule=132 {typeset a rule and move right} +@d put1=133 {typeset a character} +@d put_rule=137 {typeset a rule} +@d nop=138 {no operation} +@d push=141 {save the current positions} +@d pop=142 {restore previous positions} +@d right1=143 {move right} +@d w0=147 {move right by |w|} +@d w1=148 {move right and set |w|} +@d x0=152 {move right by |x|} +@d x1=153 {move right and set |x|} +@d down1=157 {move down} +@d y0=161 {move down by |y|} +@d y1=162 {move down and set |y|} +@d z0=166 {move down by |z|} +@d z1=167 {move down and set |z|} +@d fnt_num_0=171 {set current font to 0} +@d fnt1=235 {set current font} +@d xxx1=239 {extension to \.{DVI} primitives} +@d xxx4=242 {potentially long extension to \.{DVI} primitives} +@d fnt_def1=243 {define the meaning of a font number} +@d pre=247 {preamble} +@d post=248 {postamble beginning} +@d improper_DVI_for_VF==139,140,243,244,245,246,247,248,249,250,251,252, + 253,254,255 + +@ The character packets are followed by a trivial postamble, consisting of +one or more bytes all equal to |post| (248). The total number of bytes +in the file should be a multiple of~4. + +@* Font metric data. +The idea behind \.{TFM} files is that typesetting routines like \TeX\ +need a compact way to store the relevant information about several +dozen fonts, and computer centers need a compact way to store the +relevant information about several hundred fonts. \.{TFM} files are +compact, and most of the information they contain is highly relevant, +so they provide a solution to the problem. + +The information in a \.{TFM} file appears in a sequence of 8-bit bytes. +Since the number of bytes is always a multiple of 4, we could +also regard the file as a sequence of 32-bit words; but \TeX\ uses the +byte interpretation, and so does \.{VFtoVP}. Note that the bytes +are considered to be unsigned numbers. + +@<Glob...@>= +@!tfm_file:packed file of byte; + +@ On some systems you may have to do something special to read a +packed file of bytes. For example, the following code didn't work +when it was first tried at Stanford, because packed files have to be +opened with a special switch setting on the \PASCAL\ that was used. +@^system dependencies@> + +@<Set init...@>= +reset(tfm_file); reset(vf_file); + +@ The first 24 bytes (6 words) of a \.{TFM} file contain twelve 16-bit +integers that give the lengths of the various subsequent portions +of the file. These twelve integers are, in order: +$$\vbox{\halign{\hfil#&$\null=\null$#\hfil\cr +|@!lf|&length of the entire file, in words;\cr +|@!lh|&length of the header data, in words;\cr +|@!bc|&smallest character code in the font;\cr +|@!ec|&largest character code in the font;\cr +|@!nw|&number of words in the width table;\cr +|@!nh|&number of words in the height table;\cr +|@!nd|&number of words in the depth table;\cr +|@!ni|&number of words in the italic correction table;\cr +|@!nl|&number of words in the lig/kern table;\cr +|@!nk|&number of words in the kern table;\cr +|@!ne|&number of words in the extensible character table;\cr +|@!np|&number of font parameter words.\cr}}$$ +They are all nonnegative and less than $2^{15}$. We must have |bc-1<=ec<=255|, +|ne<=256|, and +$$\hbox{|lf=6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+nk+ne+np|.}$$ +Note that a font may contain as many as 256 characters (if |bc=0| and |ec=255|), +and as few as 0 characters (if |bc=ec+1|). + +Incidentally, when two or more 8-bit bytes are combined to form an integer of +16 or more bits, the most significant bytes appear first in the file. +This is called BigEndian order. + +@<Glob...@>= +@!ofm_level, +@!nco,@!ncw,@!npc,@!nki,@!nwi,@!nkf,@!nwf,@!nkr,@!nwr,@!nkg,@!nwg,@!nkp,@!nwp, +@!nkm,@!nwm,@!real_lf, +@!lf,@!lh,@!bc,@!ec,@!nw,@!nh,@!nd,@!ni,@!nl,@!nk,@!ne,@!np,@!font_dir:integer; + {subfile sizes} +@!ofm_on:boolean; + +@ @<Set init...@>= +ofm_on:=false; ofm_level:=-1; lf:=0; lh:=0; +nco:=0; ncw:=0; npc:=0; bc:=0; ec:=0; nw:=0; nh:=0; nd:=0; ni:=0; +nl:=0; nk:=0; ne:=0; np:=0; +nki:=0; nwi:=0; nkf:=0; nwf:=0; +nkm:=0; nwm:=0; real_lf:=0; +nkr:=0; nwr:=0; nkg:=0; nwg:=0; +nkp:=0; nwp:=0; font_dir:=0; + +@ The rest of the \.{TFM} file may be regarded as a sequence of ten data +arrays having the informal specification +$$\def\arr$[#1]#2${\&{array} $[#1]$ \&{of} #2} +\vbox{\halign{\hfil\\{#}&$\,:\,$\arr#\hfil\cr +header&|[0..lh-1]stuff|\cr +char\_info&|[bc..ec]char_info_word|\cr +width&|[0..nw-1]fix_word|\cr +height&|[0..nh-1]fix_word|\cr +depth&|[0..nd-1]fix_word|\cr +italic&|[0..ni-1]fix_word|\cr +lig\_kern&|[0..nl-1]lig_kern_command|\cr +kern&|[0..nk-1]fix_word|\cr +exten&|[0..ne-1]extensible_recipe|\cr +param&|[1..np]fix_word|\cr}}$$ +The most important data type used here is a |@!fix_word|, which is +a 32-bit representation of a binary fraction. A |fix_word| is a signed +quantity, with the two's complement of the entire word used to represent +negation. Of the 32 bits in a |fix_word|, exactly 12 are to the left of the +binary point; thus, the largest |fix_word| value is $2048-2^{-20}$, and +the smallest is $-2048$. We will see below, however, that all but one of +the |fix_word| values will lie between $-16$ and $+16$. + +@ The first data array is a block of header information, which contains +general facts about the font. The header must contain at least two words, +and for \.{TFM} files to be used with Xerox printing software it must +contain at least 18 words, allocated as described below. When different +kinds of devices need to be interfaced, it may be necessary to add further +words to the header block. + +\yskip\hang|header[0]| is a 32-bit check sum that \TeX\ will copy into the +\.{DVI} output file whenever it uses the font. Later on when the \.{DVI} +file is printed, possibly on another computer, the actual font that gets +used is supposed to have a check sum that agrees with the one in the +\.{TFM} file used by \TeX. In this way, users will be warned about +potential incompatibilities. (However, if the check sum is zero in either +the font file or the \.{TFM} file, no check is made.) The actual relation +between this check sum and the rest of the \.{TFM} file is not important; +the check sum is simply an identification number with the property that +incompatible fonts almost always have distinct check sums. +@^check sum@> + +\yskip\hang|header[1]| is a |fix_word| containing the design size of the +font, in units of \TeX\ points (7227 \TeX\ points = 254 cm). This number +must be at least 1.0; it is fairly arbitrary, but usually the design size +is 10.0 for a ``10 point'' font, i.e., a font that was designed to look +best at a 10-point size, whatever that really means. When a \TeX\ user +asks for a font `\.{at} $\delta$ \.{pt}', the effect is to override the +design size and replace it by $\delta$, and to multiply the $x$ and~$y$ +coordinates of the points in the font image by a factor of $\delta$ +divided by the design size. {\sl All other dimensions in the\/\ \.{TFM} +file are |fix_word|\kern-1pt\ numbers in design-size units.} Thus, for example, +the value of |param[6]|, one \.{em} or \.{\\quad}, is often the |fix_word| +value $2^{20}=1.0$, since many fonts have a design size equal to one em. +The other dimensions must be less than 16 design-size units in absolute +value; thus, |header[1]| and |param[1]| are the only |fix_word| entries in +the whole \.{TFM} file whose first byte might be something besides 0 or +255. @^design size@> + +\yskip\hang|header[2..11]|, if present, contains 40 bytes that identify +the character coding scheme. The first byte, which must be between 0 and +39, is the number of subsequent ASCII bytes actually relevant in this +string, which is intended to specify what character-code-to-symbol +convention is present in the font. Examples are \.{ASCII} for standard +ASCII, \.{TeX text} for fonts like \.{cmr10} and \.{cmti9}, \.{TeX math +extension} for \.{cmex10}, \.{XEROX text} for Xerox fonts, \.{GRAPHIC} for +special-purpose non-alphabetic fonts, \.{UNSPECIFIED} for the default case +when there is no information. Parentheses should not appear in this name. +(Such a string is said to be in {\mc BCPL} format.) +@^coding scheme@> + +\yskip\hang|header[12..16]|, if present, contains 20 bytes that name the +font family (e.g., \.{CMR} or \.{HELVETICA}), in {\mc BCPL} format. +This field is also known as the ``font identifier.'' +@^family name@> +@^font identifier@> + +\yskip\hang|header[17]|, if present, contains a first byte called the +|seven_bit_safe_flag|, then two bytes that are ignored, and a fourth byte +called the |face|. If the value of the fourth byte is less than 18, it has +the following interpretation as a ``weight, slope, and expansion'': Add 0 +or 2 or 4 (for medium or bold or light) to 0 or 1 (for roman or italic) to +0 or 6 or 12 (for regular or condensed or extended). For example, 13 is +0+1+12, so it represents medium italic extended. A three-letter code +(e.g., \.{MIE}) can be used for such |face| data. + +\yskip\hang|header[18..@twhatever@>]| might also be present; the individual +words are simply called |header[18]|, |header[19]|, etc., at the moment. + +@ Next comes the |char_info| array, which contains one |char_info_word| +per character. Each |char_info_word| contains six fields packed into +four bytes as follows. + +\yskip\hang first byte: |width_index| (8 bits)\par +\hang second byte: |height_index| (4 bits) times 16, plus |depth_index| + (4~bits)\par +\hang third byte: |italic_index| (6 bits) times 4, plus |tag| + (2~bits)\par +\hang fourth byte: |remainder| (8 bits)\par +\yskip\noindent +The actual width of a character is |width[width_index]|, in design-size +units; this is a device for compressing information, since many characters +have the same width. Since it is quite common for many characters +to have the same height, depth, or italic correction, the \.{TFM} format +imposes a limit of 16 different heights, 16 different depths, and +64 different italic corrections. + +Incidentally, the relation |width[0]=height[0]=depth[0]=italic[0]=0| +should always hold, so that an index of zero implies a value of zero. +The |width_index| should never be zero unless the character does +not exist in the font, since a character is valid if and only if it lies +between |bc| and |ec| and has a nonzero |width_index|. + +@ The |tag| field in a |char_info_word| has four values that explain how to +interpret the |remainder| field. + +\yskip\hang|tag=0| (|no_tag|) means that |remainder| is unused.\par +\hang|tag=1| (|lig_tag|) means that this character has a ligature/kerning +program starting at |lig_kern[remainder]|.\par +\hang|tag=2| (|list_tag|) means that this character is part of a chain of +characters of ascending sizes, and not the largest in the chain. The +|remainder| field gives the character code of the next larger character.\par +\hang|tag=3| (|ext_tag|) means that this character code represents an +extensible character, i.e., a character that is built up of smaller pieces +so that it can be made arbitrarily large. The pieces are specified in +|exten[remainder]|.\par + +@d no_tag=0 {vanilla character} +@d lig_tag=1 {character has a ligature/kerning program} +@d list_tag=2 {character has a successor in a charlist} +@d ext_tag=3 {character is extensible} + +@ The |lig_kern| array contains instructions in a simple programming language +that explains what to do for special letter pairs. Each word is a +|lig_kern_command| of four bytes. + +\yskip\hang first byte: |skip_byte|, indicates that this is the final program + step if the byte is 128 or more, otherwise the next step is obtained by + skipping this number of intervening steps.\par +\hang second byte: |next_char|, ``if |next_char| follows the current character, + then perform the operation and stop, otherwise continue.''\par +\hang third byte: |op_byte|, indicates a ligature step if less than~128, + a kern step otherwise.\par +\hang fourth byte: |remainder|.\par +\yskip\noindent +In a kern step, an +additional space equal to |kern[256*(op_byte-128)+remainder]| is inserted +between the current character and |next_char|. This amount is +often negative, so that the characters are brought closer together +by kerning; but it might be positive. + +There are eight kinds of ligature steps, having |op_byte| codes $4a+2b+c$ where +$0\le a\le b+c$ and $0\le b,c\le1$. The character whose code is +|remainder| is inserted between the current character and |next_char|; +then the current character is deleted if $b=0$, and |next_char| is +deleted if $c=0$; then we pass over $a$~characters to reach the next +current character (which may have a ligature/kerning program of its own). + +Notice that if $a=0$ and $b=1$, the current character is unchanged; if +$a=b$ and $c=1$, the current character is changed but the next character is +unchanged. \.{VFtoVP} will check to see that infinite loops are avoided. + +If the very first instruction of the |lig_kern| array has |skip_byte=255|, +the |next_char| byte is the so-called right boundary character of this font; +the value of |next_char| need not lie between |bc| and~|ec|. +If the very last instruction of the |lig_kern| array has |skip_byte=255|, +there is a special ligature/kerning program for a left boundary character, +beginning at location |256*op_byte+remainder|. +The interpretation is that \TeX\ puts implicit boundary characters +before and after each consecutive string of characters from the same font. +These implicit characters do not appear in the output, but they can affect +ligatures and kerning. + +If the very first instruction of a character's |lig_kern| program has +|skip_byte>128|, the program actually begins in location +|256*op_byte+remainder|. This feature allows access to large |lig_kern| +arrays, because the first instruction must otherwise +appear in a location |<=255|. + +Any instruction with |skip_byte>128| in the |lig_kern| array must have +|256*op_byte+remainder<nl|. If such an instruction is encountered during +normal program execution, it denotes an unconditional halt; no ligature +command is performed. + +@d stop_flag=128 {value indicating `\.{STOP}' in a lig/kern program} +@d kern_flag=128 {op code for a kern step} + +@ Extensible characters are specified by an |extensible_recipe|, +which consists of four bytes called |top|, |mid|, +|bot|, and |rep| (in this order). These bytes are the character codes +of individual pieces used to build up a large symbol. +If |top|, |mid|, or |bot| are zero, +they are not present in the built-up result. For example, an extensible +vertical line is like an extensible bracket, except that the top and +bottom pieces are missing. + + +@ The final portion of a \.{TFM} file is the |param| array, which is another +sequence of |fix_word| values. + +\yskip\hang|param[1]=@!slant| is the amount of italic slant, which is used +to help position accents. For example, |slant=.25| means that when you go +up one unit, you also go .25 units to the right. The |slant| is a pure +number; it's the only |fix_word| other than the design size itself that is +not scaled by the design size. + +\hang|param[2]=space| is the normal spacing between words in text. +Note that character |" "| in the font need not have anything to do with +blank spaces. + +\hang|param[3]=space_stretch| is the amount of glue stretching between words. + +\hang|param[4]=space_shrink| is the amount of glue shrinking between words. + +\hang|param[5]=x_height| is the height of letters for which accents don't +have to be raised or lowered. + +\hang|param[6]=quad| is the size of one em in the font. + +\hang|param[7]=extra_space| is the amount added to |param[2]| at the +ends of sentences. + +When the character coding scheme is \.{TeX math symbols}, the font is +supposed to have 15 additional parameters called |num1|, |num2|, |num3|, +|denom1|, |denom2|, |sup1|, |sup2|, |sup3|, |sub1|, |sub2|, |supdrop|, +|subdrop|, |delim1|, |delim2|, and |axis_height|, respectively. When the +character coding scheme is \.{TeX math extension}, the font is supposed to +have six additional parameters called |default_rule_thickness| and +|big_op_spacing1| through |big_op_spacing5|. + +@ So that is what \.{TFM} files hold. The next question is, ``What about +\.{VPL} files?'' A complete answer to that question appears in the +documentation of the companion program, \.{VPtoVF}, so it will not +be repeated here. Suffice it to say that a \.{VPL} file is an ordinary +\PASCAL\ text file, and that the output of \.{VFtoVP} uses only a +subset of the possible constructions that might appear in a \.{VPL} file. +Furthermore, hardly anybody really wants to look at the formal +definition of \.{VPL} format, because it is almost self-explanatory when +you see an example or two. + +@<Glob...@>= +@!vpl_file:text; + +@ @<Set init...@>= +rewrite(vpl_file); + +@* Unpacking the TFM file. +The first thing \.{VFtoVP} does is read the entire |tfm_file| into an array of +bytes, |tfm[0..(4*lf-1)]|. + +@<Types...@>= +@!index=0..tfm_size; {address of a byte in |tfm|} +@!char_type=0..65535; +@!xchar_type=0..65536; +@!xxchar_type=0..65537; +@!xxxchar_type=0..65538; + +@ +@<Glob...@>= +@!tfm:array [-1000..tfm_size] of byte; {the input data all goes here} + {the negative addresses avoid range checks for invalid characters} +@!top_char,@!top_width,@!top_height,@!top_depth,@!top_italic:integer; +@!start_ptr,@!check_sum,@!design_size,@!scheme,@!family,@!random_word:integer; +@!header_length,@!char_ptr,@!copies,@!j:integer; + +@ The input may, of course, be all screwed up and not a \.{TFM} file +at all. So we begin cautiously. + +@d abort(#)==begin print_ln(#); + print_ln('Sorry, but I can''t go on; are you sure this is a OFM?'); + goto final_end; + end + +@<Read the whole font metric file@>= +read(tfm_file,tfm[0]); +if tfm[0]>127 then abort('The first byte of the input file exceeds 127!'); +@.The first byte...@> +if eof(tfm_file) then abort('The input file is only one byte long!'); +@.The input...one byte long@> +read(tfm_file,tfm[1]); lf:=tfm[0]*@'400+tfm[1]; +if lf=0 then begin + for i:=2 to 7 do begin + if eof(tfm_file) + then abort('The input file is too short to designate its length!'); + read(tfm_file, tfm[i]); + end; + ofm_on := true; ofm_level := tfm[2]*@"100+tfm[3]; + if tfm[4]>127 then abort('The fifth byte of the input file exceeds 127!'); + lf := tfm[4]*@"1000000 + tfm[5]*@"10000 + tfm[6]*@"100 + tfm[7]; + end +else begin + ofm_on := false; + end; +case ofm_level of + -1: begin start_ptr:=2; check_sum:=24; end; + 0: begin start_ptr:=8; check_sum:=56; end; + 1: begin start_ptr:=8; check_sum:=116; end; + end; +design_size:=check_sum+4; +scheme:=design_size+4; +family:=scheme+40; +random_word:=family+20; +if lf=0 then + abort('The file claims to have length zero, but that''s impossible!'); +@.The file claims...@> +if 4*lf-1>tfm_size then abort('The file is bigger than I can handle!'); +@.The file is bigger...@> +for tfm_ptr:=start_ptr to 4*lf-1 do begin + if eof(tfm_file) then + abort('The file has fewer bytes than it claims!'); +@.The file has fewer bytes...@> + read(tfm_file,tfm[tfm_ptr]); + end; +if not eof(tfm_file) then begin + print_ln('There''s some extra junk at the end of the OFM file,'); +@.There's some extra junk...@> + print_ln('but I''ll proceed as if it weren''t there.'); + end + +@ After the file has been read successfully, we look at the subfile sizes +to see if they check out. + +@d eval_two_bytes(#)==begin if tfm[tfm_ptr]>127 then + abort('One of the subfile sizes is negative!'); +@.One of the subfile sizes...@> + #:=tfm[tfm_ptr]*@'400+tfm[tfm_ptr+1]; + tfm_ptr:=tfm_ptr+2; + end +@d eval_four_bytes(#)==begin if tfm[tfm_ptr]>127 then + abort('One of the subfile sizes is negative!'); +@.One of the subfile sizes...@> + #:=tfm[tfm_ptr]*@"1000000+tfm[tfm_ptr+1]*@"10000+ + tfm[tfm_ptr+2]*@"100+tfm[tfm_ptr+3]; + tfm_ptr:=tfm_ptr+4; + end + +@<Set subfile sizes |lh|, |bc|, \dots, |np|@>= +begin +if not ofm_on then begin + tfm_ptr:=2; + eval_two_bytes(lh); + eval_two_bytes(bc); + eval_two_bytes(ec); + eval_two_bytes(nw); + eval_two_bytes(nh); + eval_two_bytes(nd); + eval_two_bytes(ni); + eval_two_bytes(nl); + eval_two_bytes(nk); + eval_two_bytes(ne); + eval_two_bytes(np); + ncw:=(ec-bc+1); + header_length:=6; + top_char:=255; + top_width:=255; + top_height:=15; + top_depth:=15; + top_italic:=63; + end +else begin + tfm_ptr:=8; + eval_four_bytes(lh); + eval_four_bytes(bc); + eval_four_bytes(ec); + eval_four_bytes(nw); + eval_four_bytes(nh); + eval_four_bytes(nd); + eval_four_bytes(ni); + eval_four_bytes(nl); + eval_four_bytes(nk); + eval_four_bytes(ne); + eval_four_bytes(np); + eval_four_bytes(font_dir); + top_char:=65535; + top_width:=65535; + top_height:=255; + top_depth:=255; + top_italic:=255; + if ofm_level=0 then begin + header_length:=14; + ncw:=2*(ec-bc+1); + end + else begin + header_length:=29; + eval_four_bytes(nco); + eval_four_bytes(ncw); + eval_four_bytes(npc); + eval_four_bytes(nki); {Kinds of font ivalues} + eval_four_bytes(nwi); {Words of font ivalues} + eval_four_bytes(nkf); {Kinds of font fvalues} + eval_four_bytes(nwf); {Words of font fvalues} + eval_four_bytes(nkm); {Kinds of font mvalues} + eval_four_bytes(nwm); {Words of font mvalues} + eval_four_bytes(nkr); {Kinds of font rules} + eval_four_bytes(nwr); {Words of font rules} + eval_four_bytes(nkg); {Kinds of font glues} + eval_four_bytes(nwg); {Words of font glues} + eval_four_bytes(nkp); {Kinds of font penalties} + eval_four_bytes(nwp); {Words of font penalties} + end; + end; +if lf<>(header_length+lh+ncw+nw+nh+nd+ni+2*nl+nk+2*ne+np+ + nki+nwi+nkf+nwf+nkr+nwr+nkg+nwg+nkp+nwp) then + abort('Subfile sizes don''t add up to the stated total!'); +@.Subfile sizes don't add up...@> +if lh<2 then abort('The header length is only ',lh:1,'!'); +@.The header length...@> +if (2*nl)>(4*lig_size) then + abort('The lig/kern program is longer than I can handle!'); +@.The lig/kern program...@> +if (bc>ec+1)or(ec>top_char) then abort('The character code range ', +@.The character code range...@> + bc:1,'..',ec:1,'is illegal!'); +if ec>max_char then + abort('Character ',ec:1,'is too large. Ask a wizard to enlarge me.'); +if (nw=0)or(nh=0)or(nd=0)or(ni=0) then + abort('Incomplete subfiles for character dimensions!'); +@.Incomplete subfiles...@> +if ne>(top_char+1) then abort('There are ',ne:1,' extensible recipes!'); +@.There are ... recipes@> +end + +@ Once the input data successfully passes these basic checks, +\.{TFtoPL} believes that it is a \.{TFM} file, and the conversion +to \.{PL} format will take place. Access to the various subfiles +is facilitated by computing the following base addresses. For example, +the |char_info| for character |c| in a \.{TFM} file will start in location +|4*(char_base+c)| of the |tfm| array. + +@<Globals...@>= +@!ivalues_start,@!fvalues_start,@!mvalues_start, +@!rules_start,@!glues_start,@!penalties_start: +integer; +@!ivalues_base,@!fvalues_base,@!mvalues_base, +@!rules_base,@!glues_base,@!penalties_base: +integer; +@!char_base,@!width_base,@!height_base,@!depth_base,@!italic_base: integer; +@!lig_kern_base,@!kern_base,@!exten_base,@!param_base:integer; + {base addresses for the subfiles} +@!char_start:array [0..max_char] of integer; +@!bytes_per_entry:integer; + +@ @<Compute the base addresses@>= +begin +ivalues_start:=header_length+lh; +fvalues_start:=ivalues_start+nki; +mvalues_start:=fvalues_start+nkf; +rules_start:=mvalues_start+nkm; +glues_start:=rules_start+nkr; +penalties_start:=glues_start+nkg; +ivalues_base:=penalties_start+nkp; +fvalues_base:=ivalues_base+nwi; +mvalues_base:=fvalues_base+nwi; +rules_base:=mvalues_base+nwm; +glues_base:=rules_base+nwr; +penalties_base:=glues_base+nwg; +char_base:=penalties_base+nwp; +bytes_per_entry:=(12 + 2*npc) div 4 * 4; +if not ofm_on then begin + for i:=bc to ec do begin + char_start[i]:=4*char_base+4*(i-bc); + end; + end +else if ofm_level=0 then begin + for i:=bc to ec do begin + char_start[i]:=4*char_base+8*(i-bc); + end; + end +else begin + char_ptr:=4*char_base; + i:=bc; + while i<=ec do begin + copies:=1+256*tfm[char_ptr+8]+tfm[char_ptr+9]; + for j:=1 to copies do begin + char_start[i]:=char_ptr; + i:=i+1; + end; + char_ptr:=char_ptr + bytes_per_entry; + end; + if char_ptr<>(4*(char_base+ncw)) then + abort('Length of char info table does not correspond to specification'); + end; +width_base:=char_base+ncw; +height_base:=width_base+nw; +depth_base:=height_base+nh; +italic_base:=depth_base+nd; +lig_kern_base:=italic_base+ni; +kern_base:=lig_kern_base+2*nl; +exten_base:=kern_base+nk; +param_base:=exten_base+2*ne-1; +end + +@ Of course we want to define macros that suppress the detail of how the +font information is actually encoded. Each word will be referred to by +the |tfm| index of its first byte. For example, if |c| is a character +code between |bc| and |ec|, then |tfm[char_info(c)]| will be the +first byte of its |char_info|, i.e., the |width_index|; furthermore +|width(c)| will point to the |fix_word| for |c|'s width. + +@d char_info(#)==char_start[#] +@d nonexistent(#)==((#<bc)or(#>ec)or(width_index(#)=0)) +@d width(#)==4*(width_base+width_index(#)) +@d height(#)==4*(height_base+height_index(#)) +@d depth(#)==4*(depth_base+depth_index(#)) +@d italic(#)==4*(italic_base+italic_index(#)) +@d kern(#)==4*(kern_base+#) {here \#\ is an index, not a character} +@d param(#)==4*(param_base+#) {likewise} + +@p function width_index(c:char_type):integer; +begin if not ofm_on then + width_index:=tfm[char_info(c)] +else + width_index:=256*tfm[char_info(c)]+tfm[char_info(c)+1]; +end; + +function height_index(c:char_type):integer; +begin if not ofm_on then + height_index:=tfm[char_info(c)+1] div 16 +else + height_index:=tfm[char_info(c)+2]; +end; + +function depth_index(c:char_type):integer; +begin if not ofm_on then + depth_index:=tfm[char_info(c)+1] mod 16 +else + depth_index:=tfm[char_info(c)+3]; +end; + +function italic_index(c:char_type):integer; +begin if not ofm_on then + italic_index:=tfm[char_info(c)+2] div 4 +else if ofm_level=0 then + italic_index:=tfm[char_info(c)+4]*64 + tfm[char_info(c)+5] div 4 +else + italic_index:=tfm[char_info(c)+4]; +end; + +function tag(c:char_type):integer; +begin if not ofm_on then + tag:=tfm[char_info(c)+2] mod 4 +else + tag:=tfm[char_info(c)+5] mod 4; +end; + +procedure set_no_tag(c:char_type); +begin if not ofm_on then + tfm[char_info(c)+2] := (tfm[char_info(c)+2] div 64)*64 + no_tag +else + tfm[char_info(c)+5] := (tfm[char_info(c)+5] div 64)*64 + no_tag; +end; + +function ctag(c:char_type):boolean; +begin if not (ofm_level=1) then + ctag:=false +else + ctag:=tfm[char_info(c)+5] div 4 mod 2; +end; + +procedure set_no_ctag(c:char_type); +begin if not (ofm_level=1) then + tfm[char_info(c)+5] := + tfm[char_info(c)+5] div 8 * 8 + tfm[char_info(c)+5] mod 4; +end; + +function no_repeats(c:char_type):integer; +begin if ofm_level<=0 then + no_repeats:=0 +else + no_repeats:=256*tfm[char_info(c)+8]+tfm[char_info(c)+9]; +end; + +function char_param(c:char_type; i:integer):integer; +begin + char_param:=256*tfm[char_info(c)+2*i+10]+tfm[char_info(c)+2*i+11]; +end; + +function rremainder(c:char_type):integer; +begin if not ofm_on then + rremainder:=tfm[char_info(c)+3] +else + rremainder:=256*tfm[char_info(c)+6]+tfm[char_info(c)+7]; +end; + +function lig_step(c:char_type):integer; +begin if not ofm_on then + lig_step:=4*(lig_kern_base+c) +else + lig_step:=4*(lig_kern_base+2*c); +end; + +function exten(c:char_type):integer; +begin if not ofm_on then + exten:=4*(exten_base+rremainder(c)) +else + exten:=4*(exten_base+2*rremainder(c)); +end; + +function l_skip_byte(c:integer):integer; +begin if not ofm_on then + l_skip_byte:=tfm[c] +else + l_skip_byte:=256*tfm[c]+tfm[c+1]; +end; + +procedure set_l_skip_byte(c:integer; newc:integer); +begin if not ofm_on then + tfm[c]:=newc +else begin + tfm[c]:=newc div 256; + tfm[c+1]:=newc mod 256 + end +end; + +function l_next_char(c:integer):integer; +begin if not ofm_on then + l_next_char:=tfm[c+1] +else + l_next_char:=256*tfm[c+2]+tfm[c+3]; +end; + +procedure set_l_next_char(c:integer; newc:char_type); +begin if not ofm_on then + tfm[c+1]:=newc +else begin + tfm[c+2]:=newc div 256; + tfm[c+3]:=newc mod 256 + end +end; + +function l_op_byte(c:integer):integer; +begin if not ofm_on then + l_op_byte:=tfm[c+2] +else + l_op_byte:=256*tfm[c+4]+tfm[c+5]; +end; + +procedure set_l_op_byte(c:integer; newc:integer); +begin if not ofm_on then + tfm[c+2]:=newc +else begin + tfm[c+2]:=newc div 256; + tfm[c+3]:=newc mod 256 + end +end; + +function l_remainder(c:integer):integer; +begin if not ofm_on then + l_remainder:=tfm[c+3] +else + l_remainder:=256*tfm[c+6]+tfm[c+7]; +end; + +procedure set_l_remainder(c:integer; newc:char_type); +begin if not ofm_on then + tfm[c+3]:=newc +else begin + tfm[c+6]:=newc div 256; + tfm[c+7]:=newc mod 256 + end +end; + +@ One of the things we would like to do is take cognizance of fonts whose +character coding scheme is \.{TeX math symbols} or \.{TeX math extension}; +we will set the |font_type| variable to one of the three choices +|vanilla|, |mathsy|, or |mathex|. + +@d vanilla=0 {not a special scheme} +@d mathsy=1 {\.{TeX math symbols} scheme} +@d mathex=2 {\.{TeX math extension} scheme} + +@<Glob...@>= +@!font_type:vanilla..mathex; {is this font special?} + +@* Unpacking the VF file. +Once the \.{TFM} file has been brought into memory, \.{VFtoVP} completes +the input phase by reading the \.{VF} information into another array of bytes. +In this case we don't store all the data; we check the redundant bytes +for consistency with their \.{TFM} counterparts, and we partially decode +the packets. + +@<Glob...@>= +@!vf:array[0..vf_size] of byte; {the \.{VF} input data goes here} +@!font_number:array[0..max_fonts] of integer; {local font numbers} +@!font_start,@!font_chars:array[0..max_fonts] of 0..vf_size; {font info} +@!font_ptr:0..max_fonts; {number of local fonts} +@!packet_start,@!packet_end:array[char_type] of 0..vf_size; + {character packet boundaries} +@!packet_found:boolean; {at least one packet has appeared} +@!temp_byte:byte;@+@!reg_count:integer; {registers for simple calculations} +@!real_dsize:real; {the design size, converted to floating point} +@!pl:integer; {packet length} +@!vf_ptr:0..vf_size; {first unused location in |vf|} +@!vf_count:integer; {number of bytes read from |vf_file|} + +@ Again we cautiously verify that we've been given decent data. + +@d read_vf(#)==read(vf_file,#) +@d vf_abort(#)==begin + print_ln(#); + print_ln('Sorry, but I can''t go on; are you sure this is a OVF?'); + goto final_end; + end + +@<Read the whole \.{VF} file@>= +read_vf(temp_byte); +if temp_byte<>pre then vf_abort('The first byte isn''t `pre''!'); +@.The first byte...@> +@<Read the preamble command@>; +@<Read and store the font definitions and character packets@>; +@<Read and verify the postamble@> + +@ @d vf_store(#)==@t@>@;@/ + if vf_ptr+#>=vf_size then vf_abort('The file is bigger than I can handle!'); +@.The file is bigger...@> + for k:=vf_ptr to vf_ptr+#-1 do + begin if eof(vf_file) then vf_abort('The file ended prematurely!'); +@.The file ended prematurely@> + read_vf(vf[k]); + end; + vf_count:=vf_count+#; vf_ptr:=vf_ptr+# + +@<Read the preamble command@>= +if eof(vf_file) then vf_abort('The input file is only one byte long!'); +@.The input...one byte long@> +read_vf(temp_byte); +if temp_byte<>id_byte then vf_abort('Wrong OVF version number in second byte!'); +@.Wrong OVF version...@> +if eof(vf_file) then vf_abort('The input file is only two bytes long!'); +read_vf(temp_byte); {read the length of introductory comment} +vf_count:=11; vf_ptr:=0; vf_store(temp_byte); +for k:=0 to vf_ptr-1 do print(xchr[vf[k]]); +print_ln(' '); reg_count:=0; +for k:=0 to 7 do + begin if eof(vf_file) then vf_abort('The file ended prematurely!'); +@.The file ended prematurely@> + read_vf(temp_byte); + if temp_byte=tfm[check_sum+k] then incr(reg_count); + end; +real_dsize:=(((tfm[design_size]*256+tfm[design_size+1])*256+tfm[design_size+2]) + *256+tfm[design_size+3])/@'4000000; +if reg_count<>8 then + begin print_ln('Check sum and/or design size mismatch.'); +@.Check sum...mismatch@> + print_ln('Data from OFM file will be assumed correct.'); + end + +@ @<Read and store the font definitions and character packets@>= +for k:=0 to 65535 do packet_start[k]:=vf_size; +font_ptr:=0; packet_found:=false; font_start[0]:=vf_ptr; +repeat if eof(vf_file) then + begin print_ln('File ended without a postamble!'); temp_byte:=post; +@.File ended without a postamble@> + end +else begin read_vf(temp_byte); incr(vf_count); + if temp_byte<>post then + if temp_byte>long_char then @<Read and store a font definition@> + else @<Read and store a character packet@>; + end; +until temp_byte=post + +@ @<Read and verify the postamble@>= +while (temp_byte=post)and not eof(vf_file) do + begin read_vf(temp_byte); incr(vf_count); + end; +if not eof(vf_file) then + begin print_ln('There''s some extra junk at the end of the OVF file.'); +@.There's some extra junk...@> + print_ln('I''ll proceed as if it weren''t there.'); + end; +if vf_count mod 4 <> 0 then + print_ln('VF data not a multiple of 4 bytes') +@.VF data not a multiple of 4 bytes@> + +@ @<Read and store a font definition@>= +begin if packet_found or(temp_byte>=pre) then + vf_abort('Illegal byte ',temp_byte:1,' at beginning of character packet!'); +@.Illegal byte...@> +font_number[font_ptr]:=vf_read(temp_byte-fnt_def1+1); +if font_ptr=max_fonts then vf_abort('I can''t handle that many fonts!'); +@.I can't handle that many fonts@> +vf_store(14); {|c[4]| |s[4]| |d[4]| |a[1]| |l[1]|} +if vf[vf_ptr-10]>0 then {|s| is negative or exceeds $2^{24}-1$} + vf_abort('Mapped font size is too big!'); +@.Mapped font size...big@> +a:=vf[vf_ptr-2]; l:=vf[vf_ptr-1]; vf_store(a+l); {|n[a+l]|} +@<Print the name of the local font@>; +@<Read the local font's \.{TFM} file and record the characters it contains@>; +incr(font_ptr); font_start[font_ptr]:=vf_ptr; +end + +@ The font area may need to be separated from the font name on some systems. +Here we simply reproduce the font area and font name (with no space +or punctuation between them). +@^system dependencies@> + +@<Print the name...@>= +print('MAPFONT ',font_ptr:1,': '); +for k:=font_start[font_ptr]+14 to vf_ptr-1 do print(xchr[vf[k]]); +k:=font_start[font_ptr]+5; +print_ln(' at ',(((vf[k]*256+vf[k+1])*256+vf[k+2])/@'4000000)*real_dsize:2:2, + 'pt') + +@ Now we must read in another \.{TFM} file. But this time we needn't be so +careful, because we merely want to discover which characters are present. +The next few sections of the program are copied pretty much verbatim from +\.{DVItype}, so that system-dependent modifications can be copied from existing +software. + +It turns out to be convenient to read four bytes at a time, when we are +inputting from the local \.{TFM} files. The input goes into global variables +|b0|, |b1|, |b2|, and |b3|, with |b0| getting the first byte and |b3| +the fourth. + +@<Glob...@>= +@!a:integer; {length of the area/directory spec} +@!l:integer; {length of the font name proper} +@!cur_name:packed array[1..name_length] of char; {external name, + with no lower case letters} +@!b0,@!b1,@!b2,@!b3: byte; {four bytes input at once} +@!font_lh:integer; {header length of current local font} +@!font_bc,@!font_ec:integer; {character range of current local font} +@!font_ofm_level,@!font_extra_words:integer; +@!font_kprime,@!font_j:integer; + +@ The |read_tfm_word| procedure sets |b0| through |b3| to the next +four bytes in the current \.{TFM} file. +@^system dependencies@> + +@d read_tfm(#)==if eof(tfm_file) then #:=0@+else read(tfm_file,#) + +@p procedure read_tfm_word; +begin read_tfm(b0); read_tfm(b1); +read_tfm(b2); read_tfm(b3); +end; + +@ We use the |vf| array to store a list of all valid characters in the +local font, beginning at location |font_chars[f]|. + +@<Read the local font's \.{TFM} file...@>= +font_chars[font_ptr]:=vf_ptr; +@<Move font name into the |cur_name| string@>; +reset(tfm_file,cur_name); +@^system dependencies@> +if eof(tfm_file) then + print_ln('---not loaded, font metric file can''t be opened!') +@.font metric file can\'t be opened@> +else begin font_bc:=0; font_ec:=65536; {will cause error if not modified soon} + @<Read past the header@>; + if font_bc<=font_ec then + if font_ec>65535 then print_ln('---not loaded, bad font metric file!') +@.bad font metric file@> + else @<Read |char_info| entries@>; + if eof(tfm_file) then + print_ln('---trouble is brewing, font metric file ended too soon!'); +@.trouble is brewing...@> + end; +incr(vf_ptr) {leave space for character search later} + +@ @<Read past the header@>= +read_tfm_word; +if (b0*256+b1)<>0 then begin {TFM file} + font_ofm_level:=-1; + font_lh:=b2*256+b3; + read_tfm_word; font_bc:=b0*256+b1; font_ec:=b2*256+b3; + if font_ec<font_bc then font_bc:=font_ec+1; + read_tfm_word; + for k:=1 to 3+font_lh do begin + read_tfm_word; + if k=4 then @<Check the check sum@>; + if k=5 then @<Check the design size@>; + end; + end +else begin + font_ofm_level:=b2*256+b3; + read_tfm_word; font_lh:=((b0*256+b1)*256+b2)*256+b3; + read_tfm_word; font_bc:=((b0*256+b1)*256+b2)*256+b3; + read_tfm_word; font_ec:=((b0*256+b1)*256+b2)*256+b3; + if font_ec<font_bc then font_bc:=font_ec+1; + read_tfm_word; + for k:=1 to 8 do begin + read_tfm_word; + end; + if font_ofm_level=1 then begin + read_tfm_word; read_tfm_word; read_tfm_word; + font_extra_words:=(((b0*256+b1)*256+b2)*256+b3) div 2; + for k:=1 to 5 do begin + read_tfm_word; + end; + end; + for k:=1 to lh do begin + read_tfm_word; + if k=1 then @<Check the check sum@>; + if k=2 then @<Check the design size@>; + end; + end + +@ @<Read |char_info| entries@>= +case font_ofm_level of + -1: begin + for k:=font_bc to font_ec do begin + read_tfm_word; + if b0>0 then begin {character |k| exists in the font} + vf[vf_ptr]:=k; incr(vf_ptr); + if vf_ptr=vf_size then vf_abort('I''m out of OVF memory!'); + end; + end; + end; + 0: begin + for k:=font_bc to font_ec do + begin read_tfm_word; + if (b0*256+b1)>0 then {character |k| exists in the font} + begin vf[vf_ptr]:=k; incr(vf_ptr); + if vf_ptr=vf_size then vf_abort('I''m out of OVF memory!'); + end; + read_tfm_word; + end; + end; + 1: begin + k:=font_bc; + while k<font_ec do begin + read_tfm_word; + if (b0*256+b1)>0 then begin {character |k| exists in the font} + vf[vf_ptr]:=k; incr(vf_ptr); + if vf_ptr=vf_size then vf_abort('I''m out of OVF memory!'); + end; + read_tfm_word; read_tfm_word; + font_kprime:=k+(b0*256+b1); + for font_j:=k+1 to font_kprime do begin + vf[vf_ptr]:=font_j; incr(vf_ptr); + if vf_ptr=vf_size then vf_abort('I''m out of OVF memory!'); + end; + k:=font_kprime; + for font_j:=1 to font_extra_words do + read_tfm_word; + end + end; +end; + +@ @<Check the check sum@>= +if b0+b1+b2+b3>0 then + if(b0<>vf[font_start[font_ptr]])or@| + (b1<>vf[font_start[font_ptr]+1])or@| + (b2<>vf[font_start[font_ptr]+2])or@| + (b3<>vf[font_start[font_ptr]+3]) then begin + print_ln('Check sum in OVF file being replaced by font metric check sum'); +@.Check sum...replaced...@> + vf[font_start[font_ptr]]:=b0; + vf[font_start[font_ptr]+1]:=b1; + vf[font_start[font_ptr]+2]:=b2; + vf[font_start[font_ptr]+3]:=b3; + end + +@ @<Check the design size@>= +if(b0<>vf[font_start[font_ptr]+8])or@| + (b1<>vf[font_start[font_ptr]+9])or@| + (b2<>vf[font_start[font_ptr]+10])or@| + (b3<>vf[font_start[font_ptr]+11]) then + begin print_ln('Design size in OVF file being replaced by font metric design size'); +@.Design size...replaced...@> + vf[font_start[font_ptr]+8]:=b0; + vf[font_start[font_ptr]+9]:=b1; + vf[font_start[font_ptr]+10]:=b2; + vf[font_start[font_ptr]+11]:=b3; + end + +@ If no font directory has been specified, \.{DVI}-reading software +is supposed to use the default font directory, which is a +system-dependent place where the standard fonts are kept. +The string variable |default_directory| contains the name of this area. +@^system dependencies@> + +@d default_directory_name=='TeXfonts:' {change this to the correct name} +@d default_directory_name_length=9 {change this to the correct length} + +@<Glob...@>= +@!default_directory:packed array[1..default_directory_name_length] of char; + +@ @<Set init...@>= +default_directory:=default_directory_name; + +@ The string |cur_name| is supposed to be set to the external name of the +\.{TFM} file for the current font. This usually means that we need to +prepend the name of the default directory, and +to append the suffix `\.{.TFM}'. Furthermore, we change lower case letters +to upper case, since |cur_name| is a \PASCAL\ string. +@^system dependencies@> + +@<Move font name into the |cur_name| string@>= +for k:=1 to name_length do cur_name[k]:=' '; +if a=0 then begin + for k:=1 to default_directory_name_length do + cur_name[k]:=default_directory[k]; + r:=default_directory_name_length; + end +else r:=0; +for k:=font_start[font_ptr]+14 to vf_ptr-1 do begin + incr(r); + if r+4>name_length then vf_abort('Font name too long for me!'); +@.Font name too long for me@> + if (vf[k]>="a")and(vf[k]<="z") then + cur_name[r]:=xchr[vf[k]-@'40] + else cur_name[r]:=xchr[vf[k]]; + end; +cur_name[r+1]:='.'; cur_name[r+2]:='T'; cur_name[r+3]:='F'; cur_name[r+4]:='M' + + +@ It's convenient to have a subroutine +that reads a |k|-byte number from |vf_file|. + +@d get_vf(#)==if eof(vf_file) then #:=0 @+else read_vf(#) + +@p function vf_read(@!k:integer):integer; {actually |1<=k<=4|} +var @!b:byte; {input byte} +@!a:integer; {accumulator} +begin vf_count:=vf_count+k; get_vf(b); a:=b; +if k=4 then if b>=128 then a:=a-256; {4-byte numbers are signed} +while k>1 do begin + get_vf(b); + a:=256*a+b; decr(k); + end; +vf_read:=a; +end; + +@ The \.{VF} format supports arbitrary 4-byte character codes, +but \.{VPL} format presently does not. But \.{OVP} does. + +@<Read and store a character packet@>= +begin if temp_byte=long_char then + begin pl:=vf_read(4); c:=vf_read(4); reg_count:=vf_read(4); + {|pl[4]| |cc[4]| |tfm[4]|} + end +else begin pl:=temp_byte; c:=vf_read(1); reg_count:=vf_read(3); + {|pl[1]| |cc[1]| |tfm[3]|} + end; +if nonexistent(c) then vf_abort('Character ',c:1,' does not exist!'); +@.Character c does not exist@> +if packet_start[c]<vf_size then + print_ln('Discarding earlier packet for character ',c:1); +@.Discarding earlier packet...@> +if reg_count<>tfm_width(c) then + print_ln('Incorrect OFM width for character ',c:1,' in OVF file'); +@.Incorrect OFM width...@> +if pl<0 then vf_abort('Negative packet length!'); +@.Negative packet length@> +packet_start[c]:=vf_ptr; vf_store(pl); packet_end[c]:=vf_ptr-1; +packet_found:=true; +end + +@ The preceding code requires a simple subroutine that evaluates \.{TFM} data. + +@p function tfm_width(@!c:integer):integer; +var @!a:integer; {accumulator} +@!k:index; {index into |tfm|} +begin k:=width(c); {we assume that character |c| exists} +a:=tfm[k]; +if a>=128 then a:=a-256; +tfm_width:=((256*a+tfm[k+1])*256+tfm[k+2])*256+tfm[k+3]; +end; + +@* Basic output subroutines. +Let us now define some procedures that will reduce the rest of \.{VFtoVP}'s +work to a triviality. + +First of all, it is convenient to have an abbreviation for output to the +\.{VPL} file: + +@d out(#)==write(vpl_file,#) + +@ In order to stick to standard \PASCAL, we use an |xchr| array to do +appropriate conversion of ASCII codes. Three other little strings are +used to produce |face| codes like \.{MIE}. + +@<Glob...@>= +@!ASCII_04,@!ASCII_10,@!ASCII_14,HEX: packed array [1..32] of char; + {strings for output in the user's external character set} +@!xchr:packed array [0..255] of char; +@!MBL_string,@!RI_string,@!RCE_string:packed array [1..3] of char; + {handy string constants for |face| codes} + +@ @<Set init...@>= +ASCII_04:=' !"#$%&''()*+,-./0123456789:;<=>?';@/ +ASCII_10:='@@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_';@/ +ASCII_14:='`abcdefghijklmnopqrstuvwxyz{|}~?';@/ +HEX:='0123456789ABCDEF';@/ +for k:=0 to 255 do xchr[k]:='?'; +for k:=0 to @'37 do begin + xchr[k+@'40]:=ASCII_04[k+1]; + xchr[k+@'100]:=ASCII_10[k+1]; + xchr[k+@'140]:=ASCII_14[k+1]; + end; +MBL_string:='MBL'; RI_string:='RI '; RCE_string:='RCE'; + +@ The array |dig| will hold a sequence of digits to be output. + +@<Glob...@>= +@!dig:array[0..11] of integer; + +@ Here, in fact, are two procedures that output +|dig[j-1]|$\,\ldots\,$|dig[0]|, given $j>0$. + +@p procedure out_digs(j:integer); {outputs |j| digits} +begin repeat decr(j); out(HEX[1+dig[j]]); + until j=0; +end; +@# +procedure print_digs(j:integer); {prints |j| digits} +begin repeat decr(j); print(HEX[1+dig[j]]); + until j=0; +end; + +@ The |print_number| procedure indicates how |print_digs| can be used. +This procedure can print in octal, decimal or hex notation. + +@d print_hex(#)==print_number(#,16) +@d print_octal(#)==print_number(#,8) +@d print_decimal(#)==print_number(#,10) + +@p procedure print_number(c:integer; form:integer); {prints value of |c|} +var j:0..32; {index into |dig|} +begin +j:=0; +if (c<0) then begin + print_ln('Internal error: print_number (negative value)'); + c:=0; + end; +if form=8 then + print('''') {an apostrophe indicates the octal notation} +else if form=16 then + print('"') { a double apostrophe indicates the hexadecimal notation} +else if form<>10 then begin + print_ln('Internal error: print_number (form)'); + form:=16; + end; +while (c>0) or (j=0) do begin + dig[j]:=c mod form; c:=c div form; + j:=j+1; + end; +print_digs(j); +end; + +@ A \.{VPL} file has nested parentheses, and we want to format the output +so that its structure is clear. The |level| variable keeps track of the +depth of nesting. + +@<Glob...@>= +@!level:0..5; + +@ @<Set init...@>= +level:=0; + +@ Three simple procedures suffice to produce the desired structure in the +output. + +@p procedure out_ln; {finishes one line, indents the next} +var l:0..5; +begin write_ln(vpl_file); +for l:=1 to level do out(' '); +end; +@# +procedure left; {outputs a left parenthesis} +begin incr(level); out('('); +end; +@# +procedure right; {outputs a right parenthesis and finishes a line} +begin decr(level); out(')'); out_ln; +end; + +@ The value associated with a property can be output in a variety of +ways. For example, we might want to output a {\mc BCPL} string that +begins in |tfm[k]|: + +@p procedure out_BCPL(@!k:index); {outputs a string, preceded by a blank space} +var l:0..39; {the number of bytes remaining} +begin out(' '); l:=tfm[k]; +while l>0 do begin + incr(k); decr(l); out(xchr[tfm[k]]); + end; +end; + +@ The property value might also be a sequence of |l| bytes, beginning +in |tfm[k]|, that we would like to output in hex notation. +The following procedure assumes that |l<=4|, but larger values of |l| +could be handled easily by enlarging the |dig| array and increasing +the upper bounds on |b| and |j|. + +@d out_octal_number(#)==out_number(#,8) +@d out_decimal_number(#)==out_number(#,10) +@d out_hex_number(#)==out_number(#,16) +@d out_dec(#)==out_decimal_number(#) +@d out_hex_char(#)==out_hex_number(#) + +@p procedure out_number(c:integer; form:integer); {outputs value of |c|} +var j:0..32; {index into |dig|} +begin +j:=0; +if (c<0) then begin + print_ln('Internal error: print_number (negative value)'); + c:=0; + end; +if form=8 then + out(' O ') +else if form=10 then + out(' D ') +else if form=16 then + out(' H ') +else begin + print_ln('Internal error: print_number (form)'); + form:=16; + out(' H ') + end; +while (c>0) or (j=0) do begin + dig[j]:=c mod form; c:=c div form; + j:=j+1; + end; +out_digs(j); +end; +@# +procedure out_hex(@!k,@!l:index); + {outputs |l| bytes in hex} +var a:0..@"7FFFFFFF; {accumulator for bits not yet output} +@!b:0..32; {the number of significant bits in |a|} +@!j:0..11; {the number of digits of output} +begin +out(' H '); +a:=0; b:=0; j:=0; +while l>0 do @<Reduce \(1)|l| by one, preserving the invariants@>; +while (a>0)or(j=0) do begin + dig[j]:=a mod 16; a:=a div 16; incr(j); + end; +out_digs(j); +end; + +@ @<Reduce \(1)|l|...@>= +begin decr(l); +if tfm[k+l]<>0 then begin + while b>2 do begin + dig[j]:=a mod 16; a:=a div 16; b:=b-4; incr(j); + end; + case b of + 0: a:=tfm[k+l]; + 1:a:=a+2*tfm[k+l]; + 2:a:=a+4*tfm[k+l]; + 3:a:=a+8*tfm[k+l]; + end; + end; +b:=b+8; +end + +@ The property value may be a character, which is output in hex +unless it is a letter or a digit. This procedure is the only place +where a lowercase letter will be output to the \.{PL} file. +@^system dependencies@> + +@p procedure out_char(@!c:integer); {outputs a character} +begin if font_type>vanilla then + out_hex_char(c) +else if (c>="0")and(c<="9") then + out(' C ',c-"0":1) +else if (c>="A")and(c<="Z") then + out(' C ',ASCII_10[c-"A"+2]) +else if (c>="a")and(c<="z") then + out(' C ',ASCII_14[c-"a"+2]) +else out_hex_char(c); +end; + +@ The property value might be a ``face'' byte, which is output in the +curious code mentioned earlier, provided that it is less than 18. + +@p procedure out_face(@!k:index); {outputs a |face|} +var s:0..1; {the slope} +@!b:0..8; {the weight and expansion} +begin if tfm[k]>=18 then out_hex(k,1) +else begin + out(' F '); {specify face-code format} + s:=tfm[k] mod 2; b:=tfm[k] div 2; + out(MBL_string[1+(b mod 3)]); + out(RI_string[1+s]); + out(RCE_string[1+(b div 3)]); + end; +end; + +@ And finally, the value might be a |fix_word|, which is output in +decimal notation with just enough decimal places for \.{VPtoVF} +to recover every bit of the given |fix_word|. + +All of the numbers involved in the intermediate calculations of +this procedure will be nonnegative and less than $10\cdot2^{24}$. + +@p procedure out_fix(@!k:index); {outputs a |fix_word|} +var a:0..@'7777; {accumulator for the integer part} +@!f:integer; {accumulator for the fraction part} +@!j:0..12; {index into |dig|} +@!delta:integer; {amount if allowable inaccuracy} +begin out(' R '); {specify real format} +a:=(tfm[k]*16)+(tfm[k+1] div 16); +f:=((tfm[k+1] mod 16)*@'400+tfm[k+2])*@'400+tfm[k+3]; +if a>@'3777 then @<Reduce \(2)negative to positive@>; +@<Output the integer part, |a|, in decimal notation@>; +@<Output the fraction part, $|f|/2^{20}$, in decimal notation@>; +end; + +@ The following code outputs at least one digit even if |a=0|. + +@<Output the integer...@>= +begin j:=0; +repeat dig[j]:=a mod 10; a:=a div 10; incr(j); +until a=0; +out_digs(j); +end + +@ And the following code outputs at least one digit to the right +of the decimal point. + +@<Output the fraction...@>= +begin out('.'); f:=10*f+5; delta:=10; +repeat if delta>@'4000000 then f:=f+@'2000000-(delta div 2); +out(f div @'4000000:1); f:=10*(f mod @'4000000); delta:=delta*10; +until f<=delta; +end; + +@ @<Reduce \(2)negative to positive@>= +begin out('-'); a:=@'10000-a; +if f>0 then begin + f:=@'4000000-f; decr(a); + end; +end + +@* Outputting the TFM info. +\TeX\ checks the information of a \.{TFM} file for validity as the +file is being read in, so that no further checks will be needed when +typesetting is going on. And when it finds something wrong, it justs +calls the file ``bad,'' without identifying the nature of the problem, +since \.{TFM} files are supposed to be good almost all of the time. + +Of course, a bad file shows up every now and again, and that's where +\.{VFtoVP} comes in. This program wants to catch at least as many errors as +\TeX\ does, and to give informative error messages besides. +All of the errors are corrected, so that the \.{VPL} output will +be correct (unless, of course, the \.{TFM} file was so loused up +that no attempt is being made to fathom it). + +@ Just before each character is processed, its code is printed in hex +notation. Up to eight such codes appear on a line; so we have a variable +to keep track of how many are currently there. We also keep track of +whether or not any errors have had to be corrected. + +@<Glob...@>= +@!chars_on_line:0..8; {the number of characters printed on the current line} +@!perfect:boolean; {was the file free of errors?} + +@ @<Set init...@>= +chars_on_line:=0;@/ +perfect:=true; {innocent until proved guilty} + +@ Error messages are given with the help of the |bad| and |range_error| +and |bad_char| macros: + +@d bad(#)==begin perfect:=false; if chars_on_line>0 then print_ln(' '); + chars_on_line:=0; print_ln('Bad OFM file: ',#); + end +@.Bad OFM file@> +@d range_error(#)==begin perfect:=false; print_ln(' '); + print(#,' index for character '); + print_hex(c); print_ln(' is too large;'); + print_ln('so I reset it to zero.'); + end +@d bad_char_tail(#)==print_hex(#); print_ln('.'); + end +@d bad_char(#)==begin perfect:=false; if chars_on_line>0 then print_ln(' '); + chars_on_line:=0; print('Bad OFM file: ',#,' nonexistent character '); + bad_char_tail +@d correct_bad_char_tail(#)==#(k,0) + end +@d correct_bad_char_middle(#)==print_hex(#(k)); print_ln('.'); + correct_bad_char_tail +@d correct_bad_char(#)== begin perfect:=false; + if chars_on_line>0 then print_ln(' '); + chars_on_line:=0; print('Bad OFM file: ',#,' nonexistent character '); + correct_bad_char_middle + +@<Glob...@>= +@!i:integer; {an index to words of a subfile} +@!c:xchar_type; {a random character} +@!d:0..3; {byte number in a word} +@!k:index; {a random index} +@!r:0..65535; {a random two-byte value} +@!count:0..127; {for when we need to enumerate a small set} + +@ There are a lot of simple things to do, and they have to be done one +at a time, so we might as well get down to business. The first things +that \.{VFtoVP} will put into the \.{VPL} file appear in the header part. + +@<Do the header@>= +begin +case ofm_level of + 0: begin out('(OFMLEVEL H 0)'); out_ln; end; + 1: begin out('(OFMLEVEL H 1)'); out_ln; end; + end; +if ofm_on then begin + left; + if font_dir<=7 then out('FONTDIR') + else out('NFONTDIR'); + case font_dir mod 8 of + 0: out(' TL'); + 1: out(' LT'); + 2: out(' TR'); + 3: out(' LB'); + 4: out(' BL'); + 5: out(' RT'); + 6: out(' BR'); + 7: out(' RB'); + end; + right + end; +font_type:=vanilla; +if lh>=12 then begin + @<Set the true |font_type|@>; + if lh>=17 then begin + @<Output the family name@>; + if lh>=18 then @<Output the rest of the header@>; + end; + @<Output the character coding scheme@>; + end; +@<Output the design size@>; +@<Output the check sum@>; +@<Output the |seven_bit_safe_flag|@>; +end + +@ @<Output the check sum@>= +left; out('CHECKSUM'); out_hex(check_sum,4); +right + +@ Incorrect design sizes are changed to 10 points. + +@d bad_design(#)==begin bad('Design size ',#,'!'); +@.Design size wrong@> + print_ln('I''ve set it to 10 points.'); + out(' D 10'); + end + +@ @<Output the design size@>= +left; out('DESIGNSIZE'); +if tfm[design_size]>127 then bad_design('negative') +else if (tfm[design_size]=0)and(tfm[design_size+1]<16) then + bad_design('too small') +else out_fix(design_size); +right; +out('(COMMENT DESIGNSIZE IS IN POINTS)'); out_ln; +out('(COMMENT OTHER SIZES ARE MULTIPLES OF DESIGNSIZE)'); out_ln +@.DESIGNSIZE IS IN POINTS@> + +@ Since we have to check two different {\mc BCPL} strings for validity, +we might as well write a subroutine to make the check. + +@p procedure check_BCPL(@!k,@!l:index); {checks a string of length |<l|} +var j:index; {runs through the string} +@!c:integer; {character being checked} +begin if tfm[k]>=l then begin + bad('String is too long; I''ve shortened it drastically.'); +@.String is too long...@> + tfm[k]:=1; + end; +for j:=k+1 to k+tfm[k] do begin + c:=tfm[j]; + if (c="(")or(c=")") then + begin bad('Parenthesis in string has been changed to slash.'); +@.Parenthesis...changed to slash@> + tfm[j]:="/"; + end + else if (c<" ")or(c>"~") then begin + bad('Nonstandard ASCII code has been blotted out.'); +@.Nonstandard ASCII code...@> + tfm[j]:="?"; + end + else if (c>="a")and(c<="z") then tfm[j]:=c+"A"-"a"; {upper-casify letters} + end; +end; + +@ The |font_type| starts out |vanilla|; possibly we need to reset it. + +@<Set the true |font_type|@>= +begin check_BCPL(scheme,40); +if (tfm[scheme]>=11)and@|(tfm[scheme+1]="T")and@| + (tfm[scheme+2]="E")and@|(tfm[scheme+3]="X")and@| + (tfm[scheme+4]=" ")and@|(tfm[scheme+5]="M")and@| + (tfm[scheme+6]="A")and@|(tfm[scheme+7]="T")and@| + (tfm[scheme+8]="H")and@|(tfm[scheme+9]=" ") then begin + if (tfm[scheme+10]="S")and(tfm[scheme+11]="Y") then font_type:=mathsy + else if (tfm[scheme+10]="E")and(tfm[scheme+11]="X") then font_type:=mathex; + end; +end + +@ @<Output the character coding scheme@>= +left; out('CODINGSCHEME'); +out_BCPL(scheme); +right + +@ @<Output the family name@>= +left; out('FAMILY'); +check_BCPL(family,20); +out_BCPL(family); +right + +@ @<Output the rest of the header@>= +begin left; out('FACE'); out_face(random_word+3); right; +for i:=18 to lh-1 do begin + left; out('HEADER D ',i:1); + out_hex(check_sum+4*i,@,4); right; + end; +end + +@ This program does not check to see if the |seven_bit_safe_flag| has the +correct setting, i.e., if it really reflects the seven-bit-safety of +the \.{TFM} file; the stated value is merely put into the \.{VPL} file. +The \.{VPtoVF} program will store a correct value and give a warning +message if a file falsely claims to be safe. + +@<Output the |seven_bit_safe_flag|@>= +if (lh>17) and (tfm[random_word]>127) then begin + left; out('SEVENBITSAFEFLAG FALSE'); right; + end + +@ The next thing to take care of is the list of parameters. + +@<Do the parameters@>= +if np>0 then begin + left; out('FONTDIMEN'); out_ln; + for i:=1 to np do @<Check and output the $i$th parameter@>; + right; + end; +@<Check to see if |np| is complete for this font type@>; + +@ @<Check to see if |np|...@>= +if (font_type=mathsy)and(np<>22) then + print_ln('Unusual number of fontdimen parameters for a math symbols font (', +@.Unusual number of fontdimen...@> + np:1,' not 22).') +else if (font_type=mathex)and(np<>13) then + print_ln('Unusual number of fontdimen parameters for an extension font (', + np:1,' not 13).') + +@ All |fix_word| values except the design size and the first parameter +will be checked to make sure that they are less than 16.0 in magnitude, +using the |check_fix| macro: + +@d check_fix_tail(#)==bad(#,' ',i:1,' is too big;'); + print_ln('I have set it to zero.'); + end +@d check_fix(#)==if (tfm[#]>0)and(tfm[#]<255) then + begin tfm[#]:=0; tfm[(#)+1]:=0; tfm[(#)+2]:=0; tfm[(#)+3]:=0; + check_fix_tail + +@<Check and output the $i$th parameter@>= +begin left; +if i=1 then out('SLANT') {this parameter is not checked} +else begin check_fix(param(i))('Parameter');@/ +@.Parameter n is too big@> + @<Output the name of parameter $i$@>; + end; +out_fix(param(i)); right; +end + +@ @<Output the name...@>= +if i<=7 then case i of + 2:out('SPACE');@+3:out('STRETCH');@+4:out('SHRINK'); + 5:out('XHEIGHT');@+6:out('QUAD');@+7:out('EXTRASPACE')@+end +else if (i<=22)and(font_type=mathsy) then case i of + 8:out('NUM1');@+9:out('NUM2');@+10:out('NUM3'); + 11:out('DENOM1');@+12:out('DENOM2'); + 13:out('SUP1');@+14:out('SUP2');@+15:out('SUP3'); + 16:out('SUB1');@+17:out('SUB2'); + 18:out('SUPDROP');@+19:out('SUBDROP'); + 20:out('DELIM1');@+21:out('DELIM2'); + 22:out('AXISHEIGHT')@+end +else if (i<=13)and(font_type=mathex) then + if i=8 then out('DEFAULTRULETHICKNESS') + else out('BIGOPSPACING',i-8:1) +else out('PARAMETER D ',i:1) + +@ @<Glob...@>= +@!start_counter,@!base_counter,@!number_entries:integer; +@!value:integer; + +@ @<Do the ivalue parameters@>= +if nki>0 then begin + start_counter:=ivalues_start*4; + base_counter:=ivalues_base*4; + for i:=0 to nki-1 do @<Check and output the $i$th ivalue table@>; + end; + +@ @<Check and output the $i$th ivalue table@>= +begin +left; out('FONTIVALUE'); out_hex_number(i); out_ln; +number_entries:=256*tfm[start_counter+2]+tfm[start_counter+3]; +for j:=0 to number_entries-1 do begin + left; out('IVALUE'); out_hex_number(j); out_ln; + value:=256*tfm[base_counter+2]+tfm[base_counter+3]; + left; out('IVALUEVAL'); out_hex_number(value); right; + right; + base_counter:=base_counter+4; + end; +right; +start_counter:=start_counter+4;; +end; + +@ @<Do the fvalue parameters@>= +if nkf>0 then begin + start_counter:=fvalues_start*4; + base_counter:=fvalues_base*4; + for i:=0 to nkf-1 do @<Check and output the $i$th fvalue table@>; + end; + +@ @<Check and output the $i$th fvalue table@>= +begin +left; out('FONTFVALUE'); out_hex_number(i); out_ln; +number_entries:=256*tfm[start_counter+2]+tfm[start_counter+3]; +for j:=0 to number_entries-1 do begin + left; out('FVALUE'); out_hex_number(j); out_ln; + left; out('FVALUEVAL'); out_fix(base_counter); right; + right; + base_counter:=base_counter+4; + end; +right; +start_counter:=start_counter+4; +end; + +@ @<Do the mvalue parameters@>= +if nkm>0 then begin + start_counter:=mvalues_start*4; + base_counter:=mvalues_base*4; + for i:=0 to nkm-1 do @<Check and output the $i$th mvalue table@>; + end; + +@ @<Check and output the $i$th mvalue table@>= +begin +left; out('FONTMVALUE'); out_hex_number(i); out_ln; +number_entries:=256*tfm[start_counter+2]+tfm[start_counter+3]; +for j:=0 to number_entries-1 do begin + left; out('MVALUE'); out_hex_number(j); out_ln; + left; out('MVALUEVAL'); out_fix(base_counter); right; + right; + base_counter:=base_counter+4; + end; +right; +start_counter:=start_counter+4; +end; + +@ @<Do the rule parameters@>= +if nkr>0 then begin + start_counter:=rules_start*4; + base_counter:=rules_base*4; + for i:=0 to nkr-1 do @<Check and output the $i$th rule table@>; + end; + +@ @<Check and output the $i$th rule table@>= +begin +left; out('FONTRULE'); out_hex_number(i); out_ln; +number_entries:=256*tfm[start_counter+2]+tfm[start_counter+3]; +for j:=0 to number_entries-1 do begin + left; out('RULE'); out_hex_number(j); out_ln; + left; out('RULEWD'); out_fix(base_counter); right; + left; out('RULEHT'); out_fix(base_counter+4); right; + left; out('RULEDP'); out_fix(base_counter+8); right; + right; + base_counter:=base_counter+12; + end; +right; +start_counter:=start_counter+4; +end; + +@ @<Do the glue parameters@>= +if nkg>0 then begin + start_counter:=glues_start*4; + base_counter:=glues_base*4; + for i:=0 to nkg-1 do @<Check and output the $i$th glue table@>; + end; + +@ @<Glob...@>= +@!glue_subtype,@!glue_argument_kind,@!glue_stretch_order, +@!glue_shrink_order,@!glue_argument:integer; + +@ @<Check and output the $i$th glue table@>= +begin +left; out('FONTGLUE'); out_hex_number(i); out_ln; +number_entries:=256*tfm[start_counter+2]+tfm[start_counter+3]; +for j:=0 to number_entries-1 do begin + left; out('GLUE'); out_hex_number(j); out_ln; + glue_subtype:=tfm[base_counter] div 16; + glue_argument_kind:=tfm[base_counter] mod 16; + glue_stretch_order:=tfm[base_counter+1] div 16; + glue_shrink_order:=tfm[base_counter+1] mod 16; + glue_argument:=tfm[base_counter+2]*256+tfm[base_counter+3]; + left; + out('GLUETYPE'); + case glue_subtype of + 0: out(' H 0'); + 1: out(' H 1'); + 2: out(' H 2'); + 3: out(' H 3'); + end; + right; + case glue_argument_kind of + 1: begin + left; out('GLUERULE'); out_hex_number(glue_argument); right; + end; + 2: begin + left; out('GLUECHAR'); out_hex_number(glue_argument); right; + end; + end; + left; + out('GLUESTRETCHORDER'); + case glue_stretch_order of + 0: out(' H 0'); + 1: out(' H 1'); + 2: out(' H 2'); + 3: out(' H 3'); + 4: out(' H 4'); + end; + right; + left; + out('GLUESHRINKORDER'); + case glue_shrink_order of + 0: out(' H 0'); + 1: out(' H 1'); + 2: out(' H 2'); + 3: out(' H 3'); + 4: out(' H 4'); + end; + right; + left; out('GLUEWD'); out_fix(base_counter+4); right; + left; out('GLUESTRETCH'); out_fix(base_counter+8); right; + left; out('GLUESHRINK'); out_fix(base_counter+12); right; + right; + base_counter:=base_counter+16; + end; +right; +start_counter:=start_counter+4; +end; + +@ @<Do the penalty parameters@>= +if nkp>0 then begin + start_counter:=penalties_start*4; + base_counter:=penalties_base*4; + for i:=0 to nkp-1 do @<Check and output the $i$th penalty table@>; + end; + +@ @<Check and output the $i$th penalty table@>= +begin +left; out('FONTPENALTY'); out_hex_number(i); out_ln; +number_entries:=256*tfm[start_counter+2]+tfm[start_counter+3]; +for j:=0 to number_entries-1 do begin + left; out('PENALTY'); out_hex_number(j); out_ln; + value:=256*tfm[base_counter+2]+tfm[base_counter+3]; + left; out('PENALTYVAL'); out_hex_number(value); right; + right; + base_counter:=base_counter+4; + end; +right; +start_counter:=start_counter+4; +end; + +@ We need to check the range of all the remaining |fix_word| values, +and to make sure that |width[0]=0|, etc. + +@d nonzero_fix(#)==(tfm[#]>0)or(tfm[#+1]>0)or(tfm[#+2]>0)or(tfm[#+3]>0) + +@<Check the |fix_word| entries@>= +if nonzero_fix(4*width_base) then bad('width[0] should be zero.'); +@.should be zero@> +if nonzero_fix(4*height_base) then bad('height[0] should be zero.'); +if nonzero_fix(4*depth_base) then bad('depth[0] should be zero.'); +if nonzero_fix(4*italic_base) then bad('italic[0] should be zero.'); +for i:=0 to nw-1 do check_fix(4*(width_base+i))('Width'); +@.Width n is too big@> +for i:=0 to nh-1 do check_fix(4*(height_base+i))('Height'); +@.Height n is too big@> +for i:=0 to nd-1 do check_fix(4*(depth_base+i))('Depth'); +@.Depth n is too big@> +for i:=0 to ni-1 do check_fix(4*(italic_base+i))('Italic correction'); +@.Italic correction n is too big@> +if nk>0 then for i:=0 to nk-1 do check_fix(kern(i))('Kern'); +@.Kern n is too big@> + +@ The ligature/kerning program comes next. Before we can put it out in +\.{PL} format, we need to make a table of ``labels'' that will be inserted +into the program. For each character |c| whose |tag| is |lig_tag| and +whose starting address is |r|, we will store the pair |(c,r)| in the +|label_table| array. If there's a boundary-char program starting at~|r|, +we also store the pair |(256,r)|. +This array is sorted by its second components, using the +simple method of straight insertion. + +@<Glob...@>= +@!label_table:array[xxxchar_type] of record +@!cc:xchar_type;@!rr:0..lig_size;ischar:boolean;end; +@!label_ptr:xxchar_type; {the largest entry in |label_table|} +@!sort_ptr:xxchar_type; {index into |label_table|} +@!boundary_char:xchar_type; {boundary character, or |xmax_char| if none} +@!bchar_label:0..xmax_label; {beginning of boundary character program} + +@ @<Set init...@>= +boundary_char:=xmax_char; bchar_label:=xmax_label;@/ +label_ptr:=0; label_table[0].rr:=0; {a sentinel appears at the bottom} + +@ We'll also identify and remove inaccessible program steps, using the +|activity| array. + +@d unreachable=0 {a program step not known to be reachable} +@d pass_through=1 {a program step passed through on initialization} +@d accessible=2 {a program step that can be relevant} + +@<Glob...@>= +@!activity:array[0..lig_size] of unreachable..accessible; +@!ai,@!acti:0..lig_size; {indices into |activity|} + +@ @<Do the ligatures and kerns@>= +if nl>0 then begin + for ai:=0 to (nl-1) do activity[ai]:=unreachable; + @<Check for a boundary char@>; + end; +@<Build the label table@>; +if nl>0 then begin + left; out('LIGTABLE'); out_ln;@/ + @<Compute the |activity| array@>; + @<Output and correct the ligature/kern program@>; + right; + @<Check for ligature cycles@>; + end + +@ We build the label table even when |nl=0|, because this catches errors +that would not otherwise be detected. + +@<Build...@>= +for c:=bc to ec do +if (tag(c)=lig_tag) or (ctag(c)) then begin + r:=rremainder(c); + if (l_skip_byte(lig_step(r)) div 256)=0 then begin + if r<nl then begin + if l_skip_byte(lig_step(r))>stop_flag then begin + r:=256*l_op_byte(lig_step(r))+l_remainder(lig_step(r)); + if r<nl then if activity[rremainder(c)]=unreachable then + activity[rremainder(c)]:=pass_through; + end; + end; + end; + if r>=nl then begin + perfect:=false; print_ln(' '); + print('Ligature/kern starting index for character '); print_hex(c); + print_ln(' is too large;'); print_ln('so I removed it.'); set_no_tag(c); + set_no_ctag(c); +@.Ligature/kern starting index...@> + end + else @<Insert |(c,r)| into |label_table|@>; + end; +label_table[label_ptr+1].rr:=lig_size; {put ``infinite'' sentinel at the end} + +@ @<Insert |(c,r)|...@>= +begin sort_ptr:=label_ptr; {there's a hole at position |sort_ptr+1|} +while label_table[sort_ptr].rr>r do begin + label_table[sort_ptr+1]:=label_table[sort_ptr]; + decr(sort_ptr); {move the hole} + end; +label_table[sort_ptr+1].ischar:=not ctag(c); +if ctag(c) then + label_table[sort_ptr+1].cc:=char_param(c,0) +else + label_table[sort_ptr+1].cc:=c; +label_table[sort_ptr+1].rr:=r; {fill the hole} +incr(label_ptr); activity[r]:=accessible; +end + +@ @<Check for a bound...@>= +if l_skip_byte(lig_step(0))=255 then begin + left; out('BOUNDARYCHAR'); + boundary_char:=l_next_char(lig_step(0)); out_char(boundary_char); right; + activity[0]:=pass_through; + end; +if l_skip_byte(lig_step(nl-1))=255 then begin + r:=256*l_op_byte(lig_step(nl-1))+l_remainder(lig_step(nl-1)); + if r>=nl then begin + perfect:=false; print_ln(' '); + print('Ligature/kern starting index for boundarychar is too large;'); + print_ln('so I removed it.'); +@.Ligature/kern starting index...@> + end + else begin + label_ptr:=1; label_table[1].cc:=xmax_char; + label_table[1].rr:=r; + bchar_label:=r; activity[r]:=accessible; + end; + activity[nl-1]:=pass_through; + end + +@ @<Compute the |activity| array@>= +for ai:=0 to (nl-1) do +if (l_skip_byte(lig_step(ai)) div 256)=1 then + activity[ai]:=accessible +else if activity[ai]=accessible then begin + r:=l_skip_byte(lig_step(ai)); + if r<stop_flag then begin + r:=r+ai+1; + if r>=nl then begin + bad('Ligature/kern step ',ai:1,' skips too far;'); +@.Lig...skips too far@> + print_ln('I made it stop.'); set_l_skip_byte(lig_step(ai),stop_flag); + end + else activity[r]:=accessible; + end; + end + +@ We ignore |pass_through| items, which don't need to be mentioned in +the \.{PL} file. + +@<Output and correct the ligature...@>= +sort_ptr:=1; {point to the next label that will be needed} +for acti:=0 to nl-1 do if activity[acti]<>pass_through then begin + i:=acti; @<Take care of commenting out unreachable steps@>; + @<Output any labels for step $i$@>; + @<Output step $i$ of the ligature/kern program@>; + end; +if level=2 then right {the final step was unreachable} + +@ @<Globals...@>= +@!output_clabels:array[0..256] of boolean; +@!clabel_runner:integer; +@!cprime:integer; + +@ @<Set init...@>= +for clabel_runner:=0 to 256 do + output_clabels[clabel_runner]:=false; + +@ @<Output any labels...@>= +while i=label_table[sort_ptr].rr do begin + if not label_table[sort_ptr].ischar then begin + if not output_clabels[label_table[sort_ptr].cc] then begin + output_clabels[label_table[sort_ptr].cc]:=true; + left; + out('CLABEL'); + out_char(label_table[sort_ptr].cc); + right; + end + end + else begin + left; + out('LABEL'); + if label_table[sort_ptr].cc=xmax_char then out(' BOUNDARYCHAR') + else out_char(label_table[sort_ptr].cc); + right; + end; + incr(sort_ptr); + end + +@ @<Take care of commenting out...@>= +if activity[i]=unreachable then begin + if level=1 then begin + left; out('COMMENT THIS PART OF THE PROGRAM IS NEVER USED!'); out_ln; + end + end +else if level=2 then right + +@ @<Output step $i$...@>= +begin k:=lig_step(i); +if (l_skip_byte(k) div 256)=1 then begin + case l_op_byte(k) of + 17: begin + left; out('CPEN'); + out_hex_number(l_next_char(k)); + out_hex_number(l_remainder(k)); + right; + end; + 18: begin + left; out('CGLUE'); + out_hex_number(l_next_char(k)); + out_hex_number(l_remainder(k)); + right; + end; + 19: begin + left; out('CPENGLUE'); + out_hex_number(l_next_char(k)); + out_hex_number(l_remainder(k) div 256); + out_hex_number(l_remainder(k) mod 256); + right; + end; + 20: begin + left; out('CKRN'); + out_hex_number(l_next_char(k)); + r:=l_remainder(k); + if r>=nk then begin + bad('Kern index too large.'); +@.Kern index too large@> + out(' R 0.0'); + end + else out_fix(kern(r)); + right; + end; + end; + end +else if l_skip_byte(k)>stop_flag then begin + if (256*l_op_byte(k)+l_remainder(k))>=nl then + bad('Ligature unconditional stop command address is too big.'); +@.Ligature unconditional stop...@> + end +else if l_op_byte(k)>=kern_flag then @<Output a kern step@> +else @<Output a ligature step@>; +if (l_skip_byte(k) mod 256)>0 then + if level=1 then @<Output either \.{SKIP} or \.{STOP}@>; +end + +@ The \.{SKIP} command is a bit tricky, because we will be omitting all +inaccessible commands. + +@<Output either...@>= +begin if (l_skip_byte(k) mod 256)>=stop_flag then out('(STOP)') +else begin + count:=0; + for ai:=(i+1) to (i+(l_skip_byte(k) mod 256)) do + if activity[ai]=accessible then incr(count); + out('(SKIP D ',count:1,')'); {possibly $count=0$, so who cares} + end; +out_ln; +end + +@ @<Output a kern step@>= +begin if nonexistent(l_next_char(k)) then + if l_next_char(k)<>boundary_char then + correct_bad_char('Kern step for')(l_next_char)(set_l_next_char); +@.Kern step for nonexistent...@> +left; out('KRN'); out_char(l_next_char(k)); +r:=256*(l_op_byte(k)-kern_flag)+l_remainder(k); +if r>=nk then begin + bad('Kern index too large.'); +@.Kern index too large@> + out(' R 0.0'); + end +else out_fix(kern(r)); +right; +end + +@ @<Output a ligature step@>= +begin if nonexistent(l_next_char(k)) then + if l_next_char(k)<>boundary_char then + correct_bad_char('Ligature step for')(l_next_char)(set_l_next_char); +@.Ligature step for nonexistent...@> +if nonexistent(l_remainder(k)) then +correct_bad_char('Ligature step produces the')(l_remainder)(set_l_remainder); +@.Ligature step produces...@> +left; r:=l_op_byte(k); +if (r=4)or((r>7)and(r<>11)) then begin + print_ln('Ligature step with nonstandard code changed to LIG'); + r:=0; set_l_op_byte(k,0); + end; +if r mod 4>1 then out('/'); +out('LIG'); +if odd(r) then out('/'); +while r>3 do begin + out('>'); r:=r-4; + end; +out_char(l_next_char(k)); out_char(l_remainder(k)); right; +end + +@ The last thing on \.{VFtoVP}'s agenda is to go through the +list of |char_info| and spew out the information about each individual +character. + +@<Do the characters@>= +sort_ptr:=0; {this will suppress `\.{STOP}' lines in ligature comments} +c:=bc; +while (c<=ec) do begin + if width_index(c)>0 then begin + if chars_on_line=8 then begin + print_ln(' '); chars_on_line:=1; + end + else begin + if chars_on_line>0 then print(' '); + incr(chars_on_line); + end; + for cprime:=c to (c+no_repeats(c)) do begin + print_hex(cprime); {progress report} + left; out('CHARACTER'); out_char(cprime); out_ln; + @<Output the character's width@>; + if height_index(c)>0 then @<Output the character's height@>; + if depth_index(c)>0 then @<Output the character's depth@>; + if italic_index(c)>0 then @<Output the italic correction@>; + case tag(c) of + no_tag: do_nothing; + lig_tag: @<Output the applicable part of the ligature/kern + program as a comment@>; + list_tag: @<Output the character link unless there is a problem@>; + ext_tag: @<Output an extensible character recipe@>; + end; {there are no other cases} + for i:=0 to npc-1 do begin + if char_param(c,i)<>0 then begin + left; + if i<nki then begin + out('CHARIVALUE'); out_hex_number(i); + end + else if i<(nki+nkf) then begin + out('CHARFVALUE'); out_hex_number(i-nki); + end + else if i<(nki+nkf+nkr) then begin + out('CHARRULE'); out_hex_number(i-nki-nkf); + end + else if i<(nki+nkf+nkr+nkg) then begin + out('CHARGLUE'); out_hex_number(i-nki-nkf-nkr); + end + else if i<(nki+nkf+nkr+nkg+nkp) then begin + out('CHARPENALTY'); out_hex_number(i-nki-nkf-nkr-nkg); + end; + out_hex_number(char_param(c,i)); + right; + end; + end; + if not do_map(cprime) then goto final_end; + right; + end; + end; + c:=c+1+no_repeats(c); + end + +@ @<Output the character's width@>= +if width_index(c)>=nw then range_error('Width') +@.Width index for char...@> +else begin left; out('CHARWD'); out_fix(width(c)); right; + end + +@ @<Output the character's height@>= +if height_index(c)>=nh then range_error('Height') +@.Height index for char...@> +else begin left; out('CHARHT'); out_fix(height(c)); right; + end + +@ @<Output the character's depth@>= +if depth_index(c)>=nd then range_error('Depth') +@.Depth index for char@> +else begin left; out('CHARDP'); out_fix(depth(c)); right; + end + +@ @<Output the italic correction@>= +if italic_index(c)>=ni then range_error('Italic correction') +@.Italic correction index for char...@> +else begin left; out('CHARIC'); out_fix(italic(c)); right; + end + +@ @<Output the applicable part of the ligature...@>= +begin left; out('COMMENT'); out_ln;@/ +i:=rremainder(c); r:=lig_step(i); +if (l_skip_byte(r) mod 256)>stop_flag +then i:=256*l_op_byte(r)+l_remainder(r); +repeat @<Output step...@>; +if (l_skip_byte(k) mod 256)>=stop_flag then i:=nl +else i:=i+1+(l_skip_byte(k) mod 256); +until i>=nl; +right; +end + +@ We want to make sure that there is no cycle of characters linked together +by |list_tag| entries, since such a cycle would get \TeX\ into an endless +loop. If such a cycle exists, the routine here detects it when processing +the largest character code in the cycle. + +@<Output the character link unless there is a problem@>= +begin r:=rremainder(c); +if nonexistent(r) then begin + bad_char('Character list link to')(r); set_no_tag(c); +@.Character list link...@> + end +else begin + while (r<c)and(tag(r)=list_tag) do r:=rremainder(r); + if r=c then begin + bad('Cycle in a character list!'); +@.Cycle in a character list@> + print('Character '); print_hex(c); + print_ln(' now ends the list.'); + set_no_tag(c); + end + else begin + left; out('NEXTLARGER'); out_char(rremainder(c)); + right; + end; + end; +end + +@ @<Output an extensible character recipe@>= +if rremainder(c)>=ne then begin + range_error('Extensible'); set_no_tag(c); +@.Extensible index for char@> + end +else begin + left; out('VARCHAR'); out_ln; + @<Output the extensible pieces that exist@>; + right; + end + +@ @<Glob...@>= +@!exten_char:integer; + +@ @<Output the extensible pieces that...@>= +for d:=0 to 3 do begin + if not ofm_on then begin + k:=exten(c)+d; + exten_char:=tfm[k]; + end + else begin + k:=exten(c)+2*d; + exten_char:=256*tfm[k]+tfm[k+1]; + end; + if (d=3)or(exten_char>0) then begin + left; + case d of + 0:out('TOP');@+1:out('MID');@+2:out('BOT');@+3:out('REP')@+end; + if nonexistent(exten_char) then out_char(c) + else out_char(exten_char); + right; + end + end + +@ Some of the extensible recipes may not actually be used, but \TeX\ will +complain about them anyway if they refer to nonexistent characters. +Therefore \.{VFtoVP} must check them too. + +@<Check the extensible recipes@>= +if ne>0 then + for c:=0 to ne-1 do + for d:=0 to 3 do begin + if not ofm_on then begin + k:=4*(exten_base+c)+d; + exten_char:=tfm[k]; + end + else begin + k:=4*(exten_base+c)+2*d; + exten_char:=256*tfm[k]+tfm[k+1]; + end; + if (exten_char>0)or(d=3) then begin + if nonexistent(exten_char) then begin + bad_char('Extensible recipe involves the')(exten_char); +@.Extensible recipe involves...@> + if d<3 then begin + if not ofm_on then begin + tfm[k]:=0; + end + else begin + tfm[k]:=0; + tfm[k+1]:=0; + end; + end; + end; + end; + end + +@* Checking for ligature loops. +We have programmed almost everything but the most interesting calculation of +all, which has been saved for last as a special treat. \TeX's extended +ligature mechanism allows unwary users to specify sequences of ligature +replacements that never terminate. For example, the pair of commands +$$\.{(/LIG $x$ $y$) (/LIG $y$ $x$)}$$ +alternately replaces character $x$ by character $y$ and vice versa. A similar +loop occurs if \.{(LIG/ $z$ $y$)} occurs in the program for $x$ and + \.{(LIG/ $z$ $x$)} occurs in the program for $y$. + +More complicated loops are also possible. For example, suppose the ligature +programs for $x$ and $y$ are +$$\vcenter{\halign{#\hfil\cr +\.{(LABEL $x$)(/LIG/ $z$ $w$)(/LIG/> $w$ $y$)} \dots,\cr +\.{(LABEL $y$)(LIG $w$ $x$)} \dots;\cr}}$$ +then the adjacent characters $xz$ change to $xwz$, $xywz$, $xxz$, $xxwz$, +\dots, ad infinitum. + +@ To detect such loops, \.{TFtoPL} attempts to evaluate the function +$f(x,y)$ for all character pairs $x$ and~$y$, where $f$ is defined as +follows: If the current character is $x$ and the next character is +$y$, we say the ``cursor'' is between $x$ and $y$; when the cursor +first moves past $y$, the character immediately to its left is +$f(x,y)$. This function is defined if and only if no infinite loop is +generated when the cursor is between $x$ and~$y$. + +The function $f(x,y)$ can be defined recursively. It turns out that all pairs +$(x,y)$ belong to one of five classes. The simplest class has $f(x,y)=y$; +this happens if there's no ligature between $x$ and $y$, or in the cases +\.{LIG/>} and \.{/LIG/>>}. Another simple class arises when there's a +\.{LIG} or \.{/LIG>} between $x$ and~$y$, generating the character~$z$; +then $f(x,y)=z$. Otherwise we always have $f(x,y)$ equal to +either $f(x,z)$ or $f(z,y)$ or $f(f(x,z),y)$, where $z$ is the inserted +ligature character. + +The first two of these classes can be merged; we can also consider +$(x,y)$ to belong to the simple class when $f(x,y)$ has been evaluated. +For technical reasons we allow $x$ to be 256 (for the boundary character +at the left) or 257 (in cases when an error has been detected). + +For each pair $(x,y)$ having a ligature program step, we store +$(x,y)$ in a hash table from which the values $z$ and $class$ can be read. + +@d simple=0 {$f(x,y)=z$} +@d left_z=1 {$f(x,y)=f(z,y)$} +@d right_z=2 {$f(x,y)=f(x,z)$} +@d both_z=3 {$f(x,y)=f(f(x,z),y)$} +@d pending=4 {$f(x,y)$ is being evaluated} + +@<Glob...@>= +@!hash:array[0..hash_size] of integer; +@!class:array[0..hash_size] of simple..pending; +@!lig_z:array[0..hash_size] of xxchar_type; +@!hash_ptr:0..hash_size; {the number of nonzero entries in |hash|} +@!hash_list:array[0..hash_size] of 0..hash_size; + {list of those nonzero entries} +@!h,@!hh:0..hash_size; {indices into the hash table} +@!x_lig_cycle,@!y_lig_cycle:xchar_type; {problematic ligature pair} + +@ @<Check for ligature cycles@>= +hash_ptr:=0; y_lig_cycle:=xmax_char; +for hh:=0 to hash_size do hash[hh]:=0; {clear the hash table} +for c:=bc to ec do if tag(c)=lig_tag then begin + i:=rremainder(c); + if (l_skip_byte(lig_step(i)) mod 256)>stop_flag then + i:=256*l_op_byte(lig_step(i))+l_remainder(lig_step(i)); + @<Enter data for character $c$ starting at location |i| in the hash table@>; + end; +if bchar_label<nl then begin + c:=xmax_char; i:=bchar_label; + @<Enter data for character $c$ starting at location |i| in the hash table@>; + end; +if hash_ptr=hash_size then begin + print_ln('Sorry, I haven''t room for so many ligature/kern pairs!'); +@.Sorry, I haven't room...@> + goto final_end; + end; +for hh:=1 to hash_ptr do begin + r:=hash_list[hh]; + if class[r]>simple then {make sure $f$ is defined} + r:=f(r,(hash[r]-1)div xmax_char,(hash[r]-1)mod xmax_char); + end; +if y_lig_cycle<xmax_char then begin + print('Infinite ligature loop starting with '); +@.Infinite ligature loop...@> + if x_lig_cycle=xmax_char + then print('boundary')@+else print_hex(x_lig_cycle); + print(' and '); print_hex(y_lig_cycle); print_ln('!'); + out('(INFINITE LIGATURE LOOP MUST BE BROKEN!)'); goto final_end; + end + +@ @<Enter data for character $c$...@>= +repeat hash_input; k:=l_skip_byte(lig_step(i)); +if k>=stop_flag then i:=nl +else i:=i+1+k; +until i>=nl + +@ We use an ``ordered hash table'' with linear probing, because such a table +is efficient when the lookup of a random key tends to be unsuccessful. + +@p procedure hash_input; {enter data for character |c| and command |i|} +label 30; {go here for a quick exit} +var @!cc:simple..both_z; {class of data being entered} +@!zz:char_type; {function value or ligature character being entered} +@!y:char_type; {the character after the cursor} +@!key:integer; {value to be stored in |hash|} +@!t:integer; {temporary register for swapping} +begin if hash_ptr=hash_size then goto 30; +@<Compute the command parameters |y|, |cc|, and |zz|@>; +key:=xmax_char*c+y+1; h:=(hash_mult*(key mod hash_size)) mod hash_size; +while hash[h]>0 do begin + if hash[h]<=key then begin + if hash[h]=key then goto 30; {unused ligature command} + t:=hash[h]; hash[h]:=key; key:=t; {do ordered-hash-table insertion} + t:=class[h]; class[h]:=cc; cc:=t; {namely, do a swap} + t:=lig_z[h]; lig_z[h]:=zz; zz:=t; + end; + if h>0 then decr(h)@+else h:=hash_size; + end; +hash[h]:=key; class[h]:=cc; lig_z[h]:=zz; +incr(hash_ptr); hash_list[hash_ptr]:=h; +30:end; + +@ We must store kern commands as well as ligature commands, because +the former might make the latter inapplicable. + +@<Compute the command param...@>= +k:=lig_step(i); y:=l_next_char(k); t:=l_op_byte(k); +cc:=simple; zz:=l_remainder(k); +if t>=kern_flag then zz:=y +else begin case t of + 0,6:do_nothing; {\.{LIG},\.{/LIG>}} + 5,11:zz:=y; {\.{LIG/>}, \.{/LIG/>>}} + 1,7:cc:=left_z; {\.{LIG/}, \.{/LIG/>}} + 2:cc:=right_z; {\.{/LIG}} + 3:cc:=both_z; {\.{/LIG/}} + end; {there are no other cases} + end + +@ Evaluation of $f(x,y)$ is handled by two mutually recursive procedures. +Kind of a neat algorithm, generalizing a depth-first search. + +@p function f(@!h,@!x,@!y:index):index; forward;@t\2@> + {compute $f$ for arguments known to be in |hash[h]|} +function eval(@!x,@!y:index):index; {compute $f(x,y)$ with hashtable lookup} +var @!key:integer; {value sought in hash table} +begin key:=xmax_char*x+y+1; h:=(hash_mult*key) mod hash_size; +while hash[h]>key do + if h>0 then decr(h)@+else h:=hash_size; +if hash[h]<key then eval:=y {not in ordered hash table} +else eval:=f(h,x,y); +end; + +@ Pascal's beastly convention for |forward| declarations prevents us from +saying |function f(h,x,y:index):index| here. + +@p function f; +begin case class[h] of +simple: do_nothing; +left_z: begin class[h]:=pending; lig_z[h]:=eval(lig_z[h],y); class[h]:=simple; + end; +right_z: begin class[h]:=pending; lig_z[h]:=eval(x,lig_z[h]); class[h]:=simple; + end; +both_z: begin class[h]:=pending; lig_z[h]:=eval(eval(x,lig_z[h]),y); + class[h]:=simple; + end; +pending: begin x_lig_cycle:=x; y_lig_cycle:=y; + lig_z[h]:=xxmax_char; class[h]:=simple; + end; {the value |xxmax_char| will break all cycles, + since it's not in |hash|} +end; {there are no other cases} +f:=lig_z[h]; +end; + +@* Outputting the VF info. +The routines we've used for output from the |tfm| array have counterparts +for output from |vf|. One difference is that the string outputs from |vf| +need to be checked for balanced parentheses. The |string_balance| routine +tests the string of length~|l| that starts at location~|k|. + +@p function string_balance(@!k,@!l:integer):boolean; +label not_found,exit; +var @!j,@!bal:integer; +begin if l>0 then if vf[k]=" " then goto not_found; + {a leading blank is considered unbalanced} +bal:=0; +for j:=k to k+l-1 do begin + if (vf[j]<" ")or(vf[j]>=127) then goto not_found; + if vf[j]="(" then incr(bal) + else if vf[j]=")" then + if bal=0 then goto not_found else decr(bal); + end; +if bal>0 then goto not_found; +string_balance:=true; return; +not_found:string_balance:=false; +exit:end; + +@ @d bad_vf(#)==begin perfect:=false; if chars_on_line>0 then print_ln(' '); + chars_on_line:=0; print_ln('Bad OVF file: ',#); + end +@.Bad OVF file@> + +@<Do the virtual font title@>= +if string_balance(0,font_start[0]) then begin + left; out('VTITLE '); + for k:=0 to font_start[0]-1 do out(xchr[vf[k]]); + right; + end +else bad_vf('Title is not a balanced ASCII string') +@.Title is not balanced@> + +@ We can re-use some code by moving |fix_word| data to |tfm|, using the +fact that the design size has already been output. + +@p procedure out_as_fix(@!x:integer); +var @!k:1..3; +begin if abs(x)>=@'100000000 then + bad_vf('Oversize dimension has been reset to zero.'); +@.Oversize dimension...@> +if x>=0 then tfm[design_size]:=0 +else begin + tfm[design_size]:=255; x:=x+@'100000000; + end; +for k:=3 downto 1 do begin + tfm[design_size+k]:=x mod 256; x:=x div 256; + end; +out_fix(design_size); +end; + +@ @<Do the local fonts@>= +for f:=0 to font_ptr-1 do begin + left; out('MAPFONT D ',f:1); out_ln; + @<Output the font area and name@>; + for k:=0 to 11 do tfm[k]:=vf[font_start[f]+k]; + if tfm[0]+tfm[1]+tfm[2]+tfm[3]>0 then begin + left; out('FONTCHECKSUM'); out_hex(0,4); right; + end; + left; out('FONTAT'); out_fix(4); right; + left; out('FONTDSIZE'); out_fix(8); right; right; + end + +@ @<Output the font area and name@>= +a:=vf[font_start[f]+12]; l:=vf[font_start[f]+13]; +if a>0 then + if not string_balance(font_start[f]+14,a) then + bad_vf('Improper font area will be ignored') +@.Improper font area@> + else begin + left; out('FONTAREA '); + for k:=font_start[f]+14 to font_start[f]+a+13 do out(xchr[vf[k]]); + right; + end; +if (l=0)or not string_balance(font_start[f]+14+a,l) then + bad_vf('Improper font name will be ignored') +@.Improper font name@> +else begin + left; out('FONTNAME '); + for k:=font_start[f]+14+a to font_start[f]+a+l+13 do out(xchr[vf[k]]); + right; + end + +@ Now we get to the interesting part of \.{VF} output, where \.{DVI} +commands are translated into symbolic form. The \.{VPL} language is a subset +of \.{DVI}, so we sometimes need to output semantic equivalents of +the commands instead of producing a literal translation. This causes a +small but tolerable loss of efficiency. We need to simulate the stack +used by \.{DVI}-reading software. + +@<Glob...@>= +@!top:0..max_stack; {\.{DVI} stack pointer} +@!wstack,@!xstack,@!ystack,@!zstack:array[0..max_stack] of integer; + {stacked values of \.{DVI} registers |w|, |x|, |y|, |z|} +@!vf_limit:0..vf_size; {the current packet ends here} +@!o:byte; {the current opcode} + +@ @<Do the packet for character |c|@>= +if packet_start[c]=vf_size then + bad_vf('Missing packet for character ',c:1) +@.Missing packet@> +else begin + left; out('MAP'); out_ln; + top:=0; wstack[0]:=0; xstack[0]:=0; ystack[0]:=0; zstack[0]:=0; + vf_ptr:=packet_start[c]; vf_limit:=packet_end[c]+1; f:=0; + while vf_ptr<vf_limit do begin + o:=vf[vf_ptr]; incr(vf_ptr); + case o of + @<Cases of \.{DVI} instructions that can appear in character packets@>@; + improper_DVI_for_VF: bad_vf('Illegal DVI code ',o:1,' will be ignored'); + end; {there are no other cases} + end; + if top>0 then begin + bad_vf('More pushes than pops!'); +@.More pushes than pops@> + repeat out('(POP)'); decr(top);@+until top=0; + end; + right; + end + +@ A procedure called |get_bytes| helps fetch the parameters of \.{DVI} commands. + +@p function get_bytes(@!k:integer;@!signed:boolean):integer; +var @!a:integer; {accumulator} +begin if vf_ptr+k>vf_limit then begin + bad_vf('Packet ended prematurely'); k:=vf_limit-vf_ptr; + end; +a:=vf[vf_ptr]; +if (k=4) or signed then + if a>=128 then a:=a-256; +incr(vf_ptr); +while k>1 do begin + a:=a*256+vf[vf_ptr]; incr(vf_ptr); decr(k); + end; +get_bytes:=a; +end; + +@ Let's look at the simplest cases first, in order to get some experience. + +@d four_cases(#)==#,#+1,#+2,#+3 +@d eight_cases(#)==four_cases(#),four_cases(#+4) +@d sixteen_cases(#)==eight_cases(#),eight_cases(#+8) +@d thirty_two_cases(#)==sixteen_cases(#),sixteen_cases(#+16) +@d sixty_four_cases(#)==thirty_two_cases(#),thirty_two_cases(#+32) + +@<Cases...@>= +nop:do_nothing; +push:begin if top=max_stack then + begin print_ln('Stack overflow!'); goto final_end; +@.Stack overflow@> + end; + incr(top); wstack[top]:=wstack[top-1]; xstack[top]:=xstack[top-1]; + ystack[top]:=ystack[top-1]; zstack[top]:=zstack[top-1]; out('(PUSH)'); + out_ln; + end; +pop:if top=0 then bad_vf('More pops than pushes!') +@.More pops than pushes@> + else begin decr(top); out('(POP)'); out_ln; + end; +set_rule,put_rule:begin if o=put_rule then out('(PUSH)'); + left; out('SETRULE'); out_as_fix(get_bytes(4,true)); + out_as_fix(get_bytes(4,true)); + if o=put_rule then out(')(POP'); + right; + end; + +@ Horizontal and vertical motions become \.{RIGHT} and \.{DOWN} in \.{VPL} +lingo. + +@<Cases...@>= +four_cases(right1):begin out('(MOVERIGHT'); + out_as_fix(get_bytes(o-right1+1,true)); + out(')'); out_ln;@+end; +w0,four_cases(w1):begin if o<>w0 then wstack[top]:=get_bytes(o-w1+1,true); + out('(MOVERIGHT'); out_as_fix(wstack[top]); out(')'); out_ln;@+end; +x0,four_cases(x1):begin if o<>x0 then xstack[top]:=get_bytes(o-x1+1,true); + out('(MOVERIGHT'); out_as_fix(xstack[top]); out(')'); out_ln;@+end; +four_cases(down1):begin out('(MOVEDOWN'); out_as_fix(get_bytes(o-down1+1,true)); + out(')'); out_ln;@+end; +y0,four_cases(y1):begin if o<>y0 then ystack[top]:=get_bytes(o-y1+1,true); + out('(MOVEDOWN'); out_as_fix(ystack[top]); out(')'); out_ln;@+end; +z0,four_cases(z1):begin if o<>z0 then zstack[top]:=get_bytes(o-z1+1,true); + out('(MOVEDOWN'); out_as_fix(zstack[top]); out(')'); out_ln;@+end; + +@ Variable |f| always refers to the current font. If |f=font_ptr|, it's +a font that hasn't been defined (so its characters will be ignored). + +@<Cases...@>= +sixty_four_cases(fnt_num_0),four_cases(fnt1):begin f:=0; + if o>=fnt1 then font_number[font_ptr]:=get_bytes(o-fnt1+1,false) + else font_number[font_ptr]:=o-fnt_num_0; + while font_number[f]<>font_number[font_ptr] do incr(f); + if f=font_ptr then bad_vf('Undeclared font selected') +@.Undeclared font selected@> + else begin out('(SELECTFONT D ',f:1,')'); out_ln; + end; + end; + +@ Before we typeset a character we make sure that it exists. + +@<Cases...@>= +sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64), + four_cases(set1),four_cases(put1):begin if o>=set1 then + if o>=put1 then c:=get_bytes(o-put1+1,false) + else c:=get_bytes(o-set1+1,false) + else c:=o; + if f=font_ptr then + bad_vf('Character ',c:1,' in undeclared font will be ignored') +@.Character...will be ignored@> + else begin vf[font_start[f+1]-1]:=c; {store |c| in the ``hole'' we left} + k:=font_chars[f];@+while vf[k]<>c do incr(k); + if k=font_start[f+1]-1 then + bad_vf('Character ',c:1,' in font ',f:1,' will be ignored') + else begin if o>=put1 then out('(PUSH)'); + left; out('SETCHAR'); out_char(c); + if o>=put1 then out(')(POP'); + right; + end; + end; + end; + +@ The ``special'' commands are the only ones remaining to be dealt with. +We use a hexadecimal +output in the general case, if a simple string would be inadequate. + +@d vf_out_hex(#)==begin a:=#; + if a<10 then out(a:1) + else out(xchr[a-10+"A"]); + end + +@<Cases...@>= +four_cases(xxx1):begin k:=get_bytes(o-xxx1+1,false); + if k<0 then bad_vf('String of negative length!') + else begin left; + if k+vf_ptr>vf_limit then + begin bad_vf('Special command truncated to packet length'); + k:=vf_limit-vf_ptr; + end; + if (k>64)or not string_balance(vf_ptr,k) then + begin out('SPECIALHEX '); + while k>0 do + begin if k mod 32=0 then out_ln + else if k mod 4=0 then out(' '); + vf_out_hex(vf[vf_ptr] div 16); vf_out_hex(vf[vf_ptr] mod 16); + incr(vf_ptr); decr(k); + end; + end + else begin out('SPECIAL '); + while k>0 do + begin out(xchr[vf[vf_ptr]]); incr(vf_ptr); decr(k); + end; + end; + right; + end; + end; + +@* The main program. +The routines sketched out so far need to be packaged into separate procedures, +on some systems, since some \PASCAL\ compilers place a strict limit on the +size of a routine. The packaging is done here in an attempt to avoid some +system-dependent changes. + +First come the |vf_input| and |organize| procedures, which read the input data +and get ready for subsequent events. If something goes wrong, the routines +return |false|. + +@p function vf_input:boolean; +label final_end, exit; +var vf_ptr:0..vf_size; {an index into |vf|} +@!k:integer; {all-purpose index} +@!c:integer; {character code} +begin @<Read the whole \.{VF} file@>; +vf_input:=true; return; +final_end: vf_input:=false; +exit: end; +@# +function organize:boolean; +label final_end, exit; +var tfm_ptr:index; {an index into |tfm|} +begin @<Read the whole font metric file@>; +@<Set subfile sizes |lh|, |bc|, \dots, |np|@>; +@<Compute the base addresses@>; +organize:=vf_input; return; +final_end: organize:=false; +exit: end; + +@ Next we do the simple things. + +@p procedure do_simple_things; +var i:0..@'77777; {an index to words of a subfile} +@!f:0..vf_size; {local font number} +@!k:integer; {all-purpose index} +begin @<Do the virtual font title@>; +@<Do the header@>; +@<Do the parameters@>; +@<Do the ivalue parameters@>;@/ +@<Do the fvalue parameters@>;@/ +@<Do the rule parameters@>;@/ +@<Do the glue parameters@>;@/ +@<Do the penalty parameters@>;@/ +@<Do the local fonts@>; +@<Check the |fix_word| entries@>; +end; + +@ And then there's a routine for individual characters. + +@p function do_map(@!c:integer):boolean; +label final_end,exit; +var @!k:integer; +@!f:0..vf_size; {current font number} +begin @<Do the packet for character |c|@>; +do_map:=true; return; +final_end: do_map:=false; +exit:end; +@# +function do_characters:boolean; +label final_end, exit; +var @!c:integer; {character being done} +@!k:index; {a random index} +@!ai:0..lig_size; {index into |activity|} +begin @<Do the characters@>;@/ +do_characters:=true; return; +final_end: do_characters:=false; +exit:end; + +@ Here is where \.{VFtoVP} begins and ends. +@p begin initialize;@/ +if not organize then goto final_end; +do_simple_things;@/ +@<Do the ligatures and kerns@>; +@<Check the extensible recipes@>; +if not do_characters then goto final_end; +print_ln('.');@/ +if level<>0 then print_ln('This program isn''t working!'); +@.This program isn't working@> +if not perfect then begin + out('(COMMENT THE OFM AND/OR OVF FILE WAS BAD, '); + out('SO THE DATA HAS BEEN CHANGED!)'); + end; +@.THE OFM AND/OR OVF FILE WAS BAD...@> +final_end:end. + +@* System-dependent changes. +This section should be replaced, if necessary, by changes to the program +that are necessary to make \.{VFtoVP} work at a particular installation. +It is usually best to design your change file so that all changes to +previous sections preserve the section numbering; then everybody's version +will be consistent with the printed program. More extensive changes, +which introduce new sections, can be inserted here; then only the index +itself will get a new section number. +@^system dependencies@> + +@* Index. +Pointers to error messages appear here together with the section numbers +where each ident\-i\-fier is used. |