% vutex:web % % This program was written by Warren Wolfe, and all rights are reserved. % Copyright 1987 CUBE Software, Victoria, B.C., Canada. % Version 1.00 ASCII terminal driver: December 1987 % 1.01 Modification for horizontal and vertical shifts: June 1989 % 1.02 Modification for page action: November 1989 % Here is TeX material that gets inserted after \input webmac \def\hang{\hangindent 3em\indent\ignorespaces} \font\ninerm=amr9 \let\mc=\ninerm % medium caps for names like PASCAL \def\PASCAL{{\mc PASCAL}} \def\vutex{{\bf vu\TeX}} \def\tamu{Texas A\char38 M} \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{{\tenrm \vutex}} \def\contentspagenumber{0} \def\topofcontents{\null \def\titlepage{F} % include headline on the contents page \def\rheader{\mainfont\hfil \contentspagenumber} \vfill \centerline{\titlefont The {\ttitlefont \vutex} processor} \vskip 15pt \centerline{(Version 1.02, Dec 1989)} \vfill} \def\botofcontents{ \bigskip \centerline{This report was written by Warren Wolfe.} \centerline{based on the original by Tomas Rokicki} \centerline{\copyright 1988} } \pageno=\contentspagenumber \advance\pageno by 1 @* Introduction. This report documents the program designed to output \TeX\ processed data to any ASCII terminal. The program is based on \.{DVIgen} which is intended as a generic driver for \.{DVI} files created by \TeX82 and other document preparation systems and much of this documentation is extracted directly from the Rokicki original. It should be relatively easy to modify for a particular system. \vutex\ uses the 95 printable ASCII characters and the rather limited row and column format of an ordinary screen or printer to mimic somehow the typeset output of \TeX\ and fine resolution printers. Understandably the result is much less than other \.{DVIgen} processors, but it is a lot more acessible. The overall plan of \vutex\ is to scan the \.{DVI} file and to determine what fonts are being used. For most of these fonts, a table is available which maps a character in the font to a corresponding printable ASCII character. Then, with each page, a page grid is created corresponding to the raster grid of the screen or printer. Each character is placed in a location in that grid according to the horizontal and vertical coordinates determined by \TeX. Subscripts or superscripts are associated with their baseline. Finally the page grid is output to the device in either of two forms --- compressed or \TeX\ spaced. In the compressed mode, all extraneous spaces between words are deleted, while the alternate mode places words so that the characters are left-justified in the space and at the location determined by \TeX. @d clone=='vutex' @ The |banner| string defined here should be changed whenever \vutex\ gets modified. @d banner=='This is ',clone,', Version 1.02' {printed when the program starts} @ This program is written in standard \PASCAL, except where it is necessary to use extensions; for example, \vutex\ 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@> @d othercases == others: {default for cases not listed explicitly} @d endcases == @+end {follows the default case in an extended |case| statement} @f othercases == else @f endcases == end @ The binary input comes from |dvi_file|, and the final output is written to |bit_file|. On line interaction and error messages are written on \PASCAL's standard |output| file. The term |print| is used instead of |write| when this program writes on |output|, so that all such output could easily be redirected if desired. @d print(#)==write(#) @d print_ln(#)==write_ln(#) @p program vutex(dvi_file,bit_file,input,output); label @@/ const @@/ type @@/ var @@/ procedure initialize; {this procedure gets things started properly} var i:integer; {loop index for initializations} begin print_ln(banner);@/ @@/ 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} @d pdone=31 {go here when finished with a subtask during prescan} @=final_end,done,pdone; @ The following parameters can be changed at compile time to extend or reduce \vutex 's capacity. @= @!max_mem_size=68000; {the major array used for almost everything.} @!name_size=1000; {total length of all font file names, special commands, and other miscellaneous strings.} @!terminal_line_length=150; {maximum number of characters input in a single line of input from the terminal} @!stack_size=100; {\.{DVI} files shouldn't |push| beyond this depth} @!name_length=50; {maximum length of a file name} @!total_rast=56000; {maximum number of rasters on page} @!max_p_width=280; {maximum width of page in rasters} @!dfl_p_width=200; {default width of page in rasters} @!dfl_n_lines=280; {maximum number of lines in a page } @!dfl_screen_width=80; {default width of screen in rasters} @!dfl_screen_height=22; {default height of screen in rasters} @!hor_overlap=6; {overlap for horizontal page action} @!vert_overlap=2; {overlap for vertical page action} @!page_length=36; {maximum physical length of a page (in cm)} @!hh_offset=18; {offset at left side of page} @!vv_offset=36; {offset at top of page} @ 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} @ If the \.{DVI} file is badly malformed, the whole process must be aborted; \vutex\ 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 \vutex. @^system dependencies@> @d abort(#)==begin print_ln(' ',#); jump_out; end @d bad_dvi(#)==abort('Bad DVI file: ',#,'!') @.Bad DVI file@> @p procedure jump_out; begin goto final_end; end; @* The character set. Like all programs written with the \.{WEB} system, \vutex\ 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 and certain other strings. @d dfl_chr == '#' {default character ASCII code for unprintable fonts} @= @!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 \vutex, So we shall assume that the \PASCAL\ system being used for \vutex\ 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|} @= @!text_file=packed file of text_char; @ The \vutex\ processor converts between ASCII code and the user's external character set by means of arrays |xord| and |xchr| that are analogous to \PASCAL's |ord| and |chr| functions. However, the fonts used by \TeX\ do not all use the same character table and so we define other arrays to handle output for the major font families. @= @!xord: array [text_char] of ASCII_code; {specifies conversion of input characters} @!xchr: array [0..255] of text_char; {specifies conversion of output characters for tty fonts} @!rchr: array [0..255] of text_char; {specifies conversion of output characters for roman type fonts} @!mchr: array [0..255] of text_char; {specifies conversion of output characters for math italic fonts} @!schr: array [0..255] of text_char; {specifies conversion of output characters for math symbol fonts} @!echr: array [0..255] of text_char; {specifies conversion of output characters for math extension fonts} @ 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. @= for i:=0 to @'12 do xchr[i]:=dfl_chr; xchr[@'13]:='^'; xchr[@'14]:='v'; xchr[@'15]:=''''; xchr[@'16]:=dfl_chr; xchr[@'17]:=dfl_chr;@/ xchr[@'20]:='i'; xchr[@'21]:='j'; xchr[@'22]:='`'; xchr[@'23]:=''''; xchr[@'24]:=' '; xchr[@'25]:=' '; xchr[@'26]:=' '; xchr[@'27]:=' ';@/ xchr[@'30]:=' '; xchr[@'31]:=dfl_chr; xchr[@'32]:=dfl_chr; xchr[@'33]:=dfl_chr; xchr[@'34]:=dfl_chr; xchr[@'35]:=dfl_chr; xchr[@'36]:=dfl_chr; xchr[@'37]:=dfl_chr; 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]:=dfl_chr; @ Many of the \TeX\ standard fonts use a character layout slightly different from that given above. Therefore, we define a new array to handle output from these fonts. Modern Roman fonts are given below. @= for i:=0 to @'17 do rchr[i]:=dfl_chr; rchr[@'20]:='i'; rchr[@'21]:='j'; rchr[@'22]:='`'; rchr[@'23]:=''''; rchr[@'24]:=' '; rchr[@'25]:=' '; rchr[@'26]:=' '; rchr[@'27]:=' ';@/ rchr[@'30]:=' '; rchr[@'31]:='s'; rchr[@'32]:='a'; rchr[@'33]:='o'; rchr[@'34]:='o'; rchr[@'35]:='A'; rchr[@'36]:='O'; rchr[@'37]:='O';@/ rchr[@'40]:=' '; rchr[@'41]:='!'; rchr[@'42]:='"'; rchr[@'43]:='#'; rchr[@'44]:='$'; rchr[@'45]:='%'; rchr[@'46]:='&'; rchr[@'47]:='''';@/ rchr[@'50]:='('; rchr[@'51]:=')'; rchr[@'52]:='*'; rchr[@'53]:='+'; rchr[@'54]:=','; rchr[@'55]:='-'; rchr[@'56]:='.'; rchr[@'57]:='/';@/ rchr[@'60]:='0'; rchr[@'61]:='1'; rchr[@'62]:='2'; rchr[@'63]:='3'; rchr[@'64]:='4'; rchr[@'65]:='5'; rchr[@'66]:='6'; rchr[@'67]:='7';@/ rchr[@'70]:='8'; rchr[@'71]:='9'; rchr[@'72]:=':'; rchr[@'73]:=';'; rchr[@'74]:=dfl_chr; rchr[@'75]:='='; rchr[@'76]:=dfl_chr; rchr[@'77]:='?';@/ rchr[@'100]:='@@'; rchr[@'101]:='A'; rchr[@'102]:='B'; rchr[@'103]:='C'; rchr[@'104]:='D'; rchr[@'105]:='E'; rchr[@'106]:='F'; rchr[@'107]:='G';@/ rchr[@'110]:='H'; rchr[@'111]:='I'; rchr[@'112]:='J'; rchr[@'113]:='K'; rchr[@'114]:='L'; rchr[@'115]:='M'; rchr[@'116]:='N'; rchr[@'117]:='O';@/ rchr[@'120]:='P'; rchr[@'121]:='Q'; rchr[@'122]:='R'; rchr[@'123]:='S'; rchr[@'124]:='T'; rchr[@'125]:='U'; rchr[@'126]:='V'; rchr[@'127]:='W';@/ rchr[@'130]:='X'; rchr[@'131]:='Y'; rchr[@'132]:='Z'; rchr[@'133]:='['; rchr[@'134]:='"'; rchr[@'135]:=']'; rchr[@'136]:='^'; rchr[@'137]:=' ';@/ rchr[@'140]:='`'; rchr[@'141]:='a'; rchr[@'142]:='b'; rchr[@'143]:='c'; rchr[@'144]:='d'; rchr[@'145]:='e'; rchr[@'146]:='f'; rchr[@'147]:='g';@/ rchr[@'150]:='h'; rchr[@'151]:='i'; rchr[@'152]:='j'; rchr[@'153]:='k'; rchr[@'154]:='l'; rchr[@'155]:='m'; rchr[@'156]:='n'; rchr[@'157]:='o';@/ rchr[@'160]:='p'; rchr[@'161]:='q'; rchr[@'162]:='r'; rchr[@'163]:='s'; rchr[@'164]:='t'; rchr[@'165]:='u'; rchr[@'166]:='v'; rchr[@'167]:='w';@/ rchr[@'170]:='x'; rchr[@'171]:='y'; rchr[@'172]:='z'; rchr[@'173]:='-'; rchr[@'174]:='-'; rchr[@'175]:='"'; rchr[@'176]:='~'; for i:=@'177 to 255 do rchr[i]:=dfl_chr; @ The standard \TeX\ math italics fonts use quite a different layout than either of the above, and we include a separate array for these useful fonts. @= for i:=0 to @'47 do mchr[i]:=dfl_chr; mchr[@'50]:='-'; mchr[@'51]:='-'; mchr[@'52]:='-'; mchr[@'53]:='-'; mchr[@'54]:=' '; mchr[@'55]:=' '; mchr[@'56]:='>'; mchr[@'57]:='<';@/ mchr[@'60]:='0'; mchr[@'61]:='1'; mchr[@'62]:='2'; mchr[@'63]:='3'; mchr[@'64]:='4'; mchr[@'65]:='5'; mchr[@'66]:='6'; mchr[@'67]:='7';@/ mchr[@'70]:='8'; mchr[@'71]:='9'; mchr[@'72]:='.'; mchr[@'73]:=','; mchr[@'74]:='<'; mchr[@'75]:='/'; mchr[@'76]:='>'; mchr[@'77]:='*';@/ mchr[@'100]:=dfl_chr; mchr[@'101]:='A'; mchr[@'102]:='B'; mchr[@'103]:='C'; mchr[@'104]:='D'; mchr[@'105]:='E'; mchr[@'106]:='F'; mchr[@'107]:='G';@/ mchr[@'110]:='H'; mchr[@'111]:='I'; mchr[@'112]:='J'; mchr[@'113]:='K'; mchr[@'114]:='L'; mchr[@'115]:='M'; mchr[@'116]:='N'; mchr[@'117]:='O';@/ mchr[@'120]:='P'; mchr[@'121]:='Q'; mchr[@'122]:='R'; mchr[@'123]:='S'; mchr[@'124]:='T'; mchr[@'125]:='U'; mchr[@'126]:='V'; mchr[@'127]:='W';@/ mchr[@'130]:='X'; mchr[@'131]:='Y'; mchr[@'132]:='Z'; mchr[@'133]:=dfl_chr; mchr[@'134]:=dfl_chr; mchr[@'135]:=dfl_chr; mchr[@'136]:=dfl_chr; mchr[@'137]:=dfl_chr;@/ mchr[@'140]:='l'; mchr[@'141]:='a'; mchr[@'142]:='b'; mchr[@'143]:='c'; mchr[@'144]:='d'; mchr[@'145]:='e'; mchr[@'146]:='f'; mchr[@'147]:='g';@/ mchr[@'150]:='h'; mchr[@'151]:='i'; mchr[@'152]:='j'; mchr[@'153]:='k'; mchr[@'154]:='l'; mchr[@'155]:='m'; mchr[@'156]:='n'; mchr[@'157]:='o';@/ mchr[@'160]:='p'; mchr[@'161]:='q'; mchr[@'162]:='r'; mchr[@'163]:='s'; mchr[@'164]:='t'; mchr[@'165]:='u'; mchr[@'166]:='v'; mchr[@'167]:='w';@/ mchr[@'170]:='x'; mchr[@'171]:='y'; mchr[@'172]:='z'; mchr[@'173]:='i'; mchr[@'174]:='j'; mchr[@'175]:='p'; mchr[@'176]:=' '; mchr[@'177]:=' ';@/ for i:=@'200 to 255 do mchr[i]:=dfl_chr; @ The math symbol font is, for the most part, unprintable in ASCII, but we shall map some of the characters onto meaningful representations. @= schr[0]:='-'; schr[1]:='.'; schr[2]:='x'; schr[3]:='*'; schr[4]:=':'; schr[5]:=dfl_chr; schr[6]:='+'; schr[7]:='-';@/ schr[@'10]:='+'; schr[@'11]:='-'; schr[@'12]:='x'; schr[@'13]:='o'; schr[@'14]:='o'; schr[@'15]:='O'; schr[@'16]:='o'; schr[@'17]:='o';@/ schr[@'20]:='='; schr[@'21]:='='; schr[@'22]:='<'; schr[@'23]:='>'; schr[@'24]:='<'; schr[@'25]:='>'; schr[@'26]:='<'; schr[@'27]:='>';@/ schr[@'30]:='~'; schr[@'31]:='~'; schr[@'32]:='<'; schr[@'33]:='>'; schr[@'34]:='<'; schr[@'35]:='>'; schr[@'36]:='<'; schr[@'37]:='>';@/ schr[@'40]:='-'; schr[@'41]:='-'; schr[@'42]:='|'; schr[@'43]:='|'; schr[@'44]:='-'; schr[@'45]:='/'; schr[@'46]:='\'; schr[@'47]:='~';@/ schr[@'50]:='-'; schr[@'51]:='-'; schr[@'52]:='|'; schr[@'53]:='|'; schr[@'54]:='-'; schr[@'55]:='\'; schr[@'56]:='/'; schr[@'57]:=dfl_chr;@/ schr[@'60]:=''''; schr[@'61]:=dfl_chr; schr[@'62]:=dfl_chr; schr[@'63]:=dfl_chr; schr[@'64]:=dfl_chr; schr[@'65]:=dfl_chr; schr[@'66]:='/'; schr[@'67]:=dfl_chr;@/ schr[@'70]:=dfl_chr; schr[@'71]:=dfl_chr; schr[@'72]:='-'; schr[@'73]:='0'; schr[@'74]:='R'; schr[@'75]:='I'; schr[@'76]:='|'; schr[@'77]:='|';@/ schr[@'100]:=dfl_chr; schr[@'101]:='A'; schr[@'102]:='B'; schr[@'103]:='C'; schr[@'104]:='D'; schr[@'105]:='E'; schr[@'106]:='F'; schr[@'107]:='G';@/ schr[@'110]:='H'; schr[@'111]:='I'; schr[@'112]:='J'; schr[@'113]:='K'; schr[@'114]:='L'; schr[@'115]:='M'; schr[@'116]:='N'; schr[@'117]:='O';@/ schr[@'120]:='P'; schr[@'121]:='Q'; schr[@'122]:='R'; schr[@'123]:='S'; schr[@'124]:='T'; schr[@'125]:='U'; schr[@'126]:='V'; schr[@'127]:='W';@/ schr[@'130]:='X'; schr[@'131]:='Y'; schr[@'132]:='Z'; schr[@'133]:=dfl_chr; schr[@'134]:=dfl_chr; schr[@'135]:=dfl_chr; schr[@'136]:=dfl_chr; schr[@'137]:=dfl_chr;@/ schr[@'140]:='-'; schr[@'141]:='-'; schr[@'142]:='|'; schr[@'143]:='|'; schr[@'144]:='|'; schr[@'145]:='|'; schr[@'146]:='{'; schr[@'147]:='}';@/ schr[@'150]:='<'; schr[@'151]:='>'; schr[@'152]:='|'; schr[@'153]:='|'; schr[@'154]:='|'; schr[@'155]:='|'; schr[@'156]:='\'; schr[@'157]:=dfl_chr;@/ schr[@'160]:='|'; schr[@'161]:=dfl_chr; schr[@'162]:=dfl_chr; schr[@'163]:='S'; schr[@'164]:=dfl_chr; schr[@'165]:=dfl_chr; schr[@'166]:='<'; schr[@'167]:='>';@/ for i:=@'170 to @'176 do schr[i]:=dfl_chr; for i:=@'177 to 255 do schr[i]:=dfl_chr; @ And then there is the math extension font which is used to create extended symbols. We do the best that we can. @= echr[0]:='('; echr[1]:=')'; echr[2]:='['; echr[3]:=']'; echr[4]:='|'; echr[5]:='|'; echr[6]:='|'; echr[7]:='|';@/ echr[@'10]:='{'; echr[@'11]:='}'; echr[@'12]:='<'; echr[@'13]:='>'; echr[@'14]:='|'; echr[@'15]:='|'; echr[@'16]:='/'; echr[@'17]:='\';@/ echr[@'20]:='('; echr[@'21]:=')'; echr[@'22]:='('; echr[@'23]:=')'; echr[@'24]:='['; echr[@'25]:=']'; echr[@'26]:='|'; echr[@'27]:='|';@/ echr[@'30]:='|'; echr[@'31]:='|'; echr[@'32]:='{'; echr[@'33]:='}'; echr[@'34]:='<'; echr[@'35]:='>'; echr[@'36]:='/'; echr[@'37]:='\';@/ echr[@'40]:='('; echr[@'41]:=')'; echr[@'42]:='['; echr[@'43]:=']'; echr[@'44]:='|'; echr[@'45]:='|'; echr[@'46]:='|'; echr[@'47]:='|';@/ echr[@'50]:='{'; echr[@'51]:='}'; echr[@'52]:='<'; echr[@'53]:='>'; echr[@'54]:='/'; echr[@'55]:='\'; echr[@'56]:='/'; echr[@'57]:='\';@/ echr[@'60]:='('; echr[@'61]:=')'; echr[@'62]:='|'; echr[@'63]:='|'; echr[@'64]:='|'; echr[@'65]:='|'; echr[@'66]:='|'; echr[@'67]:='|';@/ echr[@'70]:='('; echr[@'71]:=')'; echr[@'72]:='('; echr[@'73]:=')'; echr[@'74]:='{'; echr[@'75]:='}'; echr[@'76]:='|'; echr[@'77]:='|';@/ echr[@'100]:='('; echr[@'101]:=')'; echr[@'102]:='|'; echr[@'103]:='|'; echr[@'104]:='<'; echr[@'105]:='>'; echr[@'106]:=dfl_chr; echr[@'107]:=dfl_chr;@/ echr[@'110]:='S'; echr[@'111]:='S'; echr[@'112]:='o'; echr[@'113]:='O'; echr[@'114]:='+'; echr[@'115]:='+'; echr[@'116]:='x'; echr[@'117]:='X';@/ echr[@'120]:='E'; echr[@'121]:=dfl_chr; echr[@'122]:='S'; for i:=@'123 to @'127 do echr[i]:=dfl_chr; echr[@'130]:='E'; echr[@'131]:=dfl_chr; echr[@'132]:='S'; for i:=@'133 to @'141 do echr[i]:=dfl_chr; echr[@'142]:='^'; echr[@'143]:='^'; echr[@'144]:='^'; echr[@'145]:='~'; echr[@'146]:='~'; echr[@'147]:='~';@/ echr[@'150]:='['; echr[@'151]:=']'; echr[@'152]:='|'; echr[@'153]:='|'; echr[@'154]:='|'; echr[@'155]:='|'; echr[@'156]:='{'; echr[@'157]:='}';@/ echr[@'160]:='|'; echr[@'161]:='|'; echr[@'162]:='|'; echr[@'163]:='|'; echr[@'164]:='|'; echr[@'165]:='|'; echr[@'166]:='|'; echr[@'167]:='|';@/ echr[@'170]:='|'; echr[@'171]:='\'; echr[@'172]:=dfl_chr; echr[@'173]:=dfl_chr; echr[@'174]:=dfl_chr; echr[@'175]:=dfl_chr; echr[@'176]:='|'; echr[@'177]:='|';@/ for i:=@'200 to 255 do echr[i]:=dfl_chr; @ The following system-independent code makes the |xord| array contain a suitable inverse to the information in |xchr|. @= for i:=first_text_char to last_text_char do xord[chr(i)]:=@'40; for i:=" " to "~" do xord[xchr[i]]:=i; @* Device-independent file format. Before we get into the details of \vutex, we need to know exactly what \.{DVI} files are. The form of such files was designed by David R. @^Fuchs, David Raymond@> Fuchs in 1979. Almost any reasonable typesetting device can be driven by a program that takes \.{DVI} files as input, and dozens of such \.{DVI}-to-whatever programs have been written. Thus, it is possible to print the output of document compilers like \TeX\ on many different kinds of equipment. A \.{DVI} file is a stream of 8-bit bytes, which may be regarded as a series of commands in a machine-like language. The first byte of each command is the operation code, and this code is followed by zero or more bytes that provide parameters to the command. The parameters themselves may consist of several consecutive bytes; for example, the `|set_rule|' command has two parameters, each of which is four bytes long. Parameters are usually regarded as nonnegative integers; but four-byte-long parameters, and shorter parameters that denote distances, can be either positive or negative. Such parameters are given in two's complement notation. For example, a two-byte-long distance parameter has a value between $-2^{15}$ and $2^{15}-1$. @.DVI {\rm files}@> A \.{DVI} file consists of a ``preamble,'' followed by a sequence of one or more ``pages,'' followed by a ``postamble.'' The preamble is simply a |pre| command, with its parameters that define the dimensions used in the file; this must come first. Each ``page'' consists of a |bop| command, followed by any number of other commands that tell where characters are to be placed on a physical page, followed by an |eop| command. The pages appear in the order that they were generated, not in any particular numerical order. If we ignore |nop| commands and \\{fnt\_def} commands (which are allowed between any two commands in the file), each |eop| command is immediately followed by a |bop| command, or by a |post| command; in the latter case, there are no more pages in the file, and the remaining bytes form the postamble. Further details about the postamble will be explained later. Some parameters in \.{DVI} commands are ``pointers.'' These are four-byte quantities that give the location number of some other byte in the file; the first byte is number~0, then comes number~1, and so on. For example, one of the parameters of a |bop| command points to the previous |bop|; this makes it feasible to read the pages in backwards order, in case the results are being directed to a device that stacks its output face up. Suppose the preamble of a \.{DVI} file occupies bytes 0 to 99. Now if the first page occupies bytes 100 to 999, say, and if the second page occupies bytes 1000 to 1999, then the |bop| that starts in byte 1000 points to 100 and the |bop| that starts in byte 2000 points to 1000. (The very first |bop|, i.e., the one that starts in byte 100, has a pointer of $-1$.) @ The \.{DVI} format is intended to be both compact and easily interpreted by a machine. Compactness is achieved by making most of the information implicit instead of explicit. When a \.{DVI}-reading program reads the commands for a page, it keeps track of several quantities: (a)~The current font |f| is an integer; this value is changed only by \\{fnt} and \\{fnt\_num} commands. (b)~The current position on the page is given by two numbers called the horizontal and vertical coordinates, |h| and |v|. Both coordinates are zero at the upper left corner of the page; moving to the right corresponds to increasing the horizontal coordinate, and moving down corresponds to increasing the vertical coordinate. Thus, the coordinates are essentially Cartesian, except that vertical directions are flipped; the Cartesian version of |(h,v)| would be |(h,-v)|. (c)~The current spacing amounts are given by four numbers |w|, |x|, |y|, and |z|, where |w| and~|x| are used for horizontal spacing and where |y| and~|z| are used for vertical spacing. (d)~There is a stack containing |(h,v,w,x,y,z)| values; the \.{DVI} commands |push| and |pop| are used to change the current level of operation. Note that the current font~|f| is not pushed and popped; the stack contains only information about positioning. The values of |h|, |v|, |w|, |x|, |y|, and |z| are signed integers having up to 32 bits, including the sign. Since they represent physical distances, there is a small unit of measurement such that increasing |h| by~1 means moving a certain tiny distance to the right. The actual unit of measurement is variable, as explained below. @ Here is a list of all the commands that may appear in a \.{DVI} file. Each command is specified by its symbolic name (e.g., |bop|), its opcode byte (e.g., 139), and its parameters (if any). The parameters are followed by a bracketed number telling how many bytes they occupy; for example, `|p[4]|' means that parameter |p| is four bytes long. \yskip\hang|set_char_0| 0. Typeset character number~0 from font~|f| such that the reference point of the character is at |(h,v)|. Then increase |h| by the width of that character. Note that a character may have zero or negative width, so one cannot be sure that |h| will advance after this command; but |h| usually does increase. \yskip\hang|set_char_1| through |set_char_127| (opcodes 1 to 127). Do the operations of |set_char_0|; but use the character whose number matches the opcode, instead of character~0. \yskip\hang|set1| 128 |c[1]|. Same as |set_char_0|, except that character number~|c| is typeset. \TeX82 uses this command for characters in the range |128<=c<256|. \yskip\hang|set2| 129 |c[2]|. Same as |set1|, except that |c|~is two bytes long, so it is in the range |0<=c<65536|. \TeX82 never uses this command, which is intended for processors that deal with oriental languages; but \vutex\ will allow character codes greater than 255, assuming that they all have the same width as the character whose code is $c \bmod 256$. @^oriental characters@>@^Chinese characters@>@^Japanese characters@> \yskip\hang|set3| 130 |c[3]|. Same as |set1|, except that |c|~is three bytes long, so it can be as large as $2^{24}-1$. \yskip\hang|set4| 131 |c[4]|. Same as |set1|, except that |c|~is four bytes long, possibly even negative. Imagine that. \yskip\hang|set_rule| 132 |a[4]| |b[4]|. Typeset a solid black rectangle of height |a| and width |b|, with its bottom left corner at |(h,v)|. Then set |h:=h+b|. If either |a<=0| or |b<=0|, nothing should be typeset. Note that if |b<0|, the value of |h| will decrease even though nothing else happens. Programs that typeset from \.{DVI} files should be careful to make the rules line up carefully with digitized characters, as explained in connection with the |rule_pixels| subroutine below. \yskip\hang|put1| 133 |c[1]|. Typeset character number~|c| from font~|f| such that the reference point of the character is at |(h,v)|. (The `put' commands are exactly like the `set' commands, except that they simply put out a character or a rule without moving the reference point afterwards.) \yskip\hang|put2| 134 |c[2]|. Same as |set2|, except that |h| is not changed. \yskip\hang|put3| 135 |c[3]|. Same as |set3|, except that |h| is not changed. \yskip\hang|put4| 136 |c[4]|. Same as |set4|, except that |h| is not changed. \yskip\hang|put_rule| 137 |a[4]| |b[4]|. Same as |set_rule|, except that |h| is not changed. \yskip\hang|nop| 138. No operation, do nothing. Any number of |nop|'s may occur between \.{DVI} commands, but a |nop| cannot be inserted between a command and its parameters or between two parameters. \yskip\hang|bop| 139 $c_0[4]$ $c_1[4]$ $\ldots$ $c_9[4]$ $p[4]$. Beginning of a page: Set |(h,v,w,x,y,z):=(0,0,0,0,0,0)| and set the stack empty. Set the current font |f| to an undefined value. The ten $c_i$ parameters can be used to identify pages, if a user wants to print only part of a \.{DVI} file; \TeX82 gives them the values of \.{\\count0} $\ldots$ \.{\\count9} at the time \.{\\shipout} was invoked for this page. The parameter |p| points to the previous |bop| command in the file, where the first |bop| has $p=-1$. \yskip\hang|eop| 140. End of page: Print what you have read since the previous |bop|. At this point the stack should be empty. (The \.{DVI}-reading programs that drive most output devices will have kept a buffer of the material that appears on the page that has just ended. This material is largely, but not entirely, in order by |v| coordinate and (for fixed |v|) by |h|~coordinate; so it usually needs to be sorted into some order that is appropriate for the device in question. \vutex\ does not do such sorting.) \yskip\hang|push| 141. Push the current values of |(h,v,w,x,y,z)| onto the top of the stack; do not change any of these values. Note that |f| is not pushed. \yskip\hang|pop| 142. Pop the top six values off of the stack and assign them to |(h,v,w,x,y,z)|. The number of pops should never exceed the number of pushes, since it would be highly embarrassing if the stack were empty at the time of a |pop| command. \yskip\hang|right1| 143 |b[1]|. Set |h:=h+b|, i.e., move right |b| units. The parameter is a signed number in two's complement notation, |-128<=b<128|; if |b<0|, the reference point actually moves left. \yskip\hang|right2| 144 |b[2]|. Same as |right1|, except that |b| is a two-byte quantity in the range |-32768<=b<32768|. \yskip\hang|right3| 145 |b[3]|. Same as |right1|, except that |b| is a three-byte quantity in the range |@t$-2^{23}$@><=b<@t$2^{23}$@>|. \yskip\hang|right4| 146 |b[4]|. Same as |right1|, except that |b| is a four-byte quantity in the range |@t$-2^{31}$@><=b<@t$2^{31}$@>|. \yskip\hang|w0| 147. Set |h:=h+w|; i.e., move right |w| units. With luck, this parameterless command will usually suffice, because the same kind of motion will occur several times in succession; the following commands explain how |w| gets particular values. \yskip\hang|w1| 148 |b[1]|. Set |w:=b| and |h:=h+b|. The value of |b| is a signed quantity in two's complement notation, |-128<=b<128|. This command changes the current |w|~spacing and moves right by |b|. \yskip\hang|w2| 149 |b[2]|. Same as |w1|, but |b| is a two-byte-long parameter, |-32768<=b<32768|. \yskip\hang|w3| 150 |b[3]|. Same as |w1|, but |b| is a three-byte-long parameter, |@t$-2^{23}$@><=b<@t$2^{23}$@>|. \yskip\hang|w4| 151 |b[4]|. Same as |w1|, but |b| is a four-byte-long parameter, |@t$-2^{31}$@><=b<@t$2^{31}$@>|. \yskip\hang|x0| 152. Set |h:=h+x|; i.e., move right |x| units. The `|x|' commands are like the `|w|' commands except that they involve |x| instead of |w|. \yskip\hang|x1| 153 |b[1]|. Set |x:=b| and |h:=h+b|. The value of |b| is a signed quantity in two's complement notation, |-128<=b<128|. This command changes the current |x|~spacing and moves right by |b|. \yskip\hang|x2| 154 |b[2]|. Same as |x1|, but |b| is a two-byte-long parameter, |-32768<=b<32768|. \yskip\hang|x3| 155 |b[3]|. Same as |x1|, but |b| is a three-byte-long parameter, |@t$-2^{23}$@><=b<@t$2^{23}$@>|. \yskip\hang|x4| 156 |b[4]|. Same as |x1|, but |b| is a four-byte-long parameter, |@t$-2^{31}$@><=b<@t$2^{31}$@>|. \yskip\hang|down1| 157 |a[1]|. Set |v:=v+a|, i.e., move down |a| units. The parameter is a signed number in two's complement notation, |-128<=a<128|; if |a<0|, the reference point actually moves up. \yskip\hang|down2| 158 |a[2]|. Same as |down1|, except that |a| is a two-byte quantity in the range |-32768<=a<32768|. \yskip\hang|down3| 159 |a[3]|. Same as |down1|, except that |a| is a three-byte quantity in the range |@t$-2^{23}$@><=a<@t$2^{23}$@>|. \yskip\hang|down4| 160 |a[4]|. Same as |down1|, except that |a| is a four-byte quantity in the range |@t$-2^{31}$@><=a<@t$2^{31}$@>|. \yskip\hang|y0| 161. Set |v:=v+y|; i.e., move down |y| units. With luck, this parameterless command will usually suffice, because the same kind of motion will occur several times in succession; the following commands explain how |y| gets particular values. \yskip\hang|y1| 162 |a[1]|. Set |y:=a| and |v:=v+a|. The value of |a| is a signed quantity in two's complement notation, |-128<=a<128|. This command changes the current |y|~spacing and moves down by |a|. \yskip\hang|y2| 163 |a[2]|. Same as |y1|, but |a| is a two-byte-long parameter, |-32768<=a<32768|. \yskip\hang|y3| 164 |a[3]|. Same as |y1|, but |a| is a three-byte-long parameter, |@t$-2^{23}$@><=a<@t$2^{23}$@>|. \yskip\hang|y4| 165 |a[4]|. Same as |y1|, but |a| is a four-byte-long parameter, |@t$-2^{31}$@><=a<@t$2^{31}$@>|. \yskip\hang|z0| 166. Set |v:=v+z|; i.e., move down |z| units. The `|z|' commands are like the `|y|' commands except that they involve |z| instead of |y|. \yskip\hang|z1| 167 |a[1]|. Set |z:=a| and |v:=v+a|. The value of |a| is a signed quantity in two's complement notation, |-128<=a<128|. This command changes the current |z|~spacing and moves down by |a|. \yskip\hang|z2| 168 |a[2]|. Same as |z1|, but |a| is a two-byte-long parameter, |-32768<=a<32768|. \yskip\hang|z3| 169 |a[3]|. Same as |z1|, but |a| is a three-byte-long parameter, |@t$-2^{23}$@><=a<@t$2^{23}$@>|. \yskip\hang|z4| 170 |a[4]|. Same as |z1|, but |a| is a four-byte-long parameter, |@t$-2^{31}$@><=a<@t$2^{31}$@>|. \yskip\hang|fnt_num_0| 171. Set |f:=0|. Font 0 must previously have been defined by a \\{fnt\_def} instruction, as explained below. \yskip\hang|fnt_num_1| through |fnt_num_63| (opcodes 172 to 234). Set |f:=1|, \dots, |f:=63|, respectively. \yskip\hang|fnt1| 235 |k[1]|. Set |f:=k|. \TeX82 uses this command for font numbers in the range |64<=k<256|. \yskip\hang|fnt2| 236 |k[2]|. Same as |fnt1|, except that |k|~is two bytes long, so it is in the range |0<=k<65536|. \TeX82 never generates this command, but large font numbers may prove useful for specifications of color or texture, or they may be used for special fonts that have fixed numbers in some external coding scheme. \yskip\hang|fnt3| 237 |k[3]|. Same as |fnt1|, except that |k|~is three bytes long, so it can be as large as $2^{24}-1$. \yskip\hang|fnt4| 238 |k[4]|. Same as |fnt1|, except that |k|~is four bytes long; this is for the really big font numbers (and for the negative ones). \yskip\hang|xxx1| 239 |k[1]| |x[k]|. This command is undefined in general; it functions as a $(k+2)$-byte |nop| unless special \.{DVI}-reading programs are being used. \TeX82 generates |xxx1| when a short enough \.{\\special} appears, setting |k| to the number of bytes being sent. It is recommended that |x| be a string having the form of a keyword followed by possible parameters relevant to that keyword. \yskip\hang|xxx2| 240 |k[2]| |x[k]|. Like |xxx1|, but |0<=k<65536|. \yskip\hang|xxx3| 241 |k[3]| |x[k]|. Like |xxx1|, but |0<=k<@t$2^{24}$@>|. \yskip\hang|xxx4| 242 |k[4]| |x[k]|. Like |xxx1|, but |k| can be ridiculously large. \TeX82 uses |xxx4| when |xxx1| would be incorrect. \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|; font definitions will be explained shortly. \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\hang|pre| 247 |i[1]| |num[4]| |den[4]| |mag[4]| |k[1]| |x[k]|. Beginning of the preamble; this must come at the very beginning of the file. Parameters |i|, |num|, |den|, |mag|, |k|, and |x| are explained below. \yskip\hang|post| 248. Beginning of the postamble, see below. \yskip\hang|post_post| 249. Ending of the postamble, see below. \yskip\noindent Commands 250--255 are undefined at the present time. @ @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 @ The preamble contains basic information about the file as a whole. As stated above, there are six parameters: $$\hbox{|@!i[1]| |@!num[4]| |@!den[4]| |@!mag[4]| |@!k[1]| |@!x[k]|.}$$ The |i| byte identifies \.{DVI} format; currently this byte is always set to~2. (Some day we will set |i=3|, when \.{DVI} format makes another incompatible change---perhaps in 1992.) The next two parameters, |num| and |den|, are positive integers that define the units of measurement; they are the numerator and denominator of a fraction by which all dimensions in the \.{DVI} file could be multiplied in order to get lengths in units of $10^{-7}$ meters. (For example, there are exactly 7227 \TeX\ points in 254 centimeters, and \TeX82 works with scaled points where there are $2^{16}$ sp in a point, so \TeX82 sets |num=25400000| and $|den|=7227\cdot2^{16}=473628672$.) @^sp@> The |mag| parameter is what \TeX82 calls \.{\\mag}, i.e., 1000 times the desired magnification. The actual fraction by which dimensions are multiplied is therefore $mn/1000d$. Note that if a \TeX\ source document does not call for any `\.{true}' dimensions, and if you change it only by specifying a different \.{\\mag} setting, the \.{DVI} file that \TeX\ creates will be completely unchanged except for the value of |mag| in the preamble and postable. Finally, |k| and |x| allow the \.{DVI} writer to include a comment, which is not interpreted further. The length of comment |x| is |k|, where |0<=k<256|. \vutex\ prints this comment out, which, in \TeX82 files, contains the date and time of execution. @d id_byte=2 {identifies the kind of \.{DVI} files described here} @ Font definitions for a given font number |k| contain further parameters $$\hbox{|c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.}$$ The four-byte value |c| is the check sum that \TeX\ (or whatever program generated the \.{DVI} file) found in the \.{TFM} file for this font; |c| should match the check sum of the font found by programs that read this \.{DVI} file. @^check sum@> Parameter |s| contains a fixed-point scale factor that is applied to the character widths in font |k|; font dimensions in \.{TFM} files and other font files are relative to this quantity, which is always positive and less than $2^{27}$. It is given in the same units as the other dimensions of the \.{DVI} file. Parameter |d| is similar to |s|; it is the ``design size,'' and it is given in \.{DVI} units that have not been corrected for the magnification~|mag| found in the preamble. Thus, font |k| is to be used at $|mag|\cdot s/1000d$ times its normal size. The remaining part of a font definition gives the external name of the font, which is an ASCII string of length |a+l|. The number |a| is the length of the ``area'' or directory, and |l| is the length of the font name itself; the standard local system font area is supposed to be used when |a=0|. The |n| field contains the area in its first |a| bytes. Font definitions must appear before the first use of a particular font number. Once font |k| is defined, it must not be defined again; however, we shall see below that font definitions appear in the postamble as well as in the pages, so in this sense each font number is defined exactly twice, if at all. Like |nop| commands and \\{xxx} commands, font definitions can appear before the first |bop|, or between an |eop| and a |bop|. @ The last page in a \.{DVI} file is followed by `|post|'; this command introduces the postamble, which summarizes important facts that \TeX\ has accumulated about the file, making it possible to print subsets of the data with reasonable efficiency. \vutex\ does not make use of this capability, however. The postamble has the form $$\vbox{\halign{\hbox{#\hfil}\cr |post| |p[4]| |num[4]| |den[4]| |mag[4]| |l[4]| |u[4]| |s[2]| |t[2]|\cr $\langle\,$font definitions$\,\rangle$\cr |post_post| |q[4]| |i[1]| 223's$[{\G}4]$\cr}}$$ Here |p| is a pointer to the final |bop| in the file. The next three parameters, |num|, |den|, and |mag|, are duplicates of the quantities that appeared in the preamble. Parameters |l| and |u| give respectively the height-plus-depth of the tallest page and the width of the widest page, in the same units as other dimensions of the file. These numbers might be used by a \.{DVI}-reading program to position individual ``pages'' on large sheets of film or paper. Unfortunately, since characters can be set outside of the page boundaries, these numbers cannot be used to set limits on the page area. Parameter |s| is the maximum stack depth (i.e., the largest excess of |push| commands over |pop| commands) needed to process this file. Then comes |t|, the total number of pages (|bop| commands) present. The postamble continues with font definitions, which are any number of \\{fnt\_def} commands as described above, possibly interspersed with |nop| commands. Each font number that is used in the \.{DVI} file must be defined exactly twice: Once before it is first selected by a \\{fnt} command, and once in the postamble. @ The last part of the postamble, following the |post_post| byte that signifies the end of the font definitions, contains |q|, a pointer to the |post| command that started the postamble. An identification byte, |i|, comes next; this currently equals~2, as in the preamble. The |i| byte is followed by four or more bytes that are all equal to the decimal number 223 (i.e., @'337 in octal). \TeX\ puts out four to seven of these trailing bytes, until the total length of the file is a multiple of four bytes, since this works out best on machines that pack four bytes per word; but any number of 223's is allowed, as long as there are at least four of them. In effect, 223 is a sort of signature that is added at the very end. @^Fuchs, David Raymond@> @* Plan of attack. A \.{DVI} to device program must deal with a large amount of data. The \.{DVI} files in most applications tend to be quite large. For this reason, one of the primary goals in the design of this program was to reduce memory requirements as much as possible. After much consideration and examination of several other \.{DVI} drivers, it was decided that operation of this program would occur in three distinct phases---prescan, font loading, and page drawing. Every \.{DVI} file is prescanned. During this phase, information about font usage is collected. A very simple interpreter simply notices the |bop| and |eop| tokens (to allow a subset of the pages to be printed), all of the |font_def| commands (the name and other information is stored), and the |set_char| commands (certain counters are incremented). For every font that is declared, space is allocated on the main |mem| array for a table of information regarding that font. This table contains that information that is necessary as determined from the \TeX\ font metric (\.{TFM}) files. During the second phase, the fonts are given a priority rating and the \.{TFM} information table is created. At this point, all the information about font usage in the file is located in the |mem| array. Therefore, a routine named |base_font| is called. This routine scans the declared fonts and assesses them as either printable ASCII fonts or as unprintable fonts. The printable fonts are given priority ratings based on the use of characters in each font. The font with priority 0 is the |base_font| and the horizontal raster resolution is based on spacings determined by that font. The vertical raster resolution is determined by the arbitrary assignment of a set number of lines to the printed page. Each character or word is then assigned a line in the page according to its vertical coordinate. The routine |load_tfm_file| creates a table for each font, declaring the width of each character in \.{DVI} units. Then a companion table is created with a raster unit width for each character. During phase three, the \.{DVI} file is reread one page at a time, and the characters are ``drawn'' onto a |page|. This is a character array which will be output to the device when the page is completed. Because of the coarse raster resolution of the device, several characters may be placed within the same array space, but the priority rating is used to determine which character shall appear there. Thus, a |priority| array is kept to determine the priority of any character appearing on the |page|. Also, because of differences in \TeX\ font spacing, blank spaces may appear in words if the characters were placed exactly in their raster positions. Instead, a |word| array positions a sequence of characters into adjacent raster positions and then attempts to reset the sequence into the space allocated by \TeX\ in the |page|. Such words are left-justified in the allocated space so that large spaces (or, possibly, one space) will appear between words in the |page|. We note that, if a sequence of characters is set from a font with smaller character sizes than the |base_font|, then the space allocated by \TeX\ for that word will be smaller than the raster width required. Hence, words consisting of characters from small fonts may be truncated. \vutex\ output is in one of two modes --- compressed or pure \TeX. In the pure \TeX\ mode, each page is printed as it was constructed in the array, i.e. each word is left-justified in the space provided by the \.{DVI} commands. Again, a number of spaces may appear between word, and the output can require a wide palette. Sub/super script lines will appear on separate associated lines. The compression mode will shrink inter-word spacing to one space, with the first character on each line appearing in its proper \TeX\ position, and all subsequent words are shifted to the left. Difficulties in associating sub/super script lines with the proper word in the baseline has forced a vertical compression of the associated lines into the baseline before the horizontal compression takes place. This may lead to ambiguous text, particularly with mathematical expressions, but it is recommended that such text be viewed in the unaltered non-compressed mode. The format of the |mem| array reflects these phases. It is organized as a large array of 32-bit words. The first 256 words are used as font pointers. They are initialized to zero at the beginning of the program. Then, whenever font number |k| is defined, 262 words are allocated in the |mem| array for the font, and |mem[k]| is set to point to the sixth word of the allocated space. The first allocated word, or |mem[mem[k]-6]|, consists of two sixteen bit integers. The least significant sixteen bits are the external or directory size of the font. The high order sixteen bits contain a pointer to the |names| array, which contains the name of the font. The next word (|mem[mem[k]-5]|) contains the |font_scaled_size| followed by the font design size. Then comes the |font_space|. The next word is |font_type| which determines the output array to use, followed by |font_status| to indicate if the font is a printable font or not. Finally comes the tabular information for the individual characters. They each use two words. The first word, |mem[mem[k]+2*f]|, contains the |tfm_width| of the character in \.{DVI} units. The next contains the |use_count| for the first pass, and the |raster_width| during the second pass. During the first pass, the first word of the first character in the table contains the checksum of the \.{TFM} file loaded from the \.{DVI} file. Hopefully this will match the checksum in the \.{TFM} file. The |names| array will contain all of the string information in the file. It is used as the input buffer from the terminal, the storage area for font names, temporary space for special's, etc. A `string' consists of a pointer into this array. The end of every string will be the value 0 as a marker. |next_names_free| indicates the end of the table. @ The following macroes should make the program slightly more readable, as they calculate the offsets, etc. automatically. @d font_desc_size=262 @d hi(#)==(# div 65536) @d lo(#)==(# mod 65536) @d raster_round(#)==round((#)/resol) + hh_offset @d line_round(#)==round((#)/vresol) + vv_offset @d set_line(#)== if line_for[#]<0 then begin line_for[#] := next_line_free; next_line_free:=next_line_free + page_width; end @d @!font_check_sum == mem[cur_fptr] {Temporary location for checksum} @d @!font_scaled_size == mem[cur_fptr-5] {Scaled size for current font} @d @!font_design_size == mem[cur_fptr-4] {Design size for current font} @d @!font_space == mem[cur_fptr-3] {Size of a thinspace in the font} @d @!font_type == mem[cur_fptr-2] {Code to determine type of font} @d @!declared == 1 @d @!other == 2 @d @!mexten == 3 @d @!msymbol == 4 @d @!mitalic == 5 @d @!tty == 6 @d @!roman == 7 @d @!font_status == mem[cur_fptr-1] {Priority of the current font} @d @!font_nsave == mem[cur_fptr-6] {For storing the name of the font} @d @!font_name == hi(mem[cur_fptr-6]) {High sixteen bits for font name ptr} @d @!directory_size == lo(mem[cur_fptr-6]) {Low sixteen bits for external size pointer} @d @!tfm_width == mem[cur_char_ptr] {\.{TFM} width of current character} @d @!use_count == mem[cur_char_ptr+1] {Use count used mostly in phase two.} @d @!raster_width == mem[cur_char_ptr+1] {character width in rasters, phase three} @d @!temp_ar(#) == mem[next_mem_free+#] {`array' used for temporary storage} @ We also need to define some of these variables, and initialize them. @= @!mem : array [0..max_mem_size] of integer ; {major memory array} @!names : array [0..name_size] of 0..127 ; {string array} @!cur_fptr, @!cur_char_ptr : integer ; {temporary pointers} @!next_mem_free, @!next_names_free : integer; { used for allocation } i,j : integer ; {plain old index variable} @ @= for i := 0 to 255 do mem[i] := 0 ; { no fonts defined yet.} next_mem_free := 256 ; { next available position in mem array } next_names_free := 1 ; { next available position in names array } @* Input from binary files. @= @!eight_bits=0..255; {unsigned one-byte quantity} @!byte_file=packed file of eight_bits; {files that contain binary data} @!word_file=packed file of integer; {for pixel file words} @ 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 \.{TFM} file from which character raster information is being read. The third file is the final output file, a text file. @= @!dvi_file:byte_file; {the stuff we are \.{DVI}typing} @!tfm_file:byte_file; {a font metric file} @!bit_file:text_file; {where the final output goes} @^system dependencies@> @ 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, we 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); cur_loc:=0; end; @# procedure reopen_dvi_file; {reopens the |dvi_file| for the next scan} begin reset(dvi_file); cur_loc:=0; end; @# procedure open_bit_file; {prepares final output for writing} begin rewrite(bit_file); bit_is_open := true ; end; @# procedure open_tfm_file; {opens \.{TFM} file} begin reset(tfm_file,cur_name); eof_tfm := eof(tfm_file); end; @# procedure open_input_text ; {prepares to read from general purpose input} begin reset(gen_input, cur_name) ; end ; @ |cur_loc| and |cur_name| are global variables: |cur_loc| is the number of the byte about to be read next from |dvi_file|, and |cur_name| is a string variable that will be set to the current \.{TFM} file name before |open_tfm_file| is called. @= @!cur_loc:integer; {where we are about to look, in |dvi_file|} @!cur_name:packed array[1..name_length] of char; {external name, with no lower case letters} @ We shall use another set of simple functions to read the next byte or bytes from |dvi_file|. There are seven possibilities, each of which is treated as a separate function in order to minimize the overhead for subroutine calls. @^system dependencies@> @p function get_byte:integer; {returns the next byte, unsigned} var b:eight_bits; begin if eof(dvi_file) then get_byte:=0 else begin read(dvi_file,b); incr(cur_loc); get_byte:=b; end; end; @# function signed_byte:integer; {returns the next byte, signed} var b:eight_bits; begin read(dvi_file,b); incr(cur_loc); 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(dvi_file,a); read(dvi_file,b); cur_loc:=cur_loc+2; get_two_bytes:=a*256+b; end; @# function signed_pair:integer; {returns the next two bytes, signed} var a,@!b:eight_bits; begin read(dvi_file,a); read(dvi_file,b); cur_loc:=cur_loc+2; 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(dvi_file,a); read(dvi_file,b); read(dvi_file,c); cur_loc:=cur_loc+3; 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(dvi_file,a); read(dvi_file,b); read(dvi_file,c); cur_loc:=cur_loc+3; 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(dvi_file,a); read(dvi_file,b); read(dvi_file,c); read(dvi_file,d); cur_loc:=cur_loc+4; 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; @* Reading the font information. \.{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 \vutex\ reads in a \.{TFM} file for each font that is used. @ It is, of course, a simple matter to print the name of a given font. @p procedure print_font(@!f:integer); var k:0..name_size; {index into |names|} cur_fptr : integer ; begin cur_fptr := mem[f]; if font_name=0 then print_ln('UNDEFINED!') @.UNDEFINED@> else begin k := font_name ; if names[k]>0 then begin while names[k]>0 do begin print(xchr[names[k]]) ; k := k + 1 ; end ; print(':'); end ; k := k + 1 ; while names[k]>0 do begin print(xchr[names[k]]); k := k + 1 ; end ; print_ln(' '); end; end; @ We need a function that will read in a word from the \.{TFM} file. If the particular system @^system dependencies@> requires buffering, here is the place to do it. It also sets a global flag |eof_tfm| when it reaches the end of the file. If this flag is set on entrance to |load_tfm_file|, it is assumed that the file is bad. @p function tfm_integer : integer ; var i:integer; begin read(tfm_file, i); eof_tfm:=eof(tfm_file); tfm_integer:=i; end; @ There is nothing wrong with defining |eof_tfm| here. @= @!eof_tfm:boolean; {true when end of \.{TFM} file is reached.} @ The most important part of |load_tfm_file| is the conversion to \.{DVI} units, which involves multiplying the relative values in the \.{TFM} file by the scaling factor in the \.{DVI} file. This fixed-point multiplication must be done with precisely the same accuracy by all \.{DVI}-reading programs, in order to validate the assumptions made by \.{DVI}-writing programs like \TeX82. Let us therefore summarize what needs to be done. Each width in a \.{TFM} file appears as a four-byte quantity called a |fix_word|. A |fix_word| whose respective bytes are $(a,b,c,d)$ represents the number $$x=\left\{\vcenter{\halign{$#$,\hfil\qquad&if $#$\hfil\cr b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=0;\cr -16+b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=255.\cr}}\right.$$ (No other choices of $a$ are allowed, since the magnitude of a \.{TFM} dimension must be less than 16.) We want to multiply this quantity by the integer~|z|, which is known to be less than $2^{27}$. Let $\alpha=16z$. If $|z|<2^{23}$, the individual multiplications $b\cdot z$, $c\cdot z$, $d\cdot z$ cannot overflow; otherwise we will divide |z| by 2, 4, 8, or 16, to obtain a multiplier less than $2^{23}$, and we can compensate for this later. If |z| has thereby been replaced by $|z|^\prime=|z|/2^e$, let $\beta=2^{4-e}$; we shall compute $$\lfloor(b+c\cdot2^{-8}+d\cdot2^{-16})\,z^\prime/\beta\rfloor$$ if $a=0$, or the same quantity minus $\alpha$ if $a=255$. This calculation must be done exactly, for the reasons stated above; the following program does the job in a system-independent way, assuming that arithmetic is exact on numbers less than $2^{31}$ in magnitude. @p function tfm_to_int(val:integer) : integer ; var alpha, beta, x, z:integer ; {help in converting widths} b0, b1, b2, b3:eight_bits ; {temporary byte storage} begin z := font_scaled_size ; @; @; z:=(((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta ; if b0=255 then z := z - alpha ; tfm_to_int := z ; end ; @ @= begin x:=val ; if x>=0 then b0:=x div @'100000000 else begin x:=(x+@'10000000000)+@'10000000000; b0:=x div @'100000000+128; end ; x:=x mod @'100000000; b1:=x div @'200000;x:=x mod @'200000;b2:=x div @'400;b3:=x mod @'400; end; @ @= begin alpha:=16*z; beta:=16; while z>=@'40000000 do begin z:=z div 2; beta:=beta div 2; end; end ; @ And, finally, we have the procedure which will extract the information from the \.{TFM} file. This routine is for input from the \.{TFM} file, which is (or should be) a |packed file of integer|, so this should cause no grief. See the documentation on |tftopl| for the structure of the file. @ @p procedure load_tfm_file ; {read in the width data} label 9997, {used for bad format} 9999; {used for normal exit} var r:integer; {used for |cur_name| manipulation} i, k : integer ; {used for general pointer} bc, ec : integer ; {first and last characters in font} @!ptr : integer; {pointer to width table} begin @ ; if out_mode > 0 then print_ln('Trying to load ',cur_name) ; open_tfm_file ; k := next_mem_free ; if eof_tfm then goto 9997 ; while not eof_tfm do begin mem[k] := tfm_integer ; k := k + 1 ; if k > max_mem_size then abort(clone,' memory size exceeded on load of tfm file!') ; end ; bc := hi(mem[next_mem_free+1]) ; ec := lo(mem[next_mem_free+1]) ; if mem[next_mem_free+6]<>font_check_sum then print_ln('Checksum in tfm file does not match that in dvi file!') ; k := next_mem_free + lo(mem[next_mem_free]) + 6 - bc ; cur_char_ptr := cur_fptr ; for i := 0 to bc-1 do begin tfm_width := 0 ; cur_char_ptr := cur_char_ptr + 2 ; end ; for i := bc to ec do begin if mem[k+i] > 0 then ptr := mem[k+i] div @'100000000 else ptr := ((mem[k+i] + one_fourth) + one_fourth) div @'100000000 + 128 ; tfm_width := tfm_to_int(mem[ptr + ec + k + 1 ]) ; cur_char_ptr := cur_char_ptr + 2 ; end ; for i := ec+1 to 127 do begin tfm_width := 0 ; cur_char_ptr := cur_char_ptr + 2 ; end ; goto 9999 ; 9997: print_ln('---not loaded, TFM file is bad') ; 9999: end ; @* User interface. \vutex\ is normally considered a filter, and therefore operates very quietly unless some error is encountered. However, for debugging purposes or the inquisitive, one can access the variable |out_mode|. This variable will allow the tracing of \vutex. It has two possible values: 0, and 1. If it is zero, then the program is very quiet, only informing the user of errors. If it is one, a trace is executed for debugging purposes. There are a few optional parameters requested when this program is invoked. It is possible to print only a restricted subset of the pages by specifying the desired starting page and the maximum number of pages. You also may select the number of rasters across a page and the compression mode for output. The starting page is specified by giving a sequence of 1 to 10 numbers or asterisks separated by dots. For example, the specification `\.{1.*.-5}' can be used to refer to a page output by \TeX\ when $\.{\\count0}=1$ and $\.{\\count2}=-5$. (Recall that |bop| commands in a \.{DVI} file are followed by ten `count' values.) An asterisk matches any number, so the `\.*' in `\.{1.*.-5}' means that \.{\\count1} is ignored when specifying the first page. If several pages match the given specification, \vutex\ will begin with the earliest such page in the file. The default specification `\.*' (which matches all pages) therefore denotes the page at the beginning of the file. When \vutex\ begins, it engages the user in a brief dialog so that the options will be specified. This part of \vutex\ requires nonstandard \PASCAL\ constructions to handle the online interaction; so it may be preferable in some cases to omit the dialog and simply to stick to the default options (|out_mode=terse|, starting page `\.*', |max_pages=1000000|, |print_width=160|, and |compress=false|). On other hand, the system-dependent routines that are needed are not complicated, so it will not be terribly difficult to introduce them. Normally, however, the necessary parameters should be taken from the command line which invokes the program (if this is possible.) Then, this part of the program would not even be used. This is in the domain of the change file, however. @^system dependencies@> @d errors_only=0 {value of |out_mode| when minimal printing occurs} @d terse=1 {value of |out_mode| for abbreviated output} @= @!out_mode:errors_only..terse; {controls the amount of output} @!max_pages:integer; {at most this many |bop..eop| pages will be printed} @!page_width:integer; {page width for setting characters} @!num_lines:integer; {number of lines in a page} @!print_width: integer; {the number of raster units across screen before truncation} @!print_height: integer; {the number of lines printed on each screen} @!compress: boolean; {true if interword spacing to be compressed to one blank} @!batch_mode: boolean; {true if pages to be processed with no interaction} @ The starting page specification is recorded in two global arrays called |start_count| and |start_there|. For example, `\.{1.*.-5}' is represented by |start_there[0]=true|, |start_count[0]=1|, |start_there[1]=false|, |start_there[2]=true|, |start_count[2]=-5|. We also set |start_vals=2|, to indicate that count 2 was the last one mentioned. The other values of |start_count| and |start_there| are not important, in this example. @= @!start_count:array[0..9] of integer; {count values to select starting page} @!start_there:array[0..9] of boolean; {is the |start_count| value relevant?} @!start_vals:0..9; {the last count considered significant} @!count:array[0..9] of integer; {the count values on the current page} @ @= out_mode := errors_only; max_pages:=1000000; start_vals:=0; start_there[0]:=false; page_width:= dfl_p_width; num_lines:=dfl_n_lines; print_width:= dfl_screen_width; print_height:= dfl_screen_height; compress:=false;batch_mode:=false; @ Here is a simple subroutine that tests if the current page might be the starting page. @p function start_match:boolean; {does |count| match the starting spec?} var k:0..9; {loop index} @!match:boolean; {does everything match so far?} begin match:=true; for k:=0 to start_vals do if start_there[k]and(start_count[k]<>count[k]) then match:=false; start_match:=match; end; @ The |input_ln| routine waits for the user to type a line at his or her terminal; then it puts ASCII-code equivalents for the characters on that line into the |buffer| array. The |term_in| file is used for terminal input, and |term_out| for terminal output. @^system dependencies@> @= @!buffer:array[0..terminal_line_length] of ASCII_code; @!term_in:text_file; {the terminal, considered as an input file} @!term_out:text_file; {the terminal, considered as an output file} @ Since the terminal is being used for both input and output, some systems need a special routine to make sure that the user can see a prompt message before waiting for input based on that message. (Otherwise the message may just be sitting in a hidden buffer somewhere, and the user will have no idea what the program is waiting for.) We shall call a system-dependent subroutine |update_terminal| in order to avoid this problem. @^system dependencies@> @d update_terminal == break(term_out) {empty the terminal output buffer} @ During the dialog, \vutex\ will treat the first blank space in a line as the end of that line. Therefore |input_ln| makes sure that there is always at least one blank space in |buffer|. @^system dependencies@> @p procedure input_ln; {inputs a line from the terminal} var k:0..terminal_line_length; begin update_terminal; reset(term_in); if eoln(term_in) then read_ln(term_in); k:=0; while (k= @!buf_ptr:0..terminal_line_length; {the number of characters read} @ Here is a routine that scans a (possibly signed) integer and computes the decimal value. If no decimal integer starts at |buf_ptr|, the value 0 is returned. The integer should be less than $2^{31}$ in absolute value. @p function get_integer : integer; var x:integer; {accumulates the value} @!negative:boolean; {should the value be negated?} begin if buffer[buf_ptr]="-" then begin negative:=true; incr(buf_ptr); end else negative:=false; x:=0; while (buffer[buf_ptr]>="0")and(buffer[buf_ptr]<="9") do begin x:=10*x+buffer[buf_ptr]-"0"; incr(buf_ptr); end; if negative then get_integer:=-x @+ else get_integer:=x; end; @ The selected options are put into global variables by the |dialog| procedure, which is called just as \vutex\ begins. @^system dependencies@> @p procedure dialog; var k:integer; {loop variable} dum_width:integer; dum_height:integer; begin rewrite(term_out); {prepare the terminal for output} dum_width := 0; dum_height := 0; @; @; @; @; @; @; @; @; @; end; @ @= write(term_out,'Interactive mode (default=Y): '); batch_mode := false; input_ln; buf_ptr:=0; if (buffer[buf_ptr] = "N") or (buffer[buf_ptr] = "n") then batch_mode:=true ; @ @= write(term_out,'Starting page (default=*): '); start_vals:=0; start_there[0]:=false; input_ln; buf_ptr:=0; k:=0; @ @ These two routines have been split in half so it is easier to accept these options from the command line if your operating system makes that possible. Put a question mark at the end of the line to end it when doing it this way. @= if (buffer[buf_ptr] <> "?") and (buffer[buf_ptr] <> " ") and (buffer[buf_ptr] <> "/") then begin repeat if buffer[buf_ptr]="*" then begin start_there[k]:=false; incr(buf_ptr); end else begin start_there[k]:=true; start_count[k]:=get_integer; end; if (k<9)and(buffer[buf_ptr]=".") then begin incr(k); incr(buf_ptr); end else if (buffer[buf_ptr]=" ") or (buffer[buf_ptr]="/") or (buffer[buf_ptr]="?") then start_vals:=k else begin k := 0 ; start_vals := 0 ; end ; until start_vals=k ; end @ @= write(term_out,'Maximum number of pages (default=1000000): '); max_pages := 1000000; input_ln; buf_ptr:=0; @ @ @= begin max_pages:=get_integer; if max_pages=0 then max_pages := 1000000 ; end @ @= write(term_out,'Width of output in characters', ' (default=160/batch, 80/interactive): '); input_ln; buf_ptr:=0; dum_width:=get_integer @ @= write(term_out,'Number of printed lines per screen', ' (default=280/batch, 22/interactive): '); input_ln; buf_ptr:=0; dum_height:=get_integer @ @= write(term_out,'Compress interword spaces, Y or N (default=N): '); input_ln; buf_ptr:=0; compress:=false ; if (buffer[buf_ptr] = "Y") or (buffer[buf_ptr] = "y") then compress:=true ; @ We may have to adjust the page dimensions to fit on the internal array. If the compression mode has been chosen, we must allow extra room for the uncompressed text. @ @= if dum_width=0 then if batch_mode then print_width := page_width else print_width := dfl_screen_width else print_width := dum_width; if compress and (print_width > (page_width * 3 div 4)) then page_width := 4 * print_width div 3 else if (not compress) and (print_width > page_width - hh_offset) then page_width := print_width + hh_offset; if page_width > max_p_width then begin page_width := max_p_width; print_width := page_width - hh_offset; end; num_lines := total_rast div page_width; if dum_height=0 then if batch_mode then print_height := num_lines else print_height := dfl_screen_height else print_height := dum_height; if print_height > num_lines then print_height := num_lines @ After the dialog is over, we print the options so that the user can see what \vutex\ thought was specified. This is only done if the |out_mode| is greater than 0, so we can run quietly usually. @= if out_mode > 0 then begin print_ln('Options selected:'); @.Options selected@> print(' Starting page = '); for k:=0 to start_vals do begin if start_there[k] then print(start_count[k]:1) else print('*'); if k= if not batch_mode then begin print_ln(' '); print_ln(' At the end of each printed screen, enter one of:'); print_ln(' L - left, R - right, U - up, P - new page, - down'); print_ln(' '); end; @ Another procedure that is included for debugging and monitoring purposes is the |diagnostics| procedure. This procedure prints out the memory usage of \vutex\. Again, this is only done if the |out_mode| is greater than zero. @p procedure diagnostics ; begin if out_mode > 0 then begin print_ln('Number of pages processed: ', actual_page_count:1) ; print_ln('String pool usage: ',next_names_free:1, ' out of ', name_size:1, ' bytes') ;end ; end ; @* Defining fonts. In \vutex, a font definition should only be recognized while the scanning is not in the postamble, and when the pass is one. A global variable |in_postamble| is provided to tell whether we are processing the postamble or not. @= @!in_postamble:boolean; {are we reading the postamble?} @ @= in_postamble:=false; @ The following subroutine does the necessary things when a \\{fnt\_def} command is being processed. @p procedure define_font(@!f:integer); {|f| is an external font number} var @!p:integer; {length of the area/directory spec} @!n:integer; {length of the font name proper} @!c,@!q,@!d:integer; {check sum, scaled size, and design size} @!k:0..name_size; {indices into |names|} begin if not in_postamble then begin if (f>255) then abort('Only font definitions 0..255 are valid in ', clone) ; @.vutex capacity exceeded...@> if (mem[f]<>0) then abort('Font number ',f:1,' already defined!') ; @.Font already defined@> cur_fptr := next_mem_free+6 ; mem[f]:=cur_fptr ; next_mem_free := next_mem_free + font_desc_size ; @; end ; end ; @ This routine takes the data from the \.{DVI} file about the current font, and loads it into memory. It also initializes the |use_count| for each character. @= c:=signed_quad; font_check_sum:=c;@/ q:=signed_quad; font_scaled_size := q ; d:=signed_quad; font_design_size := d ; font_space:=d div 6;@/ p:=get_byte; n:=get_byte; if next_names_free + p + n + 2 > name_size then abort(clone,' capacity exceeded (name size=',name_size:1,')!'); @.vutex capacity exceeded...@> font_nsave := next_names_free * 65536 ; if out_mode > 0 then print('Font ',f:1,': '); for k := 1 to p do begin names[next_names_free] := get_byte ; next_names_free := next_names_free + 1 ; end ; names[next_names_free] := 0 ; next_names_free := next_names_free + 1 ; for k := 1 to n do begin names[next_names_free] := get_byte ; next_names_free := next_names_free + 1 ; end ; names[next_names_free] := 0 ; next_names_free := next_names_free + 1 ; cur_char_ptr := cur_fptr ; for k := 0 to 127 do begin {initialize use counts} use_count := 0 ; cur_char_ptr := cur_char_ptr + 2 ; end ; font_type := declared ; if out_mode > 0 then print_font(f) ; @ If |p=0|, i.e., if no font directory has been specified, \vutex\ is supposed to use the default font directory, which is a system-dependent place where the standard font \.{TFM} files are kept. @^system dependencies@> @d tfm_directory_name=='TeXfonts:' @d tfm_directory_name_length=9 @= @!tfm_directory:packed array[1..tfm_directory_name_length] of char; @ We stick this initial value into the |names| array for later use. @= tfm_directory:=tfm_directory_name; @ The string |cur_name| is supposed to be set to the external name of the \.{TFM} file for the current font. @^system dependencies@> @= begin for k:=1 to name_length do cur_name[k]:=' '; r := 1 ; k := font_name ; if names[k]=0 then begin for i:=1 to tfm_directory_name_length do cur_name[i]:=tfm_directory[i]; r:=tfm_directory_name_length+1; end else begin while names[k] > 0 do begin cur_name[r] := xchr[names[k]] ; r := r + 1 ; k := k + 1 ; end ; end ; k := k + 1 ; while names[k] > 0 do begin cur_name[r] := xchr[names[k]] ; r := r + 1 ; k := k + 1 ; end ; cur_name[r] := '.' ; r := r + 1 ; cur_name[r] := 'T' ; r := r + 1 ; cur_name[r] := 'F' ; r := r + 1 ; cur_name[r] := 'M' ; r := r + 1 ; for k := 1 to r do if (cur_name[k]>='a')and(cur_name[k]<='z') then cur_name[k]:=xchr[xord[cur_name[k]]-@'40] ; end ; @* Special execution. In this implementation, special commands are ignored. However, the structure to handle such remains as a skeleton if some special procedures are desired. We need a procedure that will handle any special commands that might appear. Commands must have the following format: \medskip \line{\hskip20pt command(parameters$\ldots$)\hfil} \medskip If there is more than one command in a special, they must be separated by spaces. The parameters are also separated by spaces. The string that was contained in the special command is in the names array starting at |next_names_free| and ending with a 0. Also, the |prescan| boolean should be checked to see if this is the first or second time that this string was encountered. @p procedure do_special ; begin end ; @ This is where the special commands are interpreted. We accumulate the string, and pass a pointer to it to a routine called |do_special|. Note that this routine is called twice, once for the prescan, and once for the actual processing. This way, special commands that set up the device for a particular mode can be included and processed during the second pass. During pass one, specials on pages before the first page to be processed are also passed, as they might contain configuration information. @= begin bad_char:=false; if p+next_names_free > name_size then abort('Out of string space during special!') ; @.Out of string space during special@> for k:=1 to p do begin q:=get_byte; if (q<" ")or(q>"~") then bad_char:=true; end; if bad_char then print_ln('non-ASCII character in xxx command!'); @.non-ASCII character...@> do_special ; end ; @* Interpreting the page commands. The main work of \vutex\ is accomplished by the |do_page| 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. Of course, this is for the second pass. A |prescan| routine is still necessary to load the font information. @ The definition of \.{DVI} files refers to six registers, $(h,v,w,x,y,z)$, which hold integer values in \.{DVI} units. In practice, we also need registers |hh| and |vv|, the raster analogs of $h$ and $v$, since it is not always true that |hh=raster_round(h)| or |vv=line_round(v)|. The stack of $(h,v,w,x,y,z)$ values is represented by eight arrays called |hstack|, \dots, |zstack|, |hhstack|, and |vvstack|. @= @!h,@!v,@!w,@!x,@!y,@!z,@!hh,@!vv:integer; {current state values} @!hstack,@!vstack,@!wstack,@!xstack,@!ystack,@!zstack: array [0..stack_size] of integer; {pushed down values in \.{DVI} units} @!hhstack,@!vvstack: array [0..stack_size] of integer; {pushed down values in rasters} @ Three characteristics of the pages (their |max_v|, |max_h|, and |max_s|) are specified in the postamble, and a warning message is printed if these limits are exceeded. Actually |max_v| is set to the maximum height plus depth of a page, and |max_h| to the maximum width, for purposes of page layout. Since characters can legally be set outside of the page boundaries, it is not an error when |max_v| or |max_h| is exceeded. But |max_s| should not be exceeded. The postamble also specifies the total number of pages; \vutex\ checks to see if this total is accurate. @= @!max_v:integer; {the value of |abs(v)| should probably not exceed this} @!max_h:integer; {the value of |abs(h)| should probably not exceed this} @!max_s:integer; {the stack depth should not exceed this} @!max_v_so_far,@!max_h_so_far,@!max_s_so_far:integer; {the record high levels} @!page_count:integer; {the total number of pages seen so far} @!hpos_ed, @!vpos_ed : boolean ; {are we positioned in this respect?} @ @= max_v:=@'17777777777-99; max_h:=@'17777777777-99; max_s:=stack_size+1;@/ max_v_so_far:=0; max_h_so_far:=0; max_s_so_far:=0; page_count:=0; @ Before we get into the details of |do_page|, 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) @p function first_par(o:eight_bits):integer; begin case o of sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64): abort('Can''t call first_par like this.') ; 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; @ We need a routine to skip over a font definition. This is very simple, just requires skipping over the parameters: @p procedure skip_font_def ; var @!dumq, @!dumi, i : integer ; begin dumq := signed_quad ; dumq := signed_quad ; dumq := signed_quad ; dumi := get_byte + get_byte ; for i := 1 to dumi do dumi := get_byte ; end ; @ A useful constant: @= one_fourth = 1073741824 ; @ Strictly speaking, the |do_page| procedure is really a function with side effects, not a `\&{procedure}'; it returns the value |false| if \vutex\ should be aborted because of some unusual happening. The subroutine is organized as a typical interpreter, with a multiway branch on the command code followed by |goto| statements leading to routines that finish up the activities common to different commands. We will use the following labels: @d fin_set=41 {label for commands that set or put a character} @d fin_rule=42 {label for commands that set or put a rule} @d move_right=43 {label for commands that change |h|} @d move_down=44 {label for commands that change |v|} @d show_state=45 {label for commands that change |s|} @d change_font=46 {label for commands that change |cur_font|} @ Now we have the actual routine that draws a character. It simply sets the character pointer and calls the routine to set the character in the page. @p procedure draw_char(p:integer) ; @ begin cur_char_ptr := cur_fptr + 2*p ; dev_char_draw ; hpos_ed := false ; end ; @ Simple horizontal or vertical rules can be created from underscores and vertical bars. They are given low priority and will be replaced by all other characters. If a vertical line segment is the only character to appear on a line in the page, then that line will not be printed. @p procedure dev_rule_draw(rh, rw: integer) ; label done ; var position : integer ; i,j : integer ; begin if line_for[vv] < 0 then begin line_for[vv] := next_line_free ; next_line_free := next_line_free + page_width ; end; position := line_for[vv] ; j := position + hh + 1 ; if j < position + page_width then if rw >= rh then begin for i := 1 to rw do begin if (page[j] = xchr[32]) and (j > position) then page[j] := xchr[95] else if page[position] = xchr[32] then page[position] := xchr[62] ; incr(j) ; if (j >= position + page_width) then begin if page[position] = xchr[32] then page[position] := xchr[62] ; goto done ; end end ; end else if j > position then begin for i := 1 to rh do begin if page[j] = xchr[32] then page[j] := xchr[124] else page[j-hh-1] := xchr[62] ; if line_for[vv-i] < 0 then begin line_for[vv-i] := next_line_free ; next_line_free := next_line_free + page_width ; end ; position := line_for[vv-i] ; j := position + hh + 1 ; if j < 0 then goto done ; end ; end ; done: end ; @ On entrance to this routine, the character to be drawn will be in |p|, the font in |cur_fptr|. The routine should be responsible for insuring that the device is set to the correct location on the page (|hh| and |vv|) and that the current font is selected. The characters are drawn sequentially into a word array. @= procedure dev_char_draw ; var position : integer ; {points to beginning of raster row} i : integer ; c : char ; {the ASCII character to be set} begin if not set_word then @ ; incr(in_word) ; position := line_for[word_vv] ; hpos_ed := true ; vpos_ed := true ; if (hh >= 0 ) then if (word_hh+in_word < page_width) then begin i := position+ word_hh + in_word ; if (i < total_rast) then if (ord(priority[i]) > font_status) and (ord(word_priority[in_word]) > font_status) then begin case font_type of roman: @ ; tty: c := xchr[p]; mitalic: c := mchr[p]; msymbol: c := schr[p]; mexten: c := echr[p]; othercases c := dfl_chr endcases ; word[in_word] := c ; word_priority[in_word] := xchr[font_status] ; end else if page[position] = xchr[32] then page[position] := xchr[62] else if page[position] = xchr[32] then page[position] := xchr[62] ; end else if page[position] = xchr[32] then page[position] := xchr[62] else if page[position] = xchr[32] then page[position] := xchr[62] ; if font_status > 0 then if (page[position] = xchr[32]) or (page[position]=xchr[62]) then page[position] := xchr[42]; end ; @ Variables must be initialized at the beginning of a new word. If the word is appearing on a new line, the first space in the priority array is used to flag the line as a baseline or an associated sub/super script line. @= begin set_word := true ; word_hh := hh; word_vv := vv ; in_word := 0; if line_for[vv] < 0 then begin line_for[vv] := next_line_free ; next_line_free := next_line_free + page_width ; end ; if prev_vv >= 0 then begin if (priority[line_for[vv]]=xchr[126]) then if prev_vv=vv-1 then begin page[line_for[vv]] := '-' ; priority[line_for[vv]] := '-' ; end else if prev_vv=vv+1 then begin page[line_for[vv]] := '+' ; priority[line_for[vv]] := '+' ; end else priority[line_for[vv]] := 'b' end else priority[line_for[vv]] := 'b' ; prev_vv := vv ; for i := 1 to page_width do begin word[i] := ' ' ; word_priority[i] := xchr[126] ; end ; end @ The layout for the standard roman fonts is slightly different from the ASCII table. @d ff == @'13 {these define the ligatures in roman fonts} @d fi == @'14 @d fl == @'15 @d ffi == @'16 @d ffl == @'17 @d ss == @'31 @d ae == @'32 @d oe == @'33 @d AE == @'35 @d OE == @'36 @= case p of ff: begin word[in_word] := rchr[102] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[102] ; end ; fi: begin word[in_word] := rchr[102] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[105] ; end ; fl: begin word[in_word] := rchr[102] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[108] ; end ; ffi: begin word[in_word] := rchr[102] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; word[in_word] := rchr[102] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[105] ; end ; ffl: begin word[in_word] := rchr[102] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; word[in_word] := rchr[102] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[108] ; end ; ss: begin word[in_word] := rchr[115] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[115] ; end ; ae: begin word[in_word] := rchr[97] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[101] ; end ; oe: begin word[in_word] := rchr[111] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[101] ; end ; AE: begin word[in_word] := rchr[65] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[69] ; end ; OE: begin word[in_word] := rchr[79] ; word_priority[in_word] := xchr[font_status] ; incr(in_word) ; c := rchr[69] ; end ; othercases c:= rchr[p] endcases @ We need the global page arrays @= @!page : array [0..total_rast] of text_char ; {the page of data} @!priority : array [0..total_rast] of text_char ; {priority array} @!line_for : array [1..dfl_n_lines] of integer ; {points to line in array} @!next_line_free : integer ; {points to the next free position in array} @!word : array [0..max_p_width] of text_char ; {string to hold complete word} @!word_priority : array [0..max_p_width] of text_char ; {priority of each char} @!in_word : integer ; {points to location in word} @!set_word : boolean ; {set to true if a word has begun} @!word_hh : integer ; {hh value where word began} @!word_vv : integer ; {vv value of first character in word} @!min_hh,@!min_vv : integer ; {minimum hh, vv values on the page} @!prev_vv: integer ; @ This routine will reset the characters in a word if the positioning has left holes or missing characters. @p procedure reset_word ; var last:integer; {position of last character in word to be reset} i,j,position : integer ; begin set_line(vv) ; if word_hh < min_hh then min_hh := max(0,word_hh) ; if word_vv < min_vv then min_vv := max(0,word_vv) ; if hh > word_hh then begin position := line_for[word_vv] ; j := position + word_hh + 1 ; last := hh - word_hh + 1 ; if last > in_word then last := in_word ; for i := 1 to last do begin if j > position then if priority[j] > word_priority[i] then begin page[j] := word[i] ; priority[j] := word_priority[i] ; end else if page[position] = xchr[32] then page[position] := xchr[62] else if page[position] = xchr[32] then page[position] := xchr[62] ; incr(j) ; end ; end ; set_word := false ; end ; @ This routine is called at the beginning of each page, before anything is processed. @p procedure start_page ; var i : integer ; begin hpos_ed := false ; vpos_ed := false ; for i := 0 to total_rast do begin page[i] := xchr[32]; priority[i] := xchr[126] ; end; for i := 1 to num_lines do line_for[i] := -1; next_line_free := 0; min_hh :=hh_offset ; min_vv := vv_offset ; prev_vv := -1 ; end ; @ These constants define the key strokes for interactive paging action. @= @!u_left = "L"; l_left="l"; {key for move left on page} @!u_right= "R"; l_right="r"; {key for move right on page} @!u_up= "U"; l_up="u"; {key for move up on page} @!u_page= "P"; l_page="p"; {key for move to next page} @!u_comp= "C"; l_comp="c"; {key for compress text on page} @ When this routine is called, a full page has been processed, and the page array is sent to the output device. @p procedure finish_page ; label done; var i,j,k: integer ; {index counters} last_hh: integer ; {raster column of furthest right character} last_vv: integer ; {raster row of furthest down character} first_p_hh: integer ; {raster column of first printed character} last_p_hh: integer ; {raster column of last printed character} first_p_row: integer ; {row number of first printed row} last_p_row: integer ; {row number of last printed row} rows_printed: integer ; raster: integer ; jspace: integer ; shift: integer ; next_row: integer ; position: integer ; average: integer; {average spacing between baselines} menu : integer ; {used to interactively select page-action} printing_page : boolean ; begin first_p_hh := min_hh ; first_p_row := min_vv ; last_hh := raster_round(max_h_so_far) + 1 ; last_vv := line_round(max_v_so_far) ; if last_hh >= page_width then last_hh := page_width - 1 ; @ ; if compress then @ ; printing_page := true; position := first_p_row; while (printing_page) do begin last_p_hh := first_p_hh + print_width-2; if last_p_hh >= page_width then last_p_hh := page_width; write_ln(bit_file); i := first_p_row; rows_printed := 0; while (i <= last_vv) and (rows_printed < print_height) do begin if line_for[i] >= 0 then begin position := i ; raster := line_for[i] ; if raster > total_rast-last_hh-1 then goto done ; write(bit_file,page[raster]) ; k := raster + last_p_hh ; while page[k] = xchr[32] do decr(k) ; k := k - raster - 1 ; raster := raster + first_p_hh ; for j := first_p_hh to k do begin write(bit_file,page[raster]) ; incr(raster) ; end ; write_ln(bit_file,page[raster]) ; incr(rows_printed) ; end else if (average >0) and (i-position > average) then begin write_ln(bit_file) ; position := i ; incr(rows_printed) ; end ; incr(i); end; done: @ ; end; end ; @ Since each physical line is represented by perhaps several lines in the page arrays, we try to determine the spacing between physical lines by taking the average number of blank lines in the array between nonempty lines. This is used to approximate the number of blank lines in a vertical shift in \TeX. Note that a page number at the bottom of a sparse page may distort the average. Hence if the last line is set apart substantially, we will ignore it. @= begin average := 0 ; k:= 0 ; i := 1 ; j := line_for[i] ; while (i <= last_vv) and ((j < 0) or (priority[j] <>'b')) do begin incr(i); j:= line_for[i]; end; position := i ; incr(i) ; while i <= last_vv do begin j := line_for[i] ; if (j>=0) then if (priority[j]='b') then begin shift := i - position ; average := average + shift ; incr(k) ; position := i ; end; incr(i); end ; if k > 1 then begin if shift > 1.1*round(average*1.0/k) then begin k := k - 1 ; average := average - shift ; end; if k > 1 then average := round(average*1.0 / k ) end; end @ If the compress option is chosen, the text is compressed vertically so that associated sub/super script lines are pushed into the baseline. Then the horizontal spacing is abandonned and interword spacing is compressed to one blank space. @= begin for i := min_vv to last_vv do if line_for[i]>=0 then if priority[line_for[i]]='+' then begin raster := line_for[i] ; position := line_for[i+1] ; for j := min_hh to last_hh do if (page[raster+j]<>xchr[32]) then if (page[position+j]=xchr[32]) or (priority[raster+j]=0 then if priority[line_for[i]]='-' then begin raster := line_for[i] ; position := line_for[i-1] ; for j := min_hh to last_hh do if (page[raster+j]<>xchr[32]) then if (page[position+j]=xchr[32]) or (priority[raster+j]=0 then begin raster := line_for[i] + 1 ; next_row := raster + page_width - 1; while (page[raster] = ' ') and (raster < next_row) do incr(raster) ; while (page[raster] <> ' ') and (raster < next_row) do incr(raster) ; jspace := raster ; while raster < next_row do begin while (page[raster] = ' ') and (raster < next_row) do incr(raster) ; shift := raster - jspace - 1 ; while (page[raster] <> ' ') and (raster < next_row) do begin page[raster-shift] := page[raster] ; incr(raster) ; end ; for k := raster - shift to raster - 1 do page[k] := ' ' ; jspace := raster - shift ; end ; end ; last_hh := min(last_hh,jspace); end @ When the portion of the page is printed, either the interactive user will be given a menu of options to chose from or the batch job will continue. @d null = 0 @d left_arrow = 1 @d right_arrow = 2 @d up_arrow = 3 @d down_arrow = 4 @d comp = 5 @d next_page = 6 @ = begin if batch_mode then @ else begin last_p_row := i-1 ; @ = begin rewrite(term_out); write(term_out,'Hit key for page action.. '); input_ln; buf_ptr :=0; menu := null; if (buffer[buf_ptr] = u_left) or (buffer[buf_ptr] = l_left) then menu := left_arrow else if (buffer[buf_ptr] = u_right) or (buffer[buf_ptr] = l_right) then menu := right_arrow else if (buffer[buf_ptr] = u_up) or (buffer[buf_ptr] = l_up) then menu := up_arrow else if (buffer[buf_ptr] = u_page) or (buffer[buf_ptr] = l_page) then menu := next_page else if (buffer[buf_ptr] = u_comp) or (buffer[buf_ptr] = l_comp) then menu := comp else menu := down_arrow; incr(buf_ptr); end @ At the end of each page, a graded horizontal rule will be drawn. The gradations will be every 2 cm. measured from point with dvi value 0. @ = begin write_ln(bit_file); position := trunc(200000.0/num*den/resol) ; write(bit_file,' _ cm --> '); i := 0; while i <= last_p_hh do begin if i > first_p_hh + 9 then if i mod position = 0 then begin if 2*(i div position) < 10 then write(bit_file,(2*(i div position)):1,'_') else write(bit_file,(2*(i div position)):2); incr (i); end else write(bit_file,'_') ; incr (i); end; write_ln(bit_file); printing_page := false; end  @ Some \PASCAL\ compilers severely restrict the length of procedure bodies, so we shall split |do_page| into two parts, one of which is called |spcl_cases|. The different parts communicate with each other via the global variables mentioned above, together with the following ones: @= @!s:integer; {current stack size} @!cur_font:integer; {current internal font number} @!a:integer; {byte number of the current command} @ Here is the overall setup. @p @t\4@>@@; function do_page:boolean; label fin_set,fin_rule,move_right,show_state,done,9998,9999; var o:eight_bits; {operation code of the current command} @!p,@!q:integer; {parameters of the current command} @!hhh:integer; {|h|, rounded to the nearest pixel} begin start_page; cur_font:=0; {set current font undefined} while mem[cur_font]>0 do cur_font := cur_font + 1 ; s:=0; h:=0; v:=0; w:=0; x:=0; y:=0; z:=0; hh:=raster_round(0); vv:=line_round(0); {initialize the state variables} cur_fptr := 0 ; set_word := false ; while true do @; 9998: print_ln('!'); do_page:=false; 9999: end; @ @= begin a:=cur_loc ; o:=get_byte; if o > 127 then p:=first_par(o); if eof(dvi_file) then bad_dvi('the file ended prematurely'); @.the file ended prematurely@> @; fin_set: @; fin_rule: @; move_right: @; show_state: @; done:end @ The multiway switch in |first_par|, above, was organized by the length of each command; the one in |do_page| is organized by the semantics. @= if o@@; @t\4@>@@; othercases if spcl_cases(o,p) then goto done@+else goto 9998 endcases ; @ @= function spcl_cases(@!o:eight_bits;@!p:integer):boolean; label change_font,move_down,done,9998; var q:integer; {parameter of the current command} @!k:integer; {loop index} @!bad_char:boolean; {has a non-ASCII character code appeared in this \\{xxx}?} @!pure:boolean; {is the command error-free?} @!vvv:integer; {|v|, rounded to the nearest raster} begin pure:=true; case o of @t\4@>@@; @t\4@>@@; four_cases(xxx1): begin @; goto done; end; pre: begin print_ln('preamble command within a page!'); goto 9998; end; @.preamble command within a page@> post,post_post: begin print_ln('postamble command within a page!'); goto 9998; @.postamble command within a page@> end; othercases begin print_ln('undefined command ',o:1,'!'); goto done; @.undefined command@> end endcases; move_down: @; change_font: @; 9998: pure:=false; done: spcl_cases:=pure; end; @ @= nop: goto done; bop: begin print_ln('bop occurred before eop'); goto 9998; @.bop occurred before eop@> end; eop: begin finish_page ; if s<>0 then print_ln('stack not empty at end of page (level ', s:1,')!'); @.stack not empty...@> do_page:=true; goto 9999; end; push: begin if s=max_s_so_far then begin max_s_so_far:=s+1; if s=max_s then print_ln('deeper than claimed in postamble!'); @.deeper than claimed...@> @.push deeper than claimed...@> if s=stack_size then begin print_ln(clone, ' capacity exceeded (stack size=', stack_size:1,')'); goto 9998; end; end; hstack[s]:=h; vstack[s]:=v; wstack[s]:=w; xstack[s]:=x; ystack[s]:=y; zstack[s]:=z; hhstack[s]:=hh; vvstack[s]:=vv; incr(s); goto show_state; end; pop: begin if s=0 then print_ln('(illegal at level zero)!') else begin if set_word then reset_word ; decr(s); hh:=hhstack[s]; vv:=vvstack[s]; h:=hstack[s]; v:=vstack[s]; w:=wstack[s]; x:=xstack[s]; y:=ystack[s]; z:=zstack[s]; end; goto show_state; end; @ Our screen resolution is coarse, and we shall reset characters in words so that the characters are left justified in the space \TeX\ provides. We shall do our position calculations in the same manner that they would be done on the printer. Rounding to the nearest raster is best done in the manner shown here, so as to be inoffensive to the eye: When the horizontal motion is small, like a kern, |hh| changes by rounding the kern; but when the motion is large, |hh| changes by rounding the true position |h| so that accumulated rounding errors disappear. We allow a larger space in the negative direction than in the positive one, because \TeX\ makes comparatively large backspaces when it positions accents. Note that, if the horizontal movement is large, the word is reset. @d out_space(#)==if cur_fptr=0 then hh := raster_round(h+p) else begin if (abs(p) >= round(vresol / 3)) and (set_word) then reset_word ; if (p>=font_space)or(p<=-4*font_space) then hh:=raster_round(h+p) else hh:=hh+round(p/resol); end; q:=p; hpos_ed := false ; goto move_right @= four_cases(right1):begin out_space('right',o-right1+1:1); end; w0,four_cases(w1):begin w:=p; out_space('w',o-w0:1); end; x0,four_cases(x1):begin x:=p; out_space('x',o-x0:1); end; @ Vertical motion is done similarly, but with the threshold between ``small'' and ``large'' increased by a factor of five. The idea is to make fractions like ``$1\over2$'' round consistently, but to absorb accumulated rounding errors in the baseline-skip moves. Again, if the the vertical movement is large, the word is reset. @d out_vmove(#)==if cur_fptr=0 then vv := line_round(v+p) else begin if (p <> 0) and (set_word) then reset_word ; if abs(p)>=5*font_space then vv:=line_round(v+p) else vv:=vv+round(p/vresol); end; vpos_ed := false ; goto move_down @= four_cases(down1):begin out_vmove('down',o-down1+1:1); end; y0,four_cases(y1):begin y:=p; out_vmove('y',o-y0:1); end; z0,four_cases(z1):begin z:=p; out_vmove('z',o-z0:1); end; @ @= sixty_four_cases(fnt_num_0), four_cases(fnt1): goto change_font; four_cases(fnt_def1): begin skip_font_def; goto done; end; @ @= q:=tfm_width ; if o>=put1 then goto done; hh:=hh+raster_width; goto move_right @ @= if o=put_rule then goto done; hh:=hh+round(q/resol); goto move_right @ A sequence of consecutive rules, or consecutive characters in a fixed-width font whose width is not an integer number of rasters, can cause |hh| to drift far away from a correctly rounded value. \vutex\ ensures that the amount of drift will never exceed |max_drift| rasters. @d infinity==@'17777777777 {$\infty$ (approximately)} @d max_drift=1 {we insist that abs|(hh-raster_round(h))<=max_drift|} @= hhh:=raster_round(h+q); if abs(hhh-hh)>max_drift then begin hpos_ed := false ; if hhh>hh then hh:=hhh-max_drift else hh:=hhh+max_drift; end ; h:=h+q; if abs(h)>max_h_so_far then begin if abs(h)>max_h+99 then begin print_ln('warning: |h|>',max_h:1,'!'); @.warning: |h|...@> max_h:=abs(h); end; max_h_so_far:=abs(h); end; goto done @ @= if (v>0)and(p>0) then if v>infinity-p then begin print_ln('arithmetic overflow! parameter changed from ', @.arithmetic overflow...@> p:1,' to ',infinity-v:1); p:=infinity-v; end; if (v<0)and(p<0) then if -v>p+infinity then begin print_ln('arithmetic overflow! parameter changed from ', p:1, ' to ',(-v)-infinity:1); p:=(-v)-infinity; end; vvv:=line_round(v+p); if abs(vvv-vv)>max_drift then begin if vvv>vv then vv:=vvv-max_drift else vv:=vvv+max_drift; end; v:=v+p; if abs(v)>max_v_so_far then begin if abs(v)>max_v+99 then begin print_ln('warning: |v|>',max_v:1,'!'); @.warning: |v|...@> max_v:=abs(v); end; max_v_so_far:=abs(v); end; goto done @ @= goto done @ @= cur_fptr := mem[p] ; goto done @* Skipping pages. A routine that's much simpler than |do_page| is used to pass over pages that are not being translated. The |skip_pages| subroutine is assumed to begin just after the preamble has been read, or just after a |bop| has been processed. It continues until either finding a |bop| that matches the desired starting page specifications, or until running into the postamble. @p procedure skip_pages; label 9999; {end of this subroutine} var p:integer; {a parameter} @!k:0..255; {command code} @!down_the_drain:integer; {garbage} bad_char:boolean ; begin while true do begin if eof(dvi_file) then bad_dvi('the file ended prematurely'); @.the file ended prematurely@> k:=get_byte; if k > 127 then begin p:=first_par(k); case k of bop: begin @; if not started and start_match then begin started:=true; goto 9999; end; end; set_rule,put_rule: down_the_drain:=signed_quad; fnt_def1,fnt_def1+1,fnt_def1+2,fnt_def1+3: if prescan then begin define_font(p); end else skip_font_def ; xxx1,xxx1+1,xxx1+2,xxx1+3: if prescan then begin @ end else while p>0 do begin down_the_drain:=get_byte; decr(p); end; post: begin in_postamble:=true; goto 9999; end; othercases do_nothing endcases; end ; end; 9999:end; @ Now we need a routine that will scan a page from the |bop| to the |eop|, maintaining information about font usage. This routine also catches errors like illegal font numbers or characters. It also counts the number of pages actually processed. @p function scan_page : boolean ; label 9999; {end of this subroutine} var p:integer; {a parameter} @!k:0..255; {command code} bad_char:boolean; {has a non-ASCII character code appeared in this \\{xxx}?} @!down_the_drain:integer; {garbage} begin actual_page_count := actual_page_count + 1 ; while true do begin a:=cur_loc ; if eof(dvi_file) then bad_dvi('the file ended prematurely'); @.the file ended prematurely@> k:=get_byte; if eof(dvi_file) then bad_dvi('the file ended prematurely'); @.the file ended prematurely@> if k; if not started and start_match then begin started:=true; goto 9999; end; end; eop: goto 9999 ; four_cases(set1),four_cases(put1): begin if p > 128 then abort('character greater than 128 encountered!') @.character greater than...@> else begin cur_char_ptr := cur_fptr + 2*p ; incr(use_count); end; end ; set_rule,put_rule: down_the_drain:=signed_quad; fnt_def1,fnt_def1+1,fnt_def1+2,fnt_def1+3: define_font(p); sixty_four_cases(fnt_num_0),four_cases(fnt1): begin if p>255 then abort(clone, ' cannot handle font numbers > 255!') ; @.vutex capacity exceeded...@> cur_fptr := mem[p] ; if cur_fptr=0 then abort('Undefined font!') ; @.Undefined font@> end ; four_cases(xxx1): @ post: begin in_postamble:=true; goto 9999; end; othercases do_nothing endcases; end ; end; 9999:scan_page := true; end; @ The global variable |started| indicates that we have found the starting page. |last_page| points to the most recent page processed, and |prev_page| points to the page before it. These two globals can be used for processing a file in the reverse output, if the printer being driven stacks the pages backwards, for instance. @= @!started:boolean; {has the starting page been found?} @!last_page: integer; {what is the most recent page processed?} @!prev_page: integer; {what is the page before it?} @!actual_page_count: integer; {how many pages to actually process} @ @= started:=false; last_page:=-1; prev_page:=-1; actual_page_count:=0; @ @= last_page:=cur_loc; incr(page_count); for k:=0 to 9 do count[k]:=signed_quad; prev_page:=signed_quad ; {skip over the back pointers} @ We need a procedure to open the output file, and to send out any initialization that the printer might need. @= open_bit_file ; init_device ; @* Page scanning routines. Here are a few miscellaneous routines which will scan the page, calling the |pre_scan| and |do_page| routines as necessary. @= 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 print_ln('identification in byte 1 should be ',id_byte:1,'!'); @.identification...should be n@> @; p:=get_byte; {fetch the length of the introductory comment} print(''''); while p>0 do begin decr(p); print(xchr[get_byte]); end; print_ln('''') @ We also need a routine to skip over the preamble after the file has been reset again. @= reopen_dvi_file; p:=get_byte; {fetch the first byte} p:=get_byte; {fetch the identification byte} p:=signed_quad; p:=signed_quad; p:=signed_quad; {skip over the mag stuff} p:=get_byte; {fetch the length of the introductory comment} while p>0 do begin i:=get_byte; decr(p); end; @ Read in the conversion units and magnification for the font. @= @!num,den : integer; {numerator and denominator for conversion of dvi units} @ @= num:=signed_quad; if num<=0 then bad_dvi('numerator is ',num:1); @.numerator is wrong@> den:=signed_quad; if den<=0 then bad_dvi('denominator is ',den:1); @.denominator is wrong@> p:=signed_quad; @ The code shown here uses a convention that has proved to be useful: If the starting page was specified as, e.g., `\.{1.*.-5}', then all page numbers in the file are displayed by showing the values of counts 0, 1, and~2, separated by dots. Such numbers can, for example, be displayed on the console of a printer when it is working on that page. @= begin print('- Page '); while max_pages>0 do begin decr(max_pages); for k:=0 to start_vals do begin print('[',count[k]:1); if k begin repeat k:=get_byte; if (k>=fnt_def1)and(knop; if k=post then begin in_postamble:=true; goto done; end; if k<>bop then bad_dvi('byte ',cur_loc-1:1,' is not bop'); @.byte n is not bop@> @; end ; end; done: end; @ This routine will prescan the file, using the |scan_page| macro to get the information out of each page. @= begin while max_pages>0 do begin decr(max_pages); if not scan_page then bad_dvi('page ended unexpectedly'); @.page ended unexpectedly@> if in_postamble then goto pdone ; repeat k:=get_byte; if (k>=fnt_def1)and(knop; if k=post then begin in_postamble:=true; goto pdone; end; if k<>bop then bad_dvi('byte ',cur_loc-1:1,' is not bop',k); @.byte n is not bop@> @; end; pdone:end @* Font loading. Now we have the procedure which determines the vertical and horizontal raster resolution. On entrance to this procedure, all the font usage information will have been loaded into the |use_count| entries for each font descriptor. Based on this, |base_font| will determine the resolution to use for assigning screen raster positions. First, this routine reads the file \.{nonASCII.tex.fnt} to determine which fonts are non ASCII, i.e. not printable on the screen. This list is shorter than the list of ASCII fonts. @p procedure base_font ; var i, j, k : integer ; f_type : integer ; {type for listed fonts} lf,lh,bc,ec,nw,np : integer ; {help in decoding the \.{TFM} file} value, x : integer ; {a \.{TFM} |fix_word|} @<|base_font| variables@> begin @ ; @ ; end ; @ The non ASCII font information is assumed to be in the following format: \medskip {\def\ff#1\cr{\line{\hskip\parindent\tt #1\hfil}} \ff amsy 2\cr \ff ambsy 2\cr \ff amcsc 2\cr \ff amit 4\cr \ff amtt 5\cr \medskip The only item on a line is the font name and the type code. @d font_list_file=='nonASCII.tex.fnt' {change this to correct name} @d list_len==16 @= @!resol, vresol : real; {number of dvi units per raster or line} @!gen_input : text_file ; {input path for text} @!list_fonts : packed array [1..list_len] of char ; {non ASCII fonts name} @!num_asc_fonts : integer ; {number of ASCII fonts declared} @ @= list_fonts := font_list_file ; @ @= for k := 1 to name_length do cur_name[k] := ' ' ; for k := 1 to list_len do if (list_fonts[k] <= 'z') and (list_fonts[k] >= 'a') then cur_name[k] := xchr[xord[list_fonts[k]]-@'40] else cur_name[k] := list_fonts[k] ; open_input_text ; num_asc_fonts := 0 ; while not eof(gen_input) do @ ; @ At this point, we are at the beginning of a line in the non ASCII font information file. We read in the name of a non printable font, and then scan through our fonts looking for a match. By default all fonts are assumed to be roman unless named in this file. @= begin buf_ptr := 1 ; while not eoln(gen_input) do begin buffer[buf_ptr] := xord[gen_input^] ; get(gen_input) ; buf_ptr := buf_ptr + 1 ; end ; read_ln (gen_input) ; buffer[buf_ptr] := " " ; buf_ptr := 1 ; while buffer[buf_ptr] <> " " do buf_ptr := buf_ptr + 1 ; while buffer[buf_ptr] = " " do buf_ptr := buf_ptr + 1 ; f_type := get_integer ; for i := 0 to 255 do if mem[i] > 0 then begin cur_fptr := mem[i] ; j := font_name ; k := 1 ; if names[j] = 0 then begin if (font_type)=declared then begin font_type := roman ; incr(num_asc_fonts) ; end ; j := j + 1 ; while (names[j] = buffer[k]) do begin j := j + 1 ; k := k + 1 ; end ; if (buffer[k] = " ") then begin font_type := f_type ; if f_type = other then decr(num_asc_fonts) ; end ; end ; end ; end @ This section of code figures out which font is to determine the resolution. It goes through the list of fonts, and, for each font that is ASCII, it calculates the |use_count| and the font number into a list built at the top of memory. This list is then sorted, and the fonts are given priorities. Horizontal resolution normally is slightly less than the width of normal interword space, except for fonts where this parameter is 0 in which case one third of a quad is used. The vertical resolution is determined by placing |num_lines| on the physical page. @d dd_s_k(#)==mem[temp_ar(#)-1] {sort key for advantages} @= @ ; @ ; @ ; @ Making a list of the declared fonts is relatively easy, as is calculating the advantages. The advantage is the use count for all fonts. @= asc_num := 0 ; nasc_num := num_asc_fonts ; for i := 0 to 255 do if mem[i] > 0 then begin cur_fptr := mem[i] ; cur_char_ptr := cur_fptr ; dd_total := 0 ; for j := 0 to 127 do begin if use_count > 0 then dd_total := dd_total + use_count ; cur_char_ptr := cur_char_ptr + 2 ; end ; if (dd_total >0) then begin if (font_type > other) then begin temp_ar(asc_num) := cur_fptr ; incr(asc_num) ; end else if (font_type = other) then begin temp_ar(nasc_num) := cur_fptr ; incr(nasc_num) ; end ; end ; mem[cur_fptr + 1] := dd_total ; end @ The sort to determine the priority of the fonts is a simple insertion sort. Since this sort is only performed once, and it usually has less than a dozen elements to sort, simplicity is called for. Note that the ASCII fonts are sorted at the top, and the non-ASCII fonts are sorted last. @= for i := asc_num-1 downto 1 do for j := 0 to i-1 do if mem[temp_ar(j)+1] < mem[temp_ar(i)+1] then begin t := temp_ar(i) ; temp_ar(i) := temp_ar(j) ; temp_ar(j) := t ; end ; for i := nasc_num-1 downto num_asc_fonts+1 do for j := num_asc_fonts to i-1 do if mem[temp_ar(j)+1] < mem[temp_ar(i)+1] then begin t := temp_ar(i) ; temp_ar(i) := temp_ar(j) ; temp_ar(j) := t ; end ; for j := 0 to asc_num-1 do dd_s_k(j) := j ; for j := num_asc_fonts to nasc_num-1 do dd_s_k(j) := j ; @ Some of |base_font|' locals: @<|base_font| variables@>= @!asc_num : integer ; {positions the ASCII fonts used} @!nasc_num : integer ; {positions the non-ASCII fonts used} @!t : integer ; {temporary holding place for exchange swaps} @!dd_total : integer ; {contains total number of different characters} @ We now will load the \.{TFM} file for each font. We also will determine the resolution from the |base_font| and will scale all \.{TFM} widths accordingly. @= cur_fptr := mem[next_mem_free] ; load_tfm_file ; @ ; for i := 0 to 255 do begin if (mem[i] > 0) then begin cur_fptr := mem[i] ; if font_status <> 0 then load_tfm_file ; cur_char_ptr := cur_fptr ; for j := 0 to 127 do begin if (use_count > 0) then begin raster_width := round(tfm_width/resol) ; if raster_width < 1 then raster_width := 1; end ; cur_char_ptr := cur_char_ptr + 2 ; end ; end ; end @ The vertical resolution is determined to place |num_lines| on a page of dimension |page_length|. The horizontal resolution is given by the inter-word space for the |base_font|. Note that this section assumes that the \.{TFM} data for the |base_font| is exactly as it was initally read into |mem|. @= begin lf := hi(mem[next_mem_free]) ; lh := lo(mem[next_mem_free]) ; bc := hi(mem[next_mem_free+1]) ; ec := lo(mem[next_mem_free+1]) ; nw := hi(mem[next_mem_free+2]) ; np := lo(mem[next_mem_free+5]) ; k := next_mem_free + 6 + lh - bc ; vresol := den / (num / 100000.0) * page_length / num_lines ; value := mem[next_mem_free + lf - np + 1] ; x := tfm_to_int(value) ; resol := x * 1.0 ; if resol <= 0 then begin value := mem[next_mem_free + lf - np + 5] ; x := tfm_to_int(value) ; resol := x / 3.0 ; end; end ; @ This routine may be used to initialize the output device to begin receiving information. This section could be personalized to a particular system. @p procedure init_device ; begin end ; @ Now for the antithesis of the above. This routine cleans up the \TeX\ job, and prepares for the next. @p procedure clean_printer ; begin write_ln(bit_file); write_ln(bit_file,'The end...'); write_ln; end ; @* The main program. Now we are ready to put it all together. This is where \vutex\ starts, and where it ends. @p begin initialize; {get all variables initialized} dialog; {set up all the options} prescan := true ; @; skip_pages; if not in_postamble then @; @; base_font ; prescan:=false ; in_postamble := false ; started:=false ; max_pages:=actual_page_count ; @; skip_pages; @; final_end: if bit_is_open then clean_printer; diagnostics; end. @ The main program needs a few global variables in order to do its work. @= @!k,@!p,@!q:integer; {general purpose registers} @!prescan:boolean; {indicates that we are in prescan phase} @!bit_is_open : boolean ; {indicates that output file was opened.} @ @= bit_is_open := false ; @* System-dependent changes. This section should be replaced, if necessary, by changes to the program that are necessary to make \vutex\ work at a particular installation. Any additional routines should be inserted here. @^system dependencies@> @* Index. Pointers to error messages appear here together with the section numbers where each ident\-i\-fier is used.