summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/dvitomp.web
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/web2c/dvitomp.web')
-rw-r--r--Build/source/texk/web2c/dvitomp.web1688
1 files changed, 1688 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/dvitomp.web b/Build/source/texk/web2c/dvitomp.web
new file mode 100644
index 00000000000..c850ceb7f8f
--- /dev/null
+++ b/Build/source/texk/web2c/dvitomp.web
@@ -0,0 +1,1688 @@
+% Copyright 1990 - 1995 by AT&T Bell Laboratories.
+
+% Permission to use, copy, modify, and distribute this software
+% and its documentation for any purpose and without fee is hereby
+% granted, provided that the above copyright notice appear in all
+% copies and that both that the copyright notice and this
+% permission notice and warranty disclaimer appear in supporting
+% documentation, and that the names of AT&T Bell Laboratories or
+% any of its entities not be used in advertising or publicity
+% pertaining to distribution of the software without specific,
+% written prior permission.
+
+% AT&T disclaims all warranties with regard to this software,
+% including all implied warranties of merchantability and fitness.
+% In no event shall AT&T be liable for any special, indirect or
+% consequential damages or any damages whatsoever resulting from
+% loss of use, data or profits, whether in an action of contract,
+% negligence or other tortious action, arising out of or in
+% connection with the use or performance of this software.
+
+
+% Version 0 was prepared (March 1990).
+% Version 0.1 implemented virtual fonts. (May 1990)
+% Version 0.30 outputs setbounds statments, ignores rules 1sp wide (May 1991)
+% Version 0.60 outputs rules as penstrokes with butt ends
+% Version 0.62 makes the output more robust when used in a macro definition
+% Version 0.63 new version number only (Unix change file has // path searching)
+% Version 0.632 outputs a setbounds path even if no 1sp vrule appears (Jan 1997)
+% Version 0.64 avoids outputting wrong fonts, improves error handling (Jan 1998)
+
+% Although considerable effort has been expended to make the DVItoMP program
+% correct and reliable, no warranty is implied; the author disclaims any
+% obligation or liability for damages, including but not limited to
+% special, indirect, or consequential damages arising out of or in
+% connection with the use or performance of this software.
+
+% This program is loosely based on DVItype Version 3.0
+% It converts a DVI file into a sequence of MetaPost picture expressions.
+
+% TeX is a trademark of the American Mathematical Society.
+
+% Here is TeX material that gets inserted after \input webmac
+\def\hang{\hangindent 3em\indent\ignorespaces}
+\font\ninerm=cmr9
+\font\sc=cmcsc10
+\let\mc=\ninerm % medium caps for names like SAIL
+\def\MP{MetaPost}
+\def\LaTeX{{\rm L\kern-.36em\raise.3ex\hbox{\sc a}\kern-.15em
+ T\kern-.1667em\lower.7ex\hbox{E}\kern-.125emX}}
+\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{DVI$\,$\lowercase{to}MP}
+\def\contentspagenumber{1}
+\def\topofcontents{\null
+ \def\titlepage{F} % include headline on the contents page
+ \def\rheader{\mainfont\hfil \contentspagenumber}
+ \vfill
+ \centerline{\titlefont The {\ttitlefont DVItoMP} processor}
+ \vskip 15pt
+ \centerline{(Version 0.64, January 1998)}
+ \vfill}
+\pageno=\contentspagenumber \advance\pageno by 1
+
+@* Introduction.
+The \.{DVItoMP} program reads binary device-independent (``\.{DVI}'')
+files that are produced by document compilers such as \TeX, and converts them
+into a symbolic form understood by \MP. It is loosely based on the \.{DVItype}
+utility program that produces a more faithful symbolic form of a \.{DVI} file.
+
+The output file is a sequence of \MP\ picture expressions, one for every page
+in the \.{DVI} file. It makes no difference to \.{DVItoMP} where the \.{DVI}
+file comes from, but it is intended to process the result of running \TeX\
+or \LaTeX\ on the output of \.{MPtoTEX}. Such a \.{DVI} file will contain
+one page for every \.{btex}$\ldots$\.{etex} block in the original input.
+Processing in with \.{DVItoMP} creates a corresponding sequence of \MP\ picture
+expressions for use as an auxiliary input file. Since \MP\ expects such files
+to have the extension \.{.MPX}, the output of \.{DVItoMP} is sometimes called
+an ``\.{MPX}'' file.
+
+The |banner| string defined here should be changed whenever \.{DVItoMP}
+gets modified.
+
+@d banner=='% Written by DVItoMP, Version 0.64'
+ {the first line of the output file}
+
+@ This program is written in standard \PASCAL, except where it is necessary
+to use extensions; for example, \.{DVItoMP} must read files whose names
+are dynamically specified, and that would be impossible in pure \PASCAL.
+All places where nonstandard constructions are used have been listed in
+the index under ``system dependencies.''
+@!@^system dependencies@>
+
+Many \.{DVI}-reading programs need the ability to move to a random place
+in a binary file. \.{DVItoMP} does not need to do this, but it does use
+a default |case| as in \.{TANGLE}, \.{WEAVE}, etc.
+
+@ The binary input comes from |dvi_file|, and the symbolic output goes to
+|mpx_file|. \PASCAL's standard |output| file is used only to print an error
+message if the \.{DVI} file is bad. The term |print| is used instead of
+|write| when this program writes on |mpx_file| just in case this helps
+some installations deal with system dependencies.
+@^system dependencies@>
+
+@d print(#)==write(mpx_file,#)
+@d print_ln(#)==write_ln(mpx_file,#)
+@d err_print(#)==write(#)
+@d err_print_ln(#)==write_ln(#)
+
+@p program DVI_to_MP(@!dvi_file,@!mpx_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 i:integer; {loop index for initializations}
+ begin @<Set initial values@>@/
+ end;
+
+@ If the program has to stop prematurely, it goes to the
+`|final_end|'. Another label, |done|, is used when stopping normally.
+
+@d final_end=9999 {label for the end of it all}
+@d done=30 {go here when finished with a subtask}
+
+@<Labels...@>=final_end,done;
+
+@ The following parameters can be changed at compile time to extend or
+reduce \.{DVItoMP}'s capacity.
+
+@<Constants...@>=
+@!max_fonts=100; {maximum number of distinct fonts per \.{DVI} file}
+@!max_fnums=300; {maximum number of fonts plus fonts local to virtual fonts}
+@!max_widths=10000; {maximum number of different characters among all fonts}
+@!virtual_space=10000;
+ {maximum total bytes of typesetting commands for virtual fonts}
+@!line_length=79; {maximum output line length (must be at least 60)}
+@!stack_size=100; {\.{DVI} files shouldn't |push| beyond this depth}
+@!name_size=1000; {total length of all font file names}
+@!name_length=50; {a file name shouldn't be longer than this}
+
+@ There is one more parameter that is a little harder to change because
+it is of type |real|.
+
+@d font_tolerance==0.00001
+ {font sizes should match to within this multiple of $2^{20}$ \.{DVI} units}
+
+@ 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}
+
+@ A global variable |history| keeps track of what type of errors have
+occurred with the hope that that \MP\ can be warned of any problems.
+
+@d spotless=0 {|history| value when no problems have been found}
+@d cksum_trouble=1 {|history| value there have been font checksum mismatches}
+@d warning_given=2 {|history| value after a recoverable error}
+@d fatal_error=3 {|history| value if processing had to be aborted}
+
+@<Glob...@>=
+history:spotless..fatal_error;
+
+@ @<Set init...@>=
+history:=spotless;
+
+@ If the \.{DVI} file is badly malformed, the whole process must be aborted;
+\.{DVItoMP} will give up, after issuing an error message about the symptoms
+that were noticed.
+
+Such errors might be discovered inside of subroutines inside of subroutines,
+so a procedure called |jump_out| has been introduced. This procedure, which
+simply transfers control to the label |final_end| at the end of the program,
+contains the only non-local |goto| statement in \.{DVItoMP}.
+@^system dependencies@>
+
+@d abort(#)==begin err_print_ln('DVItoMP abort: ',#);
+ history:=fatal_error; jump_out;
+ end
+@d bad_dvi(#)==abort('Bad DVI file: ',#,'!')
+@.Bad DVI file@>
+@d warn(#)==begin err_print_ln('DVItoMP warning: ',#);
+ history:=warning_given;
+ end
+
+@p procedure jump_out;
+begin goto final_end;
+end;
+
+@* The character set.
+Like all programs written with the \.{WEB} system, \.{DVItoMP} can be
+used with any character set. But it uses ASCII code internally, because
+the programming for portable input-output is easier when a fixed internal
+code is used, and because \.{DVI} files use ASCII code for file names.
+
+The next few sections of \.{DVItoMP} have therefore been copied from the
+analogous ones in the \.{WEB} system routines. They have been considerably
+simplified, since \.{DVItoMP} need not deal with the controversial
+ASCII codes less than @'40 or greater than @'176.
+If such codes appear in the font names,
+they will be printed as question marks.
+
+@<Types...@>=
+@!ASCII_code=" ".."~"; {a subrange of the integers}
+
+@ The original \PASCAL\ compiler was designed in the late 60s, when six-bit
+character sets were common, so it did not make provision for lower case
+letters. Nowadays, of course, we need to deal with both upper and lower case
+alphabets in a convenient way, especially in a program like \.{DVItoMP}.
+So we shall assume that the \PASCAL\ system being used for \.{DVItoMP}
+has a character set containing at least the standard visible characters
+of ASCII code (|"!"| through |"~"|).
+
+Some \PASCAL\ compilers use the original name |char| for the data type
+associated with the characters in text files, while other \PASCAL s
+consider |char| to be a 64-element subrange of a larger data type that has
+some other name. In order to accommodate this difference, we shall use
+the name |text_char| to stand for the data type of the characters in the
+output file. We shall also assume that |text_char| consists of
+the elements |chr(first_text_char)| through |chr(last_text_char)|,
+inclusive. The following definitions should be adjusted if necessary.
+@^system dependencies@>
+
+@d text_char == char {the data type of characters in text files}
+@d first_text_char=0 {ordinal number of the smallest element of |text_char|}
+@d last_text_char=127 {ordinal number of the largest element of |text_char|}
+
+@<Types...@>=
+@!text_file=packed file of text_char;
+
+@ The \.{DVItoMP} processor converts from ASCII code to
+the user's external character set by means of an array |xchr|
+that is analogous to \PASCAL's |chr| function.
+
+@<Globals...@>=
+@!mpx_file:text_file; {destination for printed output}
+@!xchr: array [0..255] of text_char;
+ {specifies conversion of output characters}
+
+@ To prepare the |mpx_file| for output, we |rewrite| it.
+@^system dependencies@>
+
+@p procedure open_mpx_file; {prepares to write text on |mpx_file|}
+begin rewrite(mpx_file);
+end;
+
+@ Under our assumption that the visible characters of standard ASCII are
+all present, the following assignment statements initialize the
+|xchr| array properly, without needing any system-dependent changes.
+
+@<Set init...@>=
+for i:=0 to @'37 do xchr[i]:='?';
+xchr[@'40]:=' ';
+xchr[@'41]:='!';
+xchr[@'42]:='"';
+xchr[@'43]:='#';
+xchr[@'44]:='$';
+xchr[@'45]:='%';
+xchr[@'46]:='&';
+xchr[@'47]:='''';@/
+xchr[@'50]:='(';
+xchr[@'51]:=')';
+xchr[@'52]:='*';
+xchr[@'53]:='+';
+xchr[@'54]:=',';
+xchr[@'55]:='-';
+xchr[@'56]:='.';
+xchr[@'57]:='/';@/
+xchr[@'60]:='0';
+xchr[@'61]:='1';
+xchr[@'62]:='2';
+xchr[@'63]:='3';
+xchr[@'64]:='4';
+xchr[@'65]:='5';
+xchr[@'66]:='6';
+xchr[@'67]:='7';@/
+xchr[@'70]:='8';
+xchr[@'71]:='9';
+xchr[@'72]:=':';
+xchr[@'73]:=';';
+xchr[@'74]:='<';
+xchr[@'75]:='=';
+xchr[@'76]:='>';
+xchr[@'77]:='?';@/
+xchr[@'100]:='@@';
+xchr[@'101]:='A';
+xchr[@'102]:='B';
+xchr[@'103]:='C';
+xchr[@'104]:='D';
+xchr[@'105]:='E';
+xchr[@'106]:='F';
+xchr[@'107]:='G';@/
+xchr[@'110]:='H';
+xchr[@'111]:='I';
+xchr[@'112]:='J';
+xchr[@'113]:='K';
+xchr[@'114]:='L';
+xchr[@'115]:='M';
+xchr[@'116]:='N';
+xchr[@'117]:='O';@/
+xchr[@'120]:='P';
+xchr[@'121]:='Q';
+xchr[@'122]:='R';
+xchr[@'123]:='S';
+xchr[@'124]:='T';
+xchr[@'125]:='U';
+xchr[@'126]:='V';
+xchr[@'127]:='W';@/
+xchr[@'130]:='X';
+xchr[@'131]:='Y';
+xchr[@'132]:='Z';
+xchr[@'133]:='[';
+xchr[@'134]:='\';
+xchr[@'135]:=']';
+xchr[@'136]:='^';
+xchr[@'137]:='_';@/
+xchr[@'140]:='`';
+xchr[@'141]:='a';
+xchr[@'142]:='b';
+xchr[@'143]:='c';
+xchr[@'144]:='d';
+xchr[@'145]:='e';
+xchr[@'146]:='f';
+xchr[@'147]:='g';@/
+xchr[@'150]:='h';
+xchr[@'151]:='i';
+xchr[@'152]:='j';
+xchr[@'153]:='k';
+xchr[@'154]:='l';
+xchr[@'155]:='m';
+xchr[@'156]:='n';
+xchr[@'157]:='o';@/
+xchr[@'160]:='p';
+xchr[@'161]:='q';
+xchr[@'162]:='r';
+xchr[@'163]:='s';
+xchr[@'164]:='t';
+xchr[@'165]:='u';
+xchr[@'166]:='v';
+xchr[@'167]:='w';@/
+xchr[@'170]:='x';
+xchr[@'171]:='y';
+xchr[@'172]:='z';
+xchr[@'173]:='{';
+xchr[@'174]:='|';
+xchr[@'175]:='}';
+xchr[@'176]:='~';
+for i:=@'177 to 255 do xchr[i]:='?';
+
+@* Device-independent file format.
+The format of \.{DVI} files is described in many places including
+\.{dvitype.web} and Volume~B of D.~E. Knuth's {\sl Computers and Typesetting}.
+This program refers to the following command codes.
+
+@d id_byte=2 {identifies the kind of \.{DVI} files described here}
+@#
+@d set_char_0=0 {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 bop=139 {beginning of page}
+@d eop=140 {ending of page}
+@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 post_post=249 {postamble ending}
+@d undefined_commands==250,251,252,253,254,255
+
+@* Input from binary files.
+We have seen that a \.{DVI} file is a sequence of 8-bit bytes. The bytes
+appear physically in what is called a `|packed file of 0..255|'
+in \PASCAL\ lingo.
+
+Packing is system dependent, and many \PASCAL\ systems fail to implement
+such files in a sensible way (at least, from the viewpoint of producing
+good production software). For example, some systems treat all
+byte-oriented files as text, looking for end-of-line marks and such
+things. Therefore some system-dependent code is often needed to deal with
+binary files, even though most of the program in this section of
+\.{DVItoMP} is written in standard \PASCAL.
+@^system dependencies@>
+
+One common way to solve the problem is to consider files of |integer|
+numbers, and to convert an integer in the range $-2^{31}\L x<2^{31}$ to
+a sequence of four bytes $(a,b,c,d)$ using the following code, which
+avoids the controversial integer division of negative numbers:
+$$\vbox{\halign{#\hfil\cr
+|if x>=0 then a:=x div @'100000000|\cr
+|else begin x:=(x+@'10000000000)+@'10000000000; a:=x div @'100000000+128;|\cr
+\quad|end|\cr
+|x:=x mod @'100000000;|\cr
+|b:=x div @'200000; x:=x mod @'200000;|\cr
+|c:=x div @'400; d:=x mod @'400;|\cr}}$$
+The four bytes are then kept in a buffer and output one by one. (On 36-bit
+computers, an additional division by 16 is necessary at the beginning.
+Another way to separate an integer into four bytes is to use/abuse
+\PASCAL's variant records, storing an integer and retrieving bytes that are
+packed in the same place; {\sl caveat implementor!\/}) It is also desirable
+in some cases to read a hundred or so integers at a time, maintaining a
+larger buffer.
+
+We shall stick to simple \PASCAL\ in this program, for reasons of clarity,
+even if such simplicity is sometimes unrealistic.
+
+@<Types...@>=
+@!eight_bits=0..255; {unsigned one-byte quantity}
+@!byte_file=packed file of eight_bits; {files that contain binary data}
+
+@ The program deals with two binary file variables: |dvi_file| is the main
+input file that we are translating into symbolic form, and |tfm_file| is
+the current font metric file from which character-width information is
+being read. It is convenient to have a throw-away variable for function
+results when reading parts of the files that are being skipped.
+
+@<Glob...@>=
+@!dvi_file:byte_file; {the input file}
+@!tfm_file:byte_file; {a font metric file}
+@!vf_file:byte_file; {a virtual font file}
+@!down_the_drain:integer; {a ``write-only'' variable}
+
+@ To prepare these files for input, we |reset| them. An extension of
+\PASCAL\ is needed in the case of |tfm_file|, since we want to associate
+it with external files whose names are specified dynamically (i.e., not
+known at compile time). The following code assumes that `|reset(f,s)|'
+does this, when |f| is a file variable and |s| is a string variable that
+specifies the file name. If |eof(f)| is true immediately after |reset(f,s)|
+has acted, these routines assume that no file named |s| is accessible.
+@^system dependencies@>
+
+@p procedure open_dvi_file; {prepares to read packed bytes in |dvi_file|}
+begin reset(dvi_file);
+if eof(dvi_file) then abort('DVI file not found');
+end;
+@#
+function open_tfm_file:boolean; {prepares to read packed bytes in |tfm_file|}
+begin reset(tfm_file,cur_name);
+open_tfm_file:=(not eof(tfm_file));
+end;
+@#
+function open_vf_file:boolean; {prepares to read packed bytes in |vf_file|}
+begin reset(vf_file,cur_name);
+open_vf_file:=(not eof(vf_file));
+end;
+
+@ If you looked carefully at the preceding code, you probably asked,
+``What is |cur_name|?'' Good question. It's a global
+variable: |cur_name| is a string variable that will be set to the
+current font metric file name before |open_tfm_file| is called.
+
+@<Glob...@>=
+@!cur_name:packed array[1..name_length] of char; {external name,
+ with no lower case letters}
+
+@ It turns out to be convenient to read four bytes at a time, when we are
+inputting from \.{TFM} files. The input goes into global variables
+|b0|, |b1|, |b2|, and |b3|, with |b0| getting the first byte and |b3|
+the fourth.
+
+@<Glob...@>=
+@!b0,@!b1,@!b2,@!b3: eight_bits; {four bytes input at once}
+
+@ The |read_tfm_word| procedure sets |b0| through |b3| to the next
+four bytes in the current \.{TFM} file.
+@^system dependencies@>
+
+@p procedure read_tfm_word;
+begin read(tfm_file,b0); read(tfm_file,b1);
+read(tfm_file,b2); read(tfm_file,b3);
+end;
+
+@ Input can come from from three different sources depending on the settings
+of global variables. When |vf_reading| is true, we read from the \.{VF} file.
+Otherwise, input can either come directly from |dvi_file| or from a buffer
+|cmd_buf|. The latter case applies whenever |buf_ptr<virtual_space|.
+
+@<Glob...@>=
+@!vf_reading:boolean; {should input come from |vf_file|?}
+@!cmd_buf:packed array [0..virtual_space] of quarter_word;
+ {commands for virtual characters}
+@!buf_ptr:0..virtual_space; {|cmd_buf| index for the next byte}
+
+@ @<Set init...@>=
+vf_reading:=false; buf_ptr:=virtual_space;
+
+@ It is probably not critical that |cmd_buf| be packed as efficiently as possible,
+but we define a new type just in case it is necessary to for |cmd_buf| entries
+to be in the range |-128..127|.
+@^system dependencies@>
+
+@d qi(#)==# {convert from |eight_bits| to |quarter_word|}
+@d qo(#)==# {convert from |quarter_word| to |eight_bits|}
+
+@<Types...@>=
+quarter_word=0..255; {a one byte quantity as stored in |cmd_buf|}
+
+@ We shall use a set of simple functions to read the next byte or bytes from the
+current input source. There are seven possibilities, each of which is treated
+as a separate function in order to minimize the overhead for subroutine calls.
+
+@p function get_byte:integer; {returns the next byte, unsigned}
+var b:eight_bits;
+begin @<Read one byte into |b|@>;
+get_byte:=b;
+end;
+@#
+function signed_byte:integer; {returns the next byte, signed}
+var b:eight_bits;
+begin @<Read one byte into |b|@>;
+if b<128 then signed_byte:=b @+ else signed_byte:=b-256;
+end;
+@#
+function get_two_bytes:integer; {returns the next two bytes, unsigned}
+var a,@!b:eight_bits;
+begin @<Read two bytes into |a| and |b|@>;
+get_two_bytes:=a*256+b;
+end;
+@#
+function signed_pair:integer; {returns the next two bytes, signed}
+var a,@!b:eight_bits;
+begin @<Read two bytes into |a| and |b|@>;
+if a<128 then signed_pair:=a*256+b
+else signed_pair:=(a-256)*256+b;
+end;
+@#
+function get_three_bytes:integer; {returns the next three bytes, unsigned}
+var a,@!b,@!c:eight_bits;
+begin @<Read three bytes into |a|, |b|, and~|c|@>;
+get_three_bytes:=(a*256+b)*256+c;
+end;
+@#
+function signed_trio:integer; {returns the next three bytes, signed}
+var a,@!b,@!c:eight_bits;
+begin @<Read three bytes into |a|, |b|, and~|c|@>;
+if a<128 then signed_trio:=(a*256+b)*256+c
+else signed_trio:=((a-256)*256+b)*256+c;
+end;
+@#
+function signed_quad:integer; {returns the next four bytes, signed}
+var a,@!b,@!c,@!d:eight_bits;
+begin @<Read four bytes into |a|, |b|, |c|, and~|d|@>;
+if a<128 then signed_quad:=((a*256+b)*256+c)*256+d
+else signed_quad:=(((a-256)*256+b)*256+c)*256+d;
+end;
+
+@ @<Read one byte into |b|@>=
+if vf_reading then read(vf_file,b)
+else if buf_ptr=virtual_space then read(dvi_file,b)
+else begin b:=qo(cmd_buf[buf_ptr]);
+ incr(buf_ptr);
+ end
+
+@ @<Read two bytes into |a| and |b|@>=
+if vf_reading then
+ begin read(vf_file,a); read(vf_file,b);
+ end
+else if buf_ptr=virtual_space then
+ begin read(dvi_file,a); read(dvi_file,b);
+ end
+else if buf_ptr+2>n_cmds then
+ abort('Error detected while interpreting a virtual font')
+@.Error detected while...@>
+else begin a:=qo(cmd_buf[buf_ptr]);
+ b:=qo(cmd_buf[buf_ptr+1]);
+ buf_ptr:=buf_ptr+2;
+ end
+
+@ @<Read three bytes into |a|, |b|, and~|c|@>=
+if vf_reading then
+ begin read(vf_file,a); read(vf_file,b); read(vf_file,c);
+ end
+else if buf_ptr=virtual_space then
+ begin read(dvi_file,a); read(dvi_file,b); read(dvi_file,c);
+ end
+else if buf_ptr+3>n_cmds then
+ abort('Error detected while interpreting a virtual font')
+@.Error detected while...@>
+else begin a:=qo(cmd_buf[buf_ptr]);
+ b:=qo(cmd_buf[buf_ptr+1]);
+ c:=qo(cmd_buf[buf_ptr+2]);
+ buf_ptr:=buf_ptr+3;
+ end
+
+@ @<Read four bytes into |a|, |b|, |c|, and~|d|@>=
+if vf_reading then
+ begin read(vf_file,a); read(vf_file,b); read(vf_file,c); read(vf_file,d);
+ end
+else if buf_ptr=virtual_space then
+ begin read(dvi_file,a); read(dvi_file,b); read(dvi_file,c); read(dvi_file,d);
+ end
+else if buf_ptr+4>n_cmds then
+ abort('Error detected while interpreting a virtual font')
+@.Error detected while...@>
+else begin a:=qo(cmd_buf[buf_ptr]);
+ b:=qo(cmd_buf[buf_ptr+1]);
+ c:=qo(cmd_buf[buf_ptr+2]);
+ d:=qo(cmd_buf[buf_ptr+3]);
+ buf_ptr:=buf_ptr+4;
+ end
+
+@* Data structures for fonts.
+\.{DVI} file format does not include information about character widths, since
+that would tend to make the files a lot longer. But a program that reads
+a \.{DVI} file is supposed to know the widths of the characters that appear
+in \\{set\_char} commands. Therefore \.{DVItoMP} looks at the font metric
+(\.{TFM}) files for the fonts that are involved.
+@.TFM {\rm files}@>
+
+@ For purposes of this program, the only thing we need to know about a
+given character |c| in a non-virtual font |f| is the width. For the font as
+a whole, all we need is the symbolic name to use in the \.{MPX} file.
+
+This information appears implicitly in the following data
+structures. The current number of fonts defined is |nf|. Each such font has
+an internal number |f|, where |0<=f<nf|. There is also an external number
+that identifies the font in the \.{DVI} file. The correspondence is
+maintained in arrays |font_num| and |internal_num| so that |font_num[i]|
+is the external number for |f=internal_num[i]|.
+The external name of this font is the string that
+occupies positions |font_name[f]| through |font_name[f+1]-1| of the array
+|names|. The latter array consists of |ASCII_code| characters, and
+|font_name[nf]| is its first unoccupied position. The
+legal characters run from |font_bc[f]| to |font_ec[f]|, inclusive.
+The \.{TFM} file can specify that some of these are invalid, but this doesn't
+concern \.{DVItoMP} because it does not do extensive error checking.
+The width of character~|c| in font~|f| is given by
+|char_width(f)(c)=width[info_base[f]+c]|, and |info_ptr| is the
+first unused position of the |width| array.
+
+If font~|f| is a virtual font, there is a list of \.{DVI} commands for each
+character. These occupy consecutive positions in the |cmd_buf| array with
+the commands for character~|c| starting at
+|start_cmd(f)(c)=cmd_ptr[info_base[f]+c]| and ending just before
+|start_cmd(f)(c+1)|. Font numbers used when interpreting these \.{DVI}
+commands occupy positions |fbase[f]| through |ftop[f]-1| in the |font_num|
+table and the |internal_num| array gives the corresponding internal font
+numbers. If such an internal font number~|i| does not correspond to
+some font occuring in the \.{DVI} file, then |font_num[i]| has not been
+assigned a meaningful value; this is indicated by |local_only[i]=true|.
+
+If font~|f| is not virtual, then |fbase[f]=0| and |ftop[f]=0|. The |start_cmd|
+values are ignored in this case.
+
+@d char_width_end(#)==#]
+@d char_width(#)==width[info_base[#]+char_width_end
+@d start_cmd(#)==cmd_ptr[info_base[#]+start_cmd_end
+@d start_cmd_end(#)==#]
+
+@<Glob...@>=
+@!font_num:array [0..max_fnums] of integer; {external font numbers}
+@!internal_num:array [0..max_fnums] of integer; {internal font numbers}
+@!local_only:array [0..max_fonts] of boolean; {|font_num| meaningless?}
+@!font_name:array [0..max_fonts] of 0..name_size; {starting positions
+ of external font names}
+@!names:array [0..name_size] of ASCII_code; {characters of names}
+@!area_length:array [0..max_fonts] of integer;
+ {length of area part of font name}
+@!font_scaled_size:array [0..max_fonts] of real; {scale factors over $2^{20}$}
+@!font_design_size:array [0..max_fonts] of real; {design sizes over $2^{20}$}
+@!font_check_sum:array [0..max_fonts] of integer;
+ {check sum from the |font_def|}
+@!font_bc:array [0..max_fonts] of integer; {beginning characters in fonts}
+@!font_ec:array [0..max_fonts] of integer; {ending characters in fonts}
+@!info_base:array [0..max_fonts] of integer;
+ {index into |width| and |cmd_ptr| tables}
+@!width:array [0..max_widths] of integer;
+ {character widths, in units $2^{-20}$ of design size}
+@!fbase:array [0..max_fonts] of integer; {index into |font_num| for local fonts}
+@!ftop:array [0..max_fonts] of integer;
+ {|font_num| index where local fonts stop}
+@!cmd_ptr:array [0..max_widths] of integer; {starting positions in |cmd_buf|}
+@!nf:0..max_fonts; {the number of known fonts}
+@!vf_ptr:max_fonts..max_fnums;
+ {next |font_num| entry for virtual font font tables}
+@!info_ptr:0..max_widths; {allocation pointer for |width| and |cmd_ptr| tables}
+@!n_cmds:0..virtual_space; {number of occupied cells in |cmd_buf|}
+@!cur_fbase,@!cur_ftop:0..max_fnums;
+ {currently applicable part of the |font_num| table}
+
+@ @<Set init...@>=
+nf:=0; info_ptr:=0; font_name[0]:=0;
+vf_ptr:=max_fnums;
+cur_fbase:=0; cur_ftop:=0;
+
+@ Printing the name of a given font is easy except that a procedure |print_char|
+is needed to actually send an |ASCII_code| to the \.{MPX} file.
+
+@p @<Declare subroutines for printing strings@>@;
+procedure print_font(@!f:integer); {|f| is an internal font number}
+var k:0..name_size; {index into |names|}
+begin if (f<0)or(f>=nf) then bad_dvi('Undefined font')
+else begin for k:=font_name[f] to font_name[f+1]-1 do
+ print_char(names[k]);
+ end;
+end;
+
+@ Sometimes a font name is needed as part of an error message.
+
+@d font_warn(#)==begin err_print('DVItoMP warning: ',#); font_warn_end
+@d font_warn_end(#)==err_print_font(#);
+ history:=warning_given;
+ end
+@d font_abort(#)==begin err_print('DVItoMP abort: ',#); font_abort_end
+@d font_abort_end(#)==err_print_font(#);
+ history:=fatal_error; jump_out;
+ end
+
+@p procedure err_print_font(@!f:integer); {|f| is an internal font number}
+var k:0..name_size; {index into |names|}
+begin for k:=font_name[f] to font_name[f+1]-1 do
+ err_print(xchr[names[k]]);
+ err_print_ln(' ');
+end;
+
+@ When we encounter a font definition, we save the name, checksum, and size
+information, but we don't actaully read the \.{TFM} or \.{VF} file until we
+are about to use the font. If a matching font is not already defined, we then
+allocate a new internal font number.
+
+The following subroutine does the necessary things when a \\{fnt\_def} command
+is encountered in the \.{DVI} file or in a \.{VF} file. It assumes that the
+first argument has already been parsed and is given by the parameter~|e|.
+
+@p @<Declare a function called |match_font|@>@;
+procedure define_font(@!e:integer); {|e| is an external font number}
+var @!i:integer; {index into |font_num| and |internal_num|}
+@!n:integer; {length of the font name and area}
+@!k:integer; {general purpose loop counter}
+@!x:integer; {a temporary value for scaled size computation}
+begin if nf=max_fonts then abort('DVItoMP capacity exceeded (max fonts=',
+ max_fonts:1,')!');
+@.DVItoMP capacity exceeded...@>
+@<Allocate an index |i| into the |font_num| and |internal_num| tables@>;
+@<Read the font parameters into position for font |nf|@>;
+internal_num[i]:=match_font(nf,true);
+if internal_num[i]=nf then
+ begin info_base[nf]:=max_widths; {indicate that the info isn't loaded yet}
+ local_only[nf]:=vf_reading; incr(nf);
+ end;
+end;
+
+@ @<Allocate an index |i| into the |font_num| and |internal_num| tables@>=
+if vf_ptr=nf then abort('DVItoMP capacity exceeded (max font numbers=',
+ max_fnums:1,')');
+@.DVItoMP capacity exceeded...@>
+if vf_reading then
+ begin font_num[nf]:=0; i:=vf_ptr; decr(vf_ptr);
+ end
+else i:=nf;
+font_num[i]:=e
+
+@ @<Read the font parameters into position for font |nf|@>=
+font_check_sum[nf]:=signed_quad;
+@<Read |font_scaled_size[nf]| and |font_design_size[nf]|@>;
+n:=get_byte; area_length[nf]:=n;
+n:=n+get_byte;
+if font_name[nf]+n>name_size then
+ abort('DVItoMP capacity exceeded (name size=',name_size:1,')!');
+@.DVItoMP capacity exceeded...@>
+font_name[nf+1]:=font_name[nf]+n;
+for k:=font_name[nf] to font_name[nf+1]-1 do names[k]:=get_byte
+
+@ The scaled size and design size are stored in \.{DVI} units divided by $2^20$.
+The units for scaled size are a little different if we are reading a virtual
+font, but this will be corrected when the scaled size is used. The scaled size
+also needs to be truncated to at most 23 significant bits in order to make
+the character width calculation match what \TeX\ does.
+
+@<Read |font_scaled_size[nf]| and |font_design_size[nf]|@>=
+x:=signed_quad;
+k:=1;
+while x>@'40000000 do
+ begin x:=x div 2; k:=k+k;
+ end;
+font_scaled_size[nf]:=x*k/1048576.0;
+if vf_reading then
+ font_design_size[nf]:=signed_quad*dvi_per_fix/1048576.0
+else font_design_size[nf]:=signed_quad/1048576.0;
+
+@ @<Glob...@>=
+@!dvi_per_fix:real; {converts points scaled $2^{20}$ to \.{DVI} units}
+
+@ The |match_font| function tries to find a match for the font with internal
+number~|ff|, returning |nf| or the number of the matching font. If
+|exact=true|, the name and scaled size should match. Otherwise the scaled
+size need not match but the font found must be already loaded, not just
+defined.
+
+@<Declare a function called |match_font|@>=
+function match_font(ff:integer; exact:boolean):integer;
+label done, 99;
+var @!f:0..max_fonts; {font number being tested}
+@!ss,@!ll:0..name_size; {starting point and length of name of font |ff|}
+@!k,@!s:0..name_size; {registers for comparing font names}
+begin ss:=font_name[ff]; ll:=font_name[ff+1]-ss;
+f:=0;
+while f<nf do
+ begin if f<>ff then
+ begin @<Compare the names of fonts |f| and |ff|; |goto 99| if they differ@>;
+ if exact then
+ begin if abs(font_scaled_size[f]-font_scaled_size[ff])
+ <= font_tolerance then
+ begin if not vf_reading then
+ if local_only[f] then
+ begin font_num[f]:=font_num[ff]; local_only[f]:=false;
+ end
+ else if font_num[f]<>font_num[ff] then goto 99;
+ goto done;
+ end;
+ end
+ else if info_base[f]<>max_widths then goto done;
+ end;
+ 99:incr(f);
+ end;
+done:if f<nf then
+ @<Make sure fonts |f| and |ff| have matching design sizes and checksums@>;
+match_font:=f;
+end;
+
+@ @<Compare the names of fonts |f| and |ff|; |goto 99| if they differ@>=
+if (area_length[f]<area_length[ff]) or (ll<>font_name[f+1]-font_name[f]) then
+ goto 99;
+s:=font_name[f];
+k:=ll;
+while k>0 do
+ begin decr(k);
+ if names[s+k]<>names[ss+k] then goto 99;
+ end
+
+@ @<Make sure fonts |f| and |ff| have matching design sizes and checksums@>=
+if abs(font_design_size[f]-font_design_size[ff]) > font_tolerance then
+ font_warn('Inconsistent design sizes given for ')(ff)
+@.Inconsistent design sizes@>
+else if font_check_sum[f]<>font_check_sum[ff] then
+ font_warn('Checksum mismatch for ')(ff)
+@.Checksum mismatch@>
+
+@* Reading ordinary fonts.
+An auxiliary array |in_width| is used to hold the widths as they are
+input. The global variable |tfm_check_sum| is set to the check sum that
+appears in the current \.{TFM} file.
+
+@<Glob...@>=
+@!in_width:array[0..255] of integer; {\.{TFM} width data in \.{DVI} units}
+@!tfm_check_sum:integer; {check sum found in |tfm_file|}
+
+@ Here is a procedure that absorbs the necessary information from a
+\.{TFM} file, assuming that the file has just been successfully reset
+so that we are ready to read its first byte. (A complete description of
+\.{TFM} file format appears in the documentation of \.{TFtoPL} and will
+not be repeated here.) The procedure does not check the \.{TFM} file
+for validity, nor does it give explicit information about what is
+wrong with a \.{TFM} file that proves to be invalid. The procedure simply
+aborts the program if it detects anything amiss in the \.{TFM} data.
+
+@p procedure in_TFM(@!f:integer);
+ {input \.{TFM} data for font |f| or abort}
+label 9997, {go here when the format is bad}
+ 9999; {go here to exit}
+var k:integer; {index for loops}
+@!lh:integer; {length of the header data, in four-byte words}
+@!nw:integer; {number of words in the width table}
+@!wp:0..max_widths; {new value of |info_ptr| after successful input}
+begin @<Read past the header data; |goto 9997| if there is a problem@>;
+@<Store character-width indices at the end of the |width| table@>;
+@<Read the width values into the |in_width| table@>;
+@<Move the widths from |in_width| to |width|@>;
+fbase[f]:=0; ftop[f]:=0;
+info_ptr:=wp; goto 9999;
+9997: font_abort('Bad TFM file for ')(f);
+@.Bad TFM file@>
+9999: end;
+
+@ @<Read past the header...@>=
+read_tfm_word; lh:=b2*256+b3;
+read_tfm_word; font_bc[f]:=b0*256+b1; font_ec[f]:=b2*256+b3;
+if font_ec[f]<font_bc[f] then font_bc[f]:=font_ec[f]+1;
+if info_ptr+font_ec[f]-font_bc[f]+1>max_widths then
+ abort('DVItoMP capacity exceeded (width table size=',max_widths:1,')!');
+@.DVItoMP capacity exceeded...@>
+wp:=info_ptr+font_ec[f]-font_bc[f]+1;
+read_tfm_word; nw:=b0*256+b1;
+if (nw=0)or(nw>256) then goto 9997;
+for k:=1 to 3+lh do
+ begin if eof(tfm_file) then goto 9997;
+ read_tfm_word;
+ if k=4 then
+ if b0<128 then tfm_check_sum:=((b0*256+b1)*256+b2)*256+b3
+ else tfm_check_sum:=(((b0-256)*256+b1)*256+b2)*256+b3;
+ end;
+
+@ @<Store character-width indices...@>=
+if wp>0 then for k:=info_ptr to wp-1 do
+ begin read_tfm_word;
+ if b0>nw then goto 9997;
+ width[k]:=b0;
+ end;
+
+@ No fancy width calculation is needed here because \.{DVItoMP} stores
+widths in their raw form as multiples of the design size scaled by $2^{20}$.
+The |font_scaled_size| entries have been computed so that the final width
+compution can be done in floating point if enough precision is available.
+
+@<Read the width values into the |in_width| table@>=
+for k:=0 to nw-1 do
+ begin read_tfm_word;
+ if b0>127 then b0:=b0-256;
+ in_width[k]:=((b0*@'400+b1)*@'400+b2)*@'400+b3;
+ end
+
+@ The width compution uses a scale factor |dvi_scale| that will be introduced
+later. It is equal to one when not typesetting a character from a virtual
+font. In that case, the following expressions do the width computation that is
+so important in \.{DVItype}. It is less important here because it is impractical
+to guarantee precise character positioning in \MP\ output. Nevertheless, the
+width compution will be precise if reals have at least 46-bit mantissas and
+|round(x-.5)| is equivalent to $\lfloor x\rfloor$. It may be a good idea to
+modify this computation if these conditions are not met.
+@^system dependencies@>
+
+@<Width of character |c| in font |f|@>=
+round(dvi_scale*font_scaled_size[f]*char_width(f)(c)-0.5)
+
+@ @<Width of character |p| in font |cur_font|@>=
+round(dvi_scale*font_scaled_size[cur_font]*char_width(cur_font)(p)-0.5)
+
+@ @<Move the widths from |in_width| to |width|@>=
+if in_width[0]<>0 then goto 9997; {the first width should be zero}
+info_base[f]:=info_ptr-font_bc[f];
+if wp>0 then for k:=info_ptr to wp-1 do
+ width[k]:=in_width[width[k]]
+
+
+@* Reading virtual fonts.
+The |in_VF| procedure absorbs the necessary information from a \.{VF} file that
+has just been reset so that we are ready to read the first byte. (A complete
+description of \.{VF} file format appears in the documention of \.{VFtoVP}).
+Like |in_TFM|, this procedure simply aborts the program if it detects anything
+wrong with the \.{VF} file.
+
+@p @<Declare a function called |first_par|@>@;
+procedure in_VF(f:integer);
+ {read \.{VF} data for font |f| or abort}
+label 9997, {go here to abort}
+ 9999; {go here to exit}
+var @!p:integer; {a byte from the \.{VF} file}
+@!was_vf_reading:boolean; {old value of |vf_reading|}
+@!c:integer; {the current character code}
+@!limit:integer; {space limitations force character codes to be less than this}
+@!w:integer; {a \.{TFM} width being read}
+begin was_vf_reading:=vf_reading; vf_reading:=true;
+@<Start reading the preamble from a \.{VF} file@>;@/
+@<Initialize the data structures for the virtual font@>;@/
+p:=get_byte;
+while p>=fnt_def1 do
+ begin if p>fnt_def1+3 then goto 9997;
+ define_font(first_par(p));
+ p:=get_byte;
+ end;
+while p<=242 do
+ begin if eof(vf_file) then goto 9997;
+ @<Read the packet length, character code, and \.{TFM} width@>;
+ @<Store the character packet in |cmd_buf|@>;
+ p:=get_byte;
+ end;
+if p=post then
+ begin @<Finish setting up the data structures for the new virtual font@>;
+ goto 9999;
+ end;
+9997:font_abort('Bad VF file for ')(f);
+9999: vf_reading:=was_vf_reading;
+end;
+
+@ @<Start reading the preamble from a \.{VF} file@>=
+p:=get_byte;
+if p<>pre then goto 9997;
+p:=get_byte; {fetch the identification byte}
+if p<>202 then goto 9997;
+p:=get_byte; {fetch the length of the introductory comment}
+while p>0 do
+ begin decr(p); down_the_drain:=get_byte;
+ end;
+tfm_check_sum:=signed_quad;
+down_the_drain:=signed_quad; {skip over the design size}
+
+@ @<Initialize the data structures for the virtual font@>=
+ftop[f]:=vf_ptr;
+if vf_ptr=nf then abort('DVItoMP capacity exceeded (max font numbers=',
+ max_fnums:1,')');
+@.DVItoMP capacity exceeded...@>
+decr(vf_ptr);
+info_base[f]:=info_ptr;
+limit:=max_widths-info_base[f];@/
+font_bc[f]:=limit; font_ec[f]:=0
+
+@ @<Read the packet length, character code, and \.{TFM} width@>=
+if p=242 then
+ begin p:=signed_quad; c:=signed_quad; w:=signed_quad;
+ if c<0 then goto 9997;
+ end
+else begin c:=get_byte; w:=get_three_bytes;
+ end;
+if c>=limit then
+ abort('DVItoMP capacity exceeded (max widths=', max_widths:1,')!');
+@.DVItoMP capacity exceeded...@>
+if c<font_bc[f] then font_bc[f]:=c;
+if c>font_ec[f] then font_ec[f]:=c;
+char_width(f)(c):=w
+
+@ @<Store the character packet in |cmd_buf|@>=
+if n_cmds+p>=virtual_space then
+ abort('DVItoMP capacity exceeded (virtual font space=',virtual_space:1,')!');
+@.DVItoMP capacity exceeded...@>
+start_cmd(f)(c):=n_cmds;
+while p>0 do
+ begin cmd_buf[n_cmds]:=qi(get_byte);
+ incr(n_cmds); decr(p);
+ end;
+cmd_buf[n_cmds]:=qi(eop); {add the end-of-packet marker}
+incr(n_cmds)
+
+@ There are unused |width| and |cmd_ptr| entries if |font_bc[f]>0| but it isn't
+worthwhile to slide everything down just to save a little space.
+
+@<Finish setting up the data structures for the new virtual font@>=
+fbase[f]:=vf_ptr+1;
+info_ptr:=info_base[f]+font_ec[f]
+
+
+@* Loading fonts.
+The character width information for a font is loaded when the font is selected
+for the first time. This information might already be loaded if the font has
+already been used at a different scale factor. Otherwise, we look for a \.{VF}
+file, or failing that, a \.{TFM} file. All this is done by the |select_font|
+function that takes an external font number~|e| and returns the corresponding
+internal font number with the width information loaded.
+
+@p function select_font(@!e:integer):integer;
+var @!f:0..max_fonts; {the internal font number}
+@!ff:0..max_fonts; {internal font number for an existing version}
+@!k,@!l:integer; {general purpose loop counters}
+begin @<Set |f| to the internal font number that corresponds to |e|,
+ or |abort| if there is none@>;
+if info_base[f]=max_widths then
+ begin ff:=match_font(f,false);
+ if ff<nf then @<Make font |f| refer to the width information from font |ff|@>
+ else begin @<Move the \.{VF} file name into the |cur_name| string@>;
+ if open_vf_file then in_VF(f)
+ else begin @<Change \.{.VF} to \.{.TFM} in the |cur_name| string@>;
+ if not open_tfm_file then font_abort('No TFM file found for ')(f);
+ @.no TFM file found@>
+ in_TFM(f);
+ end;
+ @<Make sure the checksum in the font file matches the one given in the
+ |font_def| for font |f|@>;
+ end;
+ @<Do any other initialization required for the new font |f|@>;
+ end;
+select_font:=f;
+end;
+
+@ @<Set |f| to the internal font number that corresponds to |e|,...@>=
+if cur_ftop<=nf then cur_ftop:=nf;
+font_num[cur_ftop]:=e;
+k:=cur_fbase;
+while (font_num[k]<>e)or local_only[k] do incr(k);
+if k=cur_ftop then abort('Undefined font selected');
+f:=internal_num[k]
+
+@ @<Make font |f| refer to the width information from font |ff|@>=
+begin font_bc[f]:=font_bc[ff];
+font_ec[f]:=font_ec[ff];
+info_base[f]:=info_base[ff];
+fbase[f]:=fbase[ff];
+ftop[f]:=ftop[ff];
+end
+
+@ If |area_length[f]=0|, i.e., if no font directory has been specified,
+\.{DVItoMP} 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
+\.{VF} file for the current font. This usually means that we need to
+prepend the name of the default directory, and
+to append the suffix `\.{.VF}'. Furthermore, we change lower case letters
+to upper case, since |cur_name| is a \PASCAL\ string.
+@^system dependencies@>
+
+@<Move the \.{VF} file name into the |cur_name| string@>=
+for k:=1 to name_length do cur_name[k]:=' ';
+if area_length[f]=0 then
+ begin for k:=1 to default_directory_name_length do
+ cur_name[k]:=default_directory[k];
+ l:=default_directory_name_length;
+ end
+else l:=0;
+for k:=font_name[f] to font_name[f+1]-1 do
+ begin incr(l);
+ if l+3>name_length then
+ abort('DVItoMP capacity exceeded (max font name length=',
+ name_length:1,')!');
+@.DVItoMP capacity exceeded...@>
+ if (names[k]>="a")and(names[k]<="z") then
+ cur_name[l]:=xchr[names[k]-@'40]
+ else cur_name[l]:=xchr[names[k]];
+ end;
+cur_name[l+1]:='.'; cur_name[l+2]:='V'; cur_name[l+3]:='F'
+
+@ It is fairly simple to change a \.{VF} file name to a \.{TFM} file name.
+@^system dependencies@>
+
+@<Change \.{.VF} to \.{.TFM} in the |cur_name| string@>=
+l:=area_length[f];
+if l=0 then l:=default_directory_name_length;
+l:=l+font_name[f+1]-font_name[f];
+if l+4>name_length then
+ abort('DVItoMP capacity exceeded (max font name length=',
+ name_length:1,')!');
+@.DVItoMP capacity exceeded...@>
+cur_name[l+2]:='T'; cur_name[l+3]:='F'; cur_name[l+4]:='M'
+
+@ @<Make sure the checksum in the font file matches the one given in the...@>=
+begin if (font_check_sum[f]<>0)and(tfm_check_sum<>0)and@|
+ (font_check_sum[f]<>tfm_check_sum) then
+ begin err_print('DVItoMP warning: Checksum mismatch for ');
+@.Checksum mismatch@>
+ err_print_font(f);
+ if history=spotless then history:=cksum_trouble;
+ end;
+end
+
+@* Low level output routines.
+One of the basic output operations is to write a \MP\ string expression for
+a sequence of characters to be typeset. The main difficulties are that such
+strings can contain arbitrary eight-bit bytes and there is no fixed limit on
+the length of the string that needs to be produced. In extreme cases this
+can lead to expressions such as
+$$\vcenter{
+ \hbox{\.{char7\&char15\&char31\&"?FWayzz"}}
+ \hbox{\.{\&"zzaF"\&char15\&char3\&char31}}
+ \hbox{\.{\&"Nxzzzzzzzwvtsqo"}}}
+$$
+
+@ A global variable |state| keeps track of the output process.
+When |state=normal| we have begun a quoted string and the next character
+should be a printable character or a closing quote. When |state=special|
+the last thing printed was a ``\.{char}'' construction or a closing quote
+and an ampersand should come next. The starting condition |state=initial|
+is a lot like |state=special|, except no ampersand is required.
+
+@d special=0 {the |state| after printing a ``\.{char}'' expression}
+@d normal=1 {the |state| value in a quoted string}
+@d initial=2 {initial |state|}
+
+@<Glob...@>=
+state:special..initial; {controls the process of printing a string}
+print_col:0..line_length;
+ {there are at most this many characters on the current line}
+
+@ To print a string on the \.{MPX} file, initialize |print_col|, ensure that
+|state=initial|, and pass the characters one-at-a-time to |print_char|.
+
+@<Declare subroutines for printing strings@>=
+procedure print_char(@!c:eight_bits);
+var @!printable:boolean; {is it safe to print |xchr[c]|?}
+@!l:integer; {number of characters to print |c| or the \.{char} expression}
+begin printable:=(c>=" ")and(c<="~")and(c<>"""");
+if printable then l:=1
+else if c<10 then l:=5
+else if c<100 then l:=6
+else l:=7;
+if print_col+l>line_length-2 then
+ begin if state=normal then
+ begin print('"'); state:=special;
+ end;
+ print_ln(' ');
+ print_col:=0;
+ end;
+@<Print |c| and update |state| and |print_col|@>;
+end;
+
+@ @<Print |c| and update |state| and |print_col|@>=
+if state=normal then
+ if printable then print(xchr[c])
+ else begin print('"&char',c:1);
+ print_col:=print_col+2;
+ end
+else begin if state=special then
+ begin print('&'); incr(print_col);
+ end;
+ if printable then
+ begin print('"',xchr[c]); incr(print_col);
+ end
+ else print('char',c:1);
+ end;
+print_col:=print_col+l;
+if printable then state:=normal @+else state:=special
+
+@ The |end_char_string| procedure gets the string ended properly and ensures
+that there is room for |l| more characters on the output line.
+
+@<Declare subroutines for printing strings@>=
+procedure end_char_string(@!l:integer);
+begin while state>special do
+ begin print('"');
+ incr(print_col);
+ decr(state);
+ end;
+if print_col+l>line_length then
+ begin print_ln(' '); print_col:=0;
+ end;
+state:=initial; {get ready to print the next string}
+end;
+
+@ Since |end_char_string| resets |state:=initial|, all we have to do is set
+|state:=initial| once at the beginning.
+
+@<Set init...@>=
+state:=initial;
+
+@ Characters and rules are positioned according to global variables |h| and~|v|
+as will be explained later. We also need scale factors that convert quantities
+to the right units when they are printed in the \.{MPX} file.
+
+Even though all variable names in the \MP\ output are made local via \.{save}
+commands, it is still desirable to preceed them with underscores. This makes
+the output more likely to work when used in a macro definition, since the
+generated variables names must not collide with formal parameters in such
+cases.
+
+@<Glob...@>=
+@!h,@!v:integer; {the current position in \.{DVI} units}
+@!conv:real; {converts \.{DVI} units to \MP\ points}
+@!mag:real; {magnification factor times 1000}
+
+@ @p @<Declare a procedure called |finish_last_char|@>@;
+procedure do_set_char(@!f,@!c:integer);
+begin if (c<font_bc[f])or(c>font_ec[f]) then
+ abort('attempt to typeset invalid character ',c:1);
+@.attempt to typeset...@>
+if (h<>str_h2)or(v<>str_v)or(f<>str_f)or(dvi_scale<>str_scale) then
+ begin if str_f>=0 then finish_last_char
+ else if not fonts_used then
+ @<Prepare to output the first character on a page@>;
+ if not font_used[f] then
+ @<Prepare to use font |f| for the first time on a page@>;
+ print('_s('); print_col:=3;@/
+ str_scale:=dvi_scale; str_f:=f; str_v:=v; str_h1:=h;
+ end;
+print_char(c);
+str_h2:=h+@<Width of character |c| in font |f|@>;
+end;
+
+@ @<Glob...@>=
+@!font_used:array[0..max_fonts] of boolean;
+ {has this font been used on this page?}
+@!fonts_used:boolean; {has any font been used on this page?}
+@!rules_used:boolean; {has any rules been set on this page?}
+@!str_h1,str_v:integer; {starting position for current output string}
+@!str_h2:integer; {where the current output string ends}
+@!str_f:integer; {internal font number for the current output string}
+@!str_scale:real; {value of |dvi_scale| for the current output string}
+
+@ The |font_used| array is not initialized until it is actually time to output
+a character.
+
+@<Prepare to output the first character on a page@>=
+begin k:=0;
+while (k<nf) do
+ begin font_used[k]:=false; incr(k);
+ end;
+fonts_used:=true;
+print_ln('string _n[];');
+print_ln('vardef _s(expr _t,_f,_m,_x,_y)=');
+print_ln(' addto _p also _t infont _f scaled _m shifted (_x,_y); enddef;');
+end
+
+@ @<Do any other initialization required for the new font |f|@>=
+font_used[f]:=false;
+
+@ @<Prepare to use font |f| for the first time on a page@>=
+begin font_used[f]:=true;
+print('_n',f:1,'=');
+print_col:=6;
+print_font(f);
+end_char_string(1);
+print_ln(';');
+end
+
+@ We maintain the invariant that |str_f=-1| when there is no output string
+under construction.
+
+@<Declare a procedure called |finish_last_char|@>=
+procedure finish_last_char;
+var @!m,@!x,@!y:real;
+ {font scale factor and \MP\ coordinates of reference point}
+begin if str_f>=0 then
+ begin m:=str_scale*font_scaled_size[str_f]*mag/font_design_size[str_f];@/
+ x:=conv*str_h1; y:=conv*(-str_v);
+ if (abs(x)>=4096.0)or(abs(y)>=4096.0)or(m>=4096.0)or(m<0) then
+ begin warn('text scaled ',m:1:1,@|
+ ' at (',x:1:1,',',y:1:1,') is out of range');
+ end_char_string(60);
+ end
+ else end_char_string(40);
+ print_ln(',_n',str_f:1,',',m:1:5,',',x:1:4,',',y:1:4,');');
+ str_f:=-1;
+ end;
+end;
+
+@ Setting rules is fairly simple.
+
+@p procedure do_set_rule(@!ht,@!wd:integer);
+var @!xx1,@!yy1,@!xx2,@!yy2,@!ww:real;
+ {\MP\ coordinates of lower-left and upper-right corners}
+begin if wd=1 then @<Handle a special rule that determines the box size@>
+else if (ht>0)or(wd>0) then
+ begin if str_f>=0 then finish_last_char;
+ if not rules_used then
+ begin rules_used:=true;@/
+ print_ln('interim linecap:=0;');@/
+ print_ln('vardef _r(expr _a,_w) =');
+ print_ln(' addto _p doublepath _a withpen pencircle scaled _w enddef;');
+ end;
+ @<Make |(xx1,yy1)| and |(xx2,yy2)| then ends of the desired penstroke
+ and |ww| the desired stroke width@>;
+ if (abs(xx1)>=4096.0)or(abs(yy1)>=4096.0)or@|
+ (abs(xx2)>=4096.0)or(abs(yy2)>=4096.0)or(ww>=4096.0) then
+ warn('hrule or vrule near (',xx1:1:1,',',yy1:1:1,') is out of range');
+ print_ln('_r((',xx1:1:4,',',yy1:1:4,')..(',xx2:1:4,',',yy2:1:4,
+ '), ',ww:1:4,');');
+ end;
+end;
+
+@ @<Make |(xx1,yy1)| and |(xx2,yy2)| then ends of the desired penstroke...@>=
+xx1:=conv*h;
+yy1:=conv*(-v);
+if wd>ht then
+ begin xx2:=xx1+conv*wd;
+ ww:=conv*ht;@/
+ yy1:=yy1+0.5*ww;
+ yy2:=yy1;
+ end
+else begin yy2:=yy1+conv*ht;
+ ww:=conv*wd;@/
+ xx1:=xx1+0.5*ww;
+ xx2:=xx1;
+ end
+
+@ Rules of width one dvi unit are not typeset since \.{MPtoTeX} adds an
+extraneous rule of this width in order to allow \.{DVItoMP} to deduce the
+dimensions of the boxes it ships out. The box width is the left edge of the
+last such rule; the height and depth are at the top and bottom of the rule.
+There should be only one special rule per picture but there could be more if
+the user tries to typeset his own one-dvi-unit rules. In this case the
+dimension-determining rule is the last one in the picture.
+
+@<Handle a special rule that determines the box size@>=
+begin pic_wd:=h; pic_dp:=v; pic_ht:=ht-v;
+end
+
+@ @<Glob...@>=
+pic_dp, pic_ht, pic_wd: integer; {picture dimensions from special rule}
+
+@ The following initialization and clean-up is required. We do a little more
+initialization than is absolutely necessary since some compilers might complain
+if the variables are uninitialized when |do_set_char| tests them.
+
+@p procedure start_picture;
+begin fonts_used:=false;
+rules_used:=false;
+str_f:=-1;@/
+str_v:=0; str_h2:=0; str_scale:=1.0; {values don't matter}
+print_ln('begingroup save _p,_r,_s,_n; picture _p; _p=nullpicture;');
+end;
+@#
+procedure stop_picture;
+var @!w,@!h,@!dd:real; {width, height, negative depth in PostScript points}
+begin if str_f>=0 then finish_last_char;
+@<Print a \&{setbounds} command based on picture dimensions@>;
+print_ln('_p endgroup');
+end;
+
+@ @<Print a \&{setbounds} command based on picture dimensions@>=
+dd:=-pic_dp*conv;
+w:=conv*pic_wd; h:=conv*pic_ht;@/
+print_ln('setbounds _p to (0,',dd:1:4,')--(',w:1:4,',',dd:1:4,')--');
+print_ln(' (',w:1:4,',',h:1:4,')--(0,',h:1:4,')--cycle;')
+
+@* Translation to symbolic form.
+The main work of \.{DVItoMP} is accomplished by the |do_dvi_commands|
+procedure, which produces the output for an entire page, assuming that the
+|bop| command for that page has already been processed. This procedure is
+essentially an interpretive routine that reads and acts on the \.{DVI}
+commands. It is also capable of executing the typesetting commands for
+a character in a virtual font.
+
+@ The definition of \.{DVI} files refers to six registers,
+$(h,v,w,x,y,z)$, which hold integer values in \.{DVI} units.
+These units come directly from the input file except they need to be
+rescaled when typesetting characters from a virtual font.
+The stack of $(h,v,w,x,y,z)$ values is represented by six arrays
+called |hstack|, \dots, |zstack|.
+
+@<Glob...@>=
+@!w,@!x,@!y,@!z:integer;
+ {current state values (|h| and |v| have already been declared)}
+@!hstack,@!vstack,@!wstack,@!xstack,@!ystack,@!zstack:
+ array [0..stack_size] of integer; {pushed down values in \.{DVI} units}
+@!stk_siz:integer; {the current stack size}
+@!dvi_scale:real; {converts units of current input source to \.{DVI} units}
+
+@ @<Do initialization required before starting a new page@>=
+dvi_scale:=1.0;
+stk_siz:=0;
+h:=0; v:=0
+
+@ Next, we need procedures to handle |push| and |pop| commands.
+
+@p procedure do_push;
+begin if stk_siz=stack_size then
+ abort('DVItoMP capacity exceeded (stack size=',stack_size:1,')');
+@.DVItoMP capacity exceeded...@>
+hstack[stk_siz]:=h; vstack[stk_siz]:=v; wstack[stk_siz]:=w;
+xstack[stk_siz]:=x; ystack[stk_siz]:=y; zstack[stk_siz]:=z;
+incr(stk_siz);
+end;
+@#
+procedure do_pop;
+begin if stk_siz=0 then bad_dvi('attempt to pop empty stack')
+else begin decr(stk_siz);
+ h:=hstack[stk_siz]; v:=vstack[stk_siz]; w:=wstack[stk_siz];
+ x:=xstack[stk_siz]; y:=ystack[stk_siz]; z:=zstack[stk_siz];
+ end;
+end;
+
+@ We need to define the |set_virtual_char| procedure now because it is
+mutually recursive with |do_dvi_commands|. This is really a supervisory
+@^recursion@>
+procedure that calls |do_set_char| or adjusts the input source to read
+typesetting commands for a character in a virtual font.
+
+@p procedure do_dvi_commands;forward;@t\2@>
+procedure set_virtual_char(@!f,@!c:integer);
+var @!old_scale:real; {original value of |dvi_scale|}
+@!old_buf_ptr:0..virtual_space; {original value of the input pointer |buf_ptr|}
+@!old_fbase,@!old_ftop:0..max_fnums;
+ {originally applicable part of the |font_num| table}
+begin if fbase[f]=0 then do_set_char(f,c)
+else begin old_fbase:=cur_fbase; old_ftop:=cur_ftop;
+ cur_fbase:=fbase[f]; cur_ftop:=ftop[f];@/
+ old_scale:=dvi_scale;
+ dvi_scale:=dvi_scale*font_scaled_size[f];
+ old_buf_ptr:=buf_ptr;
+ buf_ptr:=start_cmd(f)(c);@/
+ do_push;
+ do_dvi_commands;
+ do_pop;@/
+ buf_ptr:=old_buf_ptr;
+ dvi_scale:=old_scale;
+ cur_fbase:=old_fbase; cur_ftop:=old_ftop;
+ end;
+end;
+
+@ Before we get into the details of |do_dvi_commands|, it is convenient to
+consider a simpler routine that computes the first parameter of each
+opcode.
+
+@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)
+
+@<Declare a function called |first_par|@>=
+function first_par(o:eight_bits):integer;
+begin case o of
+sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64):
+ first_par:=o-set_char_0;
+set1,put1,fnt1,xxx1,fnt_def1: first_par:=get_byte;
+set1+1,put1+1,fnt1+1,xxx1+1,fnt_def1+1: first_par:=get_two_bytes;
+set1+2,put1+2,fnt1+2,xxx1+2,fnt_def1+2: first_par:=get_three_bytes;
+right1,w1,x1,down1,y1,z1: first_par:=signed_byte;
+right1+1,w1+1,x1+1,down1+1,y1+1,z1+1: first_par:=signed_pair;
+right1+2,w1+2,x1+2,down1+2,y1+2,z1+2: first_par:=signed_trio;
+set1+3,set_rule,put1+3,put_rule,right1+3,w1+3,x1+3,down1+3,y1+3,z1+3,
+ fnt1+3,xxx1+3,fnt_def1+3: first_par:=signed_quad;
+nop,bop,eop,push,pop,pre,post,post_post,undefined_commands: first_par:=0;
+w0: first_par:=w;
+x0: first_par:=x;
+y0: first_par:=y;
+z0: first_par:=z;
+sixty_four_cases(fnt_num_0): first_par:=o-fnt_num_0;
+end;
+end;
+
+@ Here is the |do_dvi_commands| procedure.
+
+@p procedure do_dvi_commands;
+label 9999;
+var o:eight_bits; {operation code of the current command}
+@!p,@!q:integer; {parameters of the current command}
+@!cur_font:integer; {current internal font number}
+begin if (cur_fbase<cur_ftop) and (buf_ptr<virtual_space) then
+ cur_font:=select_font(font_num[cur_ftop-1]) {select first local font}
+else cur_font:=max_fnums+1; {current font is undefined}
+w:=0; x:=0; y:=0; z:=0; {initialize the state variables}
+while true do @<Translate the next command in the \.{DVI} file;
+ |goto 9999| if it was |eop|@>;
+9999: do_nothing;
+end;
+
+@ The multiway switch in |first_par|, above, was organized by the length
+of each command; the one in |do_dvi_commands| is organized by the semantics.
+
+@ @<Translate the next command...@>=
+begin o:=get_byte; p:=first_par(o);
+if eof(dvi_file) then bad_dvi('the DVI file ended prematurely');
+@.the DVI file ended prematurely@>
+if o<set1+4 then {|set_char_0| through |set_char_127|, |set1| through |set4|}
+ begin if cur_font>max_fnums then
+ if vf_reading then
+ abort('no font selected for character ',p:1,' in virtual font')
+ else bad_dvi('no font selected for character ',p:1);
+@.no font selected@>
+ set_virtual_char(cur_font,p);
+ h:=h+@<Width of character |p| in font |cur_font|@>;
+ end
+else case o of
+ four_cases(put1): set_virtual_char(cur_font,p);
+ set_rule: begin q:=trunc(signed_quad*dvi_scale);
+ do_set_rule(trunc(p*dvi_scale),q);
+ h:=h+q;
+ end;
+ put_rule: do_set_rule(trunc(p*dvi_scale),trunc(signed_quad*dvi_scale));
+ @t\4@>@<Additional cases for translating \.{DVI} command |o| with
+ first paramter |p|@>@;
+ undefined_commands:bad_dvi('undefined command ',o:1);
+@.undefined command@>
+ end; {all cases have been enumerated}
+end
+
+@ @<Additional cases for translating \.{DVI} command |o|...@>=
+four_cases(xxx1): for k:=1 to p do
+ down_the_drain:=get_byte;
+pre,post,post_post: bad_dvi('preamble or postamble within a page!');
+@.preamble or postamble within a page@>
+
+@ @<Additional cases for translating \.{DVI} command |o|...@>=
+nop: do_nothing;
+bop: bad_dvi('bop occurred before eop');
+@.bop occurred before eop@>
+eop: goto 9999;
+push: do_push;
+pop: do_pop;
+
+@ @<Additional cases for translating \.{DVI} command |o|...@>=
+four_cases(right1):h:=h+trunc(p*dvi_scale);
+w0,four_cases(w1):begin w:=trunc(p*dvi_scale); h:=h+w;
+ end;
+x0,four_cases(x1):begin x:=trunc(p*dvi_scale); h:=h+x;
+ end;
+four_cases(down1):v:=v+trunc(p*dvi_scale);
+y0,four_cases(y1):begin y:=trunc(p*dvi_scale); v:=v+y;
+ end;
+z0,four_cases(z1):begin z:=trunc(p*dvi_scale); v:=v+z;
+ end;
+
+@ @<Additional cases for translating \.{DVI} command |o|...@>=
+sixty_four_cases(fnt_num_0),four_cases(fnt1):
+ cur_font:=select_font(p);
+four_cases(fnt_def1): define_font(p);
+
+@* The main program.
+Now we are ready to put it all together. This is where \.{DVItoMP} starts,
+and where it ends.
+
+@p begin initialize; {get all variables initialized}
+@<Process the preamble@>;
+open_mpx_file;
+print_ln(banner);
+begin while true do
+ begin @<Advance to the next |bop| command@>;
+ for k:=0 to 10 do down_the_drain:=signed_quad;
+ @<Do initialization required before starting a new page@>;
+ start_picture;
+ do_dvi_commands;
+ if stk_siz<>0 then bad_dvi('stack not empty at end of page');
+@.stack not empty...@>
+ stop_picture;
+ print_ln('mpxbreak');
+ end;
+done:end;
+final_end:end.
+
+@ The main program needs a few global variables in order to do its work.
+
+@<Glob...@>=
+@!k,@!p:integer; {general purpose registers}
+@!numerator,@!denominator:integer; {stated conversion ratio}
+
+@ @<Process the preamble@>=
+open_dvi_file;
+p:=get_byte; {fetch the first byte}
+if p<>pre then bad_dvi('First byte isn''t start of preamble!');
+@.First byte isn't...@>
+p:=get_byte; {fetch the identification byte}
+if p<>id_byte then
+ warn('identification in byte 1 should be ',id_byte:1,'!');
+@.identification...should be n@>
+@<Compute the conversion factor@>;
+p:=get_byte; {fetch the length of the introductory comment}
+while p>0 do
+ begin decr(p); down_the_drain:=get_byte;
+ end
+
+@ The conversion factor |conv| is figured as follows: There are exactly
+|n/d| decimicrons per \.{DVI} unit, and 254000 decimicrons per inch,
+and |resolution| pixels per inch. Then we have to adjust this
+by the stated amount of magnification. No such adjustment is needed for
+|dvi_per_fix| since it is used to convert design sizes.
+
+@<Compute the conversion factor@>=
+numerator:=signed_quad; denominator:=signed_quad;
+if (numerator<=0)or(denominator<=0) then
+ bad_dvi('bad scale ratio in preamble');
+@.bad scale ratio@>
+mag:=signed_quad/1000.0;
+if mag<=0.0 then bad_dvi('magnification isn''t positive');
+@.magnification isn't positive@>
+conv:=(numerator/254000.0)*(72.0/denominator)*mag;
+dvi_per_fix:=(254000.0/numerator)*(denominator/72.27)/1048576.0;
+
+@ @<Advance to the next |bop| command@>=
+repeat k:=get_byte;
+if (k>=fnt_def1)and(k<fnt_def1+4) then
+ begin p:=first_par(k); define_font(p); k:=nop;
+ end;
+until k<>nop;
+if k=post then goto done;
+if k<>bop then bad_dvi('missing bop');
+@.missing bop@>
+
+
+@* System-dependent changes.
+This section should be replaced, if necessary, by changes to the program
+that are necessary to make \.{DVItoMP} 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.