summaryrefslogtreecommitdiff
path: root/dviware/screenview
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /dviware/screenview
Initial commit
Diffstat (limited to 'dviware/screenview')
-rw-r--r--dviware/screenview/vms/build.com8
-rw-r--r--dviware/screenview/vms/crude.com21
-rw-r--r--dviware/screenview/vms/crudetype.ch287
-rw-r--r--dviware/screenview/vms/crudetype.web3282
-rw-r--r--dviware/screenview/vms/screen.sty29
-rw-r--r--dviware/screenview/vms/screenview.hlp43
-rw-r--r--dviware/screenview/vms/test.dvibin0 -> 2048 bytes
-rw-r--r--dviware/screenview/vms/test.tex53
-rw-r--r--dviware/screenview/vms/test.txt131
9 files changed, 3854 insertions, 0 deletions
diff --git a/dviware/screenview/vms/build.com b/dviware/screenview/vms/build.com
new file mode 100644
index 0000000000..2a4863cdc8
--- /dev/null
+++ b/dviware/screenview/vms/build.com
@@ -0,0 +1,8 @@
+$set ver
+$on warning then continue
+$tangle crudetype ! ignore warning messages
+$pas/opt crudetype
+$link/notrace crudetype
+$del/nolog crudetype.obj;*
+$del/nolog crudetype.pas;*
+$set nover
diff --git a/dviware/screenview/vms/crude.com b/dviware/screenview/vms/crude.com
new file mode 100644
index 0000000000..449b288180
--- /dev/null
+++ b/dviware/screenview/vms/crude.com
@@ -0,0 +1,21 @@
+$ if (p1 .nes. "") then goto file_given
+$ type sys$input
+
+Usage: screenview file [first_page max_pages magnification]
+
+$ exit
+$
+$ file_given:
+$ open/write temp sys$scratch:crude.tmp
+$ write temp p1
+$ write temp p2
+$ write temp p3
+$ write temp p4
+$ close temp
+$ on warning then goto finish
+$ define/user sys$input sys$scratch:crude.tmp
+$ r TeX_Root:[Drivers.Screenview]Crudetype
+$
+$ finish:
+$ delete/nolog sys$scratch:crude.tmp;
+$ exit
diff --git a/dviware/screenview/vms/crudetype.ch b/dviware/screenview/vms/crudetype.ch
new file mode 100644
index 0000000000..1ce66d4025
--- /dev/null
+++ b/dviware/screenview/vms/crudetype.ch
@@ -0,0 +1,287 @@
+% Andrew Trevorrow, June 1988
+% All my changes (both here and in .WEB file) are flagged with "AKT".
+% AKT: Text file Change file for Crudetype.
+
+@x
+*** Attach printer change file here ***
+@y
+\vskip 0.5in
+\centerline{The text file change file for Crudetype starts here}
+\vskip 0.5in
+
+@<Const...@>= device_ID = 'Text file';
+
+@ Where will the printed file go to?
+
+@<Set init...@>=
+ set_string( print_end,
+ '.TXT ' , ' ', 32 ) ;
+ {AKT: was .PRI}
+
+@ The first lot of data describes the printer's overall style of carriage
+control. |fortran| means that the carriage control character gets put at the
+start of the line, and it is here assumed that it must be inserted explicitly.
+Note also that the program makes no attempt to check all these values for
+consistency.
+
+@<Carriage control constants@>=
+ fortran = false ;
+ list = true ; {AKT: was false}
+ w_l_does_c_r = true ; {AKT: was false}
+ b_feed_absolute = false ;
+ b_feed_by_string = false ;
+ feed_absolute = false ;
+ b_feed_scream = true ;
+ b_space_absolute = false ;
+ b_space_by_string = false ;
+ space_absolute = false ;
+ abs_is_incr = false ;
+ want_split = true ;
+ is_header = false ; {each page needs a header}
+ param_type = 'D' ;
+
+@ This batch is concerned with distances and resolutions.
+
+@<Const...@>=
+ l_margin = 0 ; {AKT: was 6}
+ top_margin = 0 ; {AKT: was 6}
+ h_resolution = 10 ; {|h_steps| per inch}
+ v_resolution = 6 ; {|v_steps| per inch}
+ fixed_width = true ; {printers characters are fixed width}
+ char_width = 1 ;
+ {all printer characters are this width, in units of |h_step|. Normally,
+ |space_dist| will be equal to this, but some printers are not normal!}
+ gap_width = 1 ; {Intended minimum space between words}
+ char_ht = 1 ;
+
+@ The general run of \TeX\ characters are narrower than line-printer chars. So
+we spread them out to make them fit.
+
+@<Set init...@>=
+ h_fudge := 7.227 {number of points per |h_step|}
+ / 5.25 ; {A typical design width}
+ v_fudge := 1.2 ; {AKT: was 2.0 to force double-spacing }
+
+@ @<Glob...@>=
+ h_fudge, v_fudge: real_num ;
+
+@ Next, some constants for rule-setting.
+
+@<Rule setting const...@>=
+ rail_width = 1 ; {Height and width of rule chars, in steps}
+ rail_height = 1 ;
+ post_width = 1 ;
+ post_height = 1 ;
+ rail_types = 2 ;
+
+@ @<Set rule characters@>=
+ rail_chars[1] := codes[ 1, 95 ] ;
+ rail_chars[2] := codes[ 1, 45 ] ;
+ post_char := codes[ 1, 124] ;
+
+@ @<Pause constants...@>=
+ do_pause = false ;
+ pause_steps = 20 ;
+ pause_ask = 'PAUSED. Type <return> to continue' ;
+
+@ The next batch are concerned with fonts.
+
+@<Const...@>=
+ min_font = 1 ;
+ {smallest and largest number of printers resident fonts}
+ max_font = 1 ;
+ only_one_font = true ;
+ can_dl_font = false ;
+ min_dl_font = 0 ;
+ max_dl_font = 0 ; {printers down-loadable fonts}
+ max_codes = 9 ; {AKT: was 8; no. of known \TeX\ coding schemes}
+ max_char = 255 ; {AKT: was 127; max. no. of chars per \TeX\ or PS font}
+ max_plain = 4 ; {Max number of a plain text font}
+
+@ Finally, consider command strings. These are intended to set options, but a
+line printer hasnt any.
+
+@<Set init...@>=
+ start_stuff := blank ;
+ stop_stuff := blank ;
+ page_top := blank ;
+ pause_after := blank ;
+
+@* Character code data for the printer.
+
+Here we actually put data into the |codes| array. In general, I have merely
+replaced each character in the PLAIN.TEX coding schemes by the nearest
+equivalent in ASCII, when a reasonable one exists ; I have not tried to tackle
+either AMSY or AMATHEX ; clearly there will be some scope for doing so. First
+we give the scheme names, ( padded to |code_len|) then do stuff for each
+scheme in turn.
+
+@<Assign char...@>=
+ set_string( known_schemes[ 0 ] ,
+ ' ' , ' ', 32 ) ;
+ set_string( known_schemes[ 1 ] ,
+ 'TEX EXTENDED ASCII ' , ' ', 32 ) ;
+ set_string( known_schemes[ 2 ] ,
+ 'TEX TYPEWRITER TEXT ' , ' ', 32 ) ;
+ set_string( known_schemes[ 3 ] ,
+ 'TEX TEXT ' , ' ', 32 ) ;
+ set_string( known_schemes[ 4 ] ,
+ 'TEX TEXT WITHOUT F-LIGATURES ' , ' ', 32 ) ;
+ set_string( known_schemes[ 5 ] ,
+ 'AEFMNOT ONLY ' , ' ', 32 ) ;
+ set_string( known_schemes[ 6 ] ,
+ 'TEX MATH ITALIC ' , ' ', 32 ) ;
+ set_string( known_schemes[ 7 ] ,
+ 'TEX MATH SYMBOLS ' , ' ', 32 ) ;
+ set_string( known_schemes[ 8 ] ,
+ 'TEX MATH EXTENSION ' , ' ', 32 ) ;
+ set_string( known_schemes[ 9 ] ,
+ 'AKT: This scheme will be used for all unknown schemes; what a hack! ' , ' ', 32 ) ;
+
+@ Now do scheme 1 = EXTENDED ASCII
+
+@<Assign char...@>=
+ alphabet( 32, 95, 1, 1, 32) ;
+ row( ' . {|v} Z Z & ~ {LC-} Z ' ,1,0,1) ;
+ row( ' Z Z Z {^|} {+_} {LO+} Z Z ' ,1,1,1) ;
+ row( ' Z Z Z Z Z Z {LOx} Z ' ,1,2,1) ;
+ row( ' [{L<-}-] [-{-L>}] {=/} Z {L<_} {L>_} {=_} [or] ' ,1,3,1) ;
+
+@ Now do scheme 2 = TYPEWRITER. Unfortunately, \.{TANGLE} imposes a limit of 69
+on the length of quoted strings. This causes difficulty because several multi-
+character commands are too long for 8 of them to fit neatly into a string of
+that length. So I use the Z command to ( effectively) split any complicated
+|row_spec| into two.
+
+@<Assign char...@>=
+ alphabet( 32, 95, 2, 1, 32) ;
+ codes[2, 255].IM_char:= 34 ; {AKT: was 2, 127}
+ row( ' Z [{/_}{_\}] {0-} [/\] Z Z Z Z ' ,2,0,1) ;
+ {first half row}
+ row( ' Z Z Z Z <U_{-_}> <U[__][||]> <U_{L>_}> LY ' ,2,0,1) ;
+ {and second}
+ row( ' {oI} {u|} Z {^|} {v|} Q ! ? ' ,2,1,1) ;
+ row( ' i j ` Q Z Z <U_> <U.> ' ,2,2,1) ;
+ row( ' , {LB_} [ae] [oe] {o/} [LALE] [LOLE] {LO/} ' ,2,3,1) ;
+
+@ The TEX TEXT scheme is nearly the same, so we start by copying it.
+
+@<Assign char...@>=
+ for in_i := 0 to 255 do codes [3, in_i] := codes [2, in_i] ;
+ {AKT: was 0 to 127}
+ row( ' Z Z Z [ff] [fi] [fl] [ffi] [ffl] ' ,3,1,1) ;
+ row( ' Z Z Z Z ! Z ? Z ' ,3,7,1) ;
+ row( ' Z Z Z Z W Z Z <U.> ' ,3,11,1) ;
+ row( ' Z Z Z - [--] W ~ W ' ,3,15,1) ;
+
+@ Recently there has appeared a scheme, called TEX TEXT WITHOUT F-LIGATURES.
+
+@<Assign char...@>=
+ for in_i := 0 to 255 do codes [4, in_i] := codes [3, in_i] ;
+ {AKT: was 0 to 127}
+ row( ' {oI} {u|} Z {^|} {v|} Q ! ? ' ,4,1,1) ;
+
+@ and AEFMNOT ONLY (for the Metafont logo).
+
+@<Assign char...@>=
+ row( ' Z LA Z Z Z LE LF Z ' ,5,8,1) ;
+ row( ' Z Z Z Z Z LM LN LO ' ,5,9,1) ;
+ row( ' Z Z Z Z LT Z Z Z ' ,5,10,1) ;
+
+@ The MATH ITALIC scheme is almost impossible.
+
+@<Assign char...@>=
+ for in_i := 0 to 9 do codes [6, in_i] := codes [2, in_i] ;
+ alphabet( 48, 43, 6, 1, 48) ;
+ alphabet( 97, 26, 6, 1, 97) ;
+ row( ' Z Z . , Z / Z * ' ,6,7,1) ;
+
+@ MATH SYMBOLS are messy, and no doubt the results will look unpleasant.
+
+@<Assign char...@>=
+ alphabet( 65, 26, 7, 1, 65) ;
+ row( ' - . {\/} * {-:} Z {+_} <U_+> ' ,7,0,1) ;
+ row( ' {LO+} {LO-} {LOx} {LO/} LO LO o o ' ,7,1,1) ;
+ row( ' Z {=_} Z Z {L<_} {L>_} {L<_} {L>_} ' ,7,2,1) ;
+ row( ' ~ <U~~> Z Z [L<L<] [L>L>] L< L> ' ,7,3,1) ;
+ row( ' [{L<-}-] [-{L>-}] {|^} {|v} Z Z Z Z ' ,7,4,1) ;
+ row( ' Z Z Z Z [{L<-}{-L>}] / \ {~_} ' ,7,4,1) ;
+ row( ' [{L<=}=] [={L>=}] {|^} {|v} Z Z Z Z ' ,7,5,1) ;
+ row( ' Z Z Z Z [{L<=}{=L>}] Z Z Z ' ,7,5,1) ;
+ row( ' Q [oo] {L(-} {-L)} Z Z Z Z ' ,7,6,1) ;
+ row( ' Z Z Z Z [{/_}{_\}] <U[__][\/]> / Q ' ,7,6,1) ;
+ row( ' [{\-}{-/}] <U_[{-_}|]> ~ {0/} LR LT <U_|> {|_}' ,7,7,1) ;
+ row( ' Z Z Z LU Z {LU+} & [or] ' ,7,11,1) ;
+ row( ' [{|-}-] [-{-|}] Z Z Z Z L{ L} ' ,7,12,1) ;
+ row( ' L< L> | [||] {^|v} {^|v} \ Z ' ,7,13,1) ;
+ row( ' <U[S_]{v/}> [{LI_}{LI_}] <[__][\/]> </|/> Z Z Z Z ' ,7,14,1) ;
+
+@ And here is a first attempt at the MATH EXTENSION scheme. These codes look
+rather peculiar because characters in the Extension font (unlike all others)
+have their reference points at the top. Here the restriction of string length
+to 69 is a real pain. It seemed that the least bad way to arrange these |row|s
+was by splitting each into 2 halves; then most half-rows do fit into one
+rowstring. Here are the left hand halves.
+
+@<Assign char...@>=
+ row(' <SL(L(> <SL)L)> <SL[L[> <SL]L]> ZZZZ',8,0,1);
+ row(' <SL{L{> <SL}L}> <S/\> <S\/> ZZZZ',8,1,1);
+ row(' <SL(L(L(> <SL)L)L)> <SL(L(L(L(> <SL)L)L)L)> ZZZZ',8,2,1);
+ row(' <SL[|||> <SL]|||> <SL{/\L{> <SL}\/L}> ZZZZ',8,3,1);
+ row(' <SL(L(L(L(L(> <SL)L)L)L)L)> <SL[|||L[> <SL]|||L]> ZZZZ',8,4,1);
+ row(' <SL{L|L<|L{> <SL}|L>|L}> <S[S/]/\[S\]> <S\[S\][S/]/> ZZZZ',8,5,1);
+ row(' <S/||> <S\||> <SL[||> <SL]||> ZZZZ',8,6,1);
+ row(' <S[S/]|> <S\[S|]> <S|[S\]> <S[S|]/> ZZZZ',8,7,1);
+ row(' <S||\> <S||/> <S|> <S|> ZZZZ',8,8,1);
+ row(' <[__]\[{/_}_]> <[__][LILI]> </|/> <S[||][\/]> ZZZZ',8,10,1);
+ row(' <S[{|_}{|_}]> <S[|S|][{|_}_{_|}]> <U_> <U[__]> ZZZZ',8,12,1);
+ row(' <SL[L[L[> <SL]L]L]> <S||L[> <S||L]> ZZZZ',8,13,1);
+ row(' <S{|^}> <S{|v}> / \ ZZZZ',8,15,1);
+
+@ Here are the right hand halves.
+
+@<Assign char...@>=
+ row('ZZZZ <S|L[> <S|L]> <SL[|> <SL]|> ',8,0,1);
+ row('ZZZZ <S|> <S[||]> <S[S/]/> <Z\[S\]> ',8,1,1);
+ row('ZZZZ <SL[L[L[L[> <SL]L]L]L]> <S|||L[> <S|||L]> ',8,2,1);
+ row('ZZZZ <S[S/]/\[S\]> <S\[S\][S/]/> <S[S/][S/]//> <S\\[S\][S\]> ',8,3,1);
+ row('ZZZZ <S||||L[> <S||||L]> <SL[||||> <SL]||||> ',8,4,1);
+ row('ZZZZ <S[S/][S/]//> <S\\[S\][S\]> <S[SS/][S/]/> <S\[S\][SS\]> ',8,5,1);
+ row('ZZZZ <S||L[> <S||L]> <S|> <S|> ',8,6,1);
+ row('ZZZZ <S[S|]L<[S|]> <S|[SL>]|> <S|> <S|> ',8,7,1);
+ row('ZZZZ <S[S/]L<[S\]> <S\[SL>]/> <S[{|_}{|_}]> <S[|S|][{|_}S{|_}]> ',8,8,1);
+ row('ZZZZ <S[/\][||]> <S[|+|][\_/]> <S[/\]> <S[\/]> ',8,10,1);
+ row('ZZZZ <U[___]> <U~> <U[~~]> <U[~~~]> ',8,12,1);
+ row('ZZZZ <SL[||> <SL]||> <SL{L{L{> <SL}L}L}> ',8,13,1);
+ row('ZZZZ \ / <S{|^}> <S{|v}> ',8,15,1);
+
+@ And here are the half rows that are so long that even half a row must be
+further split.
+
+@<Assign char...@>=
+ row(' </{|O}/> <S/{|O}|/> <S[/\][\/]> <S[S_][/.\][\_/]> ZZZZ',8,9,1);
+ row('ZZZZ <S[{/_}{\_}][\/]> <S[S_][{/_}{|_}{_\}][\{|_}/]> ZZ',8,9,1);
+ row('ZZZZZZ <S[{\/}{/\}][{\/}{/\}]> <[S_][{\/}S{/\}][|{\/}|][{/\}_{/\}]> ',8,9,1);
+
+@#
+ row(' <[___]\[SL>][{/_}__]> <[___][|S|][|S|][|S|]> <S/||/> ZZZZZ',8,11,1);
+ row('ZZZ <S[|S|][|S|][\_/]> <[S_][/S\][|S|][|S|]> ZZZ',8,11,1);
+ row('ZZZZZ <S[|S|][|+|][\_/]> <SS[S/\][/SS\]> <SS[\SS/][S\/]> ',8,11,1);
+
+@#
+ row(' <S[_S|][S\|]> <S[SS|][_S|][S\|]> <S[SS|][SS|][_S|][S\|]> ZZZZZ',8,14,1);
+ row('ZZZ <S[SS|][SS|][SS|][_S|][S\|]> <S[SS|][_S|][S\|]> ZZZ',8,14,1);
+ row('ZZZZZ <S|> <U_|> <S[||]> ',8,14,1);
+
+@ AKT: Now do scheme 9 = unknown (eg. PostScript font).
+We simply use the TEX TEXT scheme.
+
+@<Assign char...@>=
+ for in_i := 0 to 255 do codes [9, in_i] := codes [3, in_i] ;
+
+@ This must be done last of all:
+
+@<Assign char...@>=
+ @<Set rule characters@>
+
+@z
diff --git a/dviware/screenview/vms/crudetype.web b/dviware/screenview/vms/crudetype.web
new file mode 100644
index 0000000000..8e9215f0e5
--- /dev/null
+++ b/dviware/screenview/vms/crudetype.web
@@ -0,0 +1,3282 @@
+% ADAPTED FROM DVITYPE, VERSION 2.6.
+
+% REVISIONS:
+% 9/86: clarify names of global variables, supply hooks for attempted
+% Hewlett-Packard Laserjet version.
+% 1/88: Several bugfixes. Chiefly the noscheme bug (TFM files without coding
+% schemes)
+% Also, added some MATH EXTENSION character codes.
+% 4/88: Unix change file by P. King.
+%
+% 6/88: Changes to produce a "normal" VMS text file by Andrew Trevorrow.
+% All such changes are flagged by AKT comments.
+
+% Here is TeX material that gets inserted after \input webmac
+\def\hang{\hangindent 3em\indent\ignorespaces}
+\font\ninerm=cmr9
+\let\mc=\ninerm % medium caps for names like PASCAL
+\def\PASCAL{{\mc PASCAL}}
+
+\def\(#1){} % this is used to make section names sort themselves better
+\def\9#1{} % this is used for sort keys in the index
+
+\def\title{Crudetype}
+\def\contentspagenumber{1}
+\def\topofcontents{\null
+ \def\titlepage{F} % include headline on the contents page
+ \def\rheader{\mainfont\hfil \contentspagenumber}
+ \vfill
+ \centerline{\titlefont Crudetype}
+ \vskip 50pt
+ \centerline{An adaptable device driver (Version 1, 1988)}
+ \vskip 50pt
+ \centerline{R.M.Damerell,} \vskip 30pt
+ \centerline{Mathematics Dept.,} \vskip 20pt
+ \centerline{Royal Holloway and Bedford College,} \vskip 20pt
+ \centerline{Egham, Surrey, U.K.} \vskip 20pt
+\vfill}
+\pageno=\contentspagenumber \advance\pageno by 1
+
+
+% These macros for verbatim scanning are copied from MANMAC.TEX. But we cant
+% use the vertical bar for a temporary escape character as WEAVE catches it.
+% So we will use ! instead and hope for the best
+\chardef\other=12
+\def\ttverbatim{\begingroup
+ \catcode`\\=\other \catcode`\{=\other \catcode`\}=\other \catcode`\$=\other
+ \catcode`\&=\other \catcode`\#=\other \catcode`\%=\other \catcode`\~=\other
+ \catcode`\_=\other \catcode`\^=\other
+ \obeyspaces \obeylines \tt}
+
+\outer\def\begintt{$$\let\par=\endgraf \ttverbatim \parskip=0pt
+ \catcode`\!=0 \rightskip-5pc \ttfinish}
+{\catcode`\!=0 !catcode`!\=\other % ! is temporary escape character
+ !obeylines !obeyspaces % end of line is active
+ !gdef!ttfinish#1^^M#2\endtt{#1!vbox{#2}!endgroup$$}}
+
+\def\up{\hbox{\tt{\char'013}}}
+\def\markarrow#1{\vtop{\hbox{#1}\up}}
+
+@* Introduction.
+
+COPYRIGHT ( C ) R.M.Damerell, 1988.
+
+Permission is given to any person to make and distribute copies of this
+software, subject to the following conditions:
+
+1. All copies of the software must carry an exact copy of this notice.
+
+2. This software is distributed free of charge, "AS IS" with absolutely no
+guarantee of performance. Any persons receiving or using this software must do
+so entirely at their own risk. Neither the authors nor their institutions
+accept any liability for any defects of this software, or for any consequential
+loss or damage however caused.
+
+3. Any person who changes this software must clearly mark it as modified and
+add a note describing the changes made.
+
+This is an experimental version and no guarantee of performance is given.
+I would like to receive bug reports, same address or electronic mail to
+DAMERELL at ARPA.UCL.CS.NSS. \par\vskip 0.5in
+
+This program was originally based on D.E.Knuth's program \.{DVItype}, but so
+many changes were needed for various reasons that there is hardly any of the
+original code left. The purpose of this program is to provide a framework for
+users to write \TeX\ device drivers for a variety of `crude' devices. Roughly
+speaking, `crude' means any printer that cannot print the fonts that Metafont
+generates. This would include daisy-wheels and most impact dot-matrix
+printers. Considered as output printers for \TeX, such devices usually have
+some of the following misfeatures: \item
+1. Coarse resolution.\item
+2. Restricted character set. \item
+3. Some printers cannot do reverse line feeds, some can, and tear the paper.
+\item
+4. Slow interface between CPU and printer.\par
+
+Although such printers cannot do justice to \TeX\ output, drivers for them
+are still needed. Some users cannot afford high quality printers. Some can
+only afford to use them for final output; so they need to make proofs on a
+cheaper printer. Also, anybody who has a high quality printer may well need
+to refer to various \.{WEB} files while writing a driver for it. These can
+become illegible in critical places. Here is a sample from \.{DVItype}:
+\begintt
+
+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.$$
+\endtt
+
+Using the basic (line printer) version of \.{Crudetype}, we can get a copy of
+these formulae which is at least legible, even though the result may not be at
+all pleasant to look at. A further difficulty with conventional drivers is
+that most of these use the algorithm `paint a page of pixels, send it down the
+line'. This places a heavy load on both the host computer and the link to the
+printer. Of course, one can try to reduce this load by various optimisations,
+(e.g. by writing critical bits of code in machine language) but this makes the
+program non-portable, and often introduces bugs. \.{Crudetype} is written
+entirely in \PASCAL, without any attempt at optimisation. When compiled on a
+VAX 780 with the NO-OPTIMISE, CHECK and DEBUG qualifiers it runs at about 2--3
+seconds a page. These times are highly variable, and the VMS optimiser reduces
+them by about 10-15\%.
+
+@ Printers vary enormously both in their capabilities and in the commands that
+drive them. The behaviour of \.{Crudetype} is controlled by a large number of
+constants, which supposedly describe how the target printer does things. This
+does have the disadvantage that the user must compile a separate copy of the
+program for each different printer, and also devise some way to ensure that he
+uses the right version for the intended printer. But the only alternative
+seemed to be that \.{Crudetype} should read and parse a file describing the
+printer and this appeared to be unbearably messy. Ideally, these constants
+should be so designed that: \item
+1. Any decent printer can be driven by assigning the right values to these
+constants and recompiling. \item
+2. If the printer is properly documented, it should be immediately obvious
+what are the correct values for all these constants.
+
+At present I do not have enough experience of different printers to come near
+this ideal. In particular, some printers can download characters. The
+problems of writing a program to support this facility in proper generality
+are horrible and ghastly. I have not made any serious attempt yet to tackle
+them. There are just a few places where a hook appears, and I hope eventually
+to attach actual routines for downloading.
+
+Some of the more obvious problems of downloading are: when can you download?
+At any time? start of page? or only at start of document? Can you load one
+character, or must you load a whole font at a time? How much memory does the
+printer provide for down loading? How efficiently does it use its memory? What
+does it do when it runs out? Can you clear out old fonts to make more space?
+What is the format of a down-load command? What parameters does it need, in
+what order, with what punctuation? In what order must pixels be sent? Should
+they be compressed, and how?
+
+@* Implementation.
+
+The original version of \.{Crudetype} was aimed at a line printer, (because
+everybody has these) and was written on the VAX-VMS operating system. The
+intention is that this program should be easily adaptable both to other
+systems and to other printers. So most of it is written in Standard \PASCAL.
+(It is not possible to tell exactly how much of it is Standard, as we do not
+have a certified compiler.) But in some places, it is necessary to use
+extensions. In particular, \.{Crudetype} must read the font files, whose names
+are dynamically specified. That would be impossible in pure \PASCAL.
+\.{Crudetype} also uses non-Standard code in order to talk to the user's
+terminal. It asks for the name of the \.{DVI} file, and for the first page and
+the number of pages to print. If an operating system forbids terminal
+interaction, the installer will have to find another way to give the program
+this information. As file handling is inevitably system-dependent, I have here
+allowed myself a lot of latitude in using VMS-specific procedures. If
+\.{Crudetype} cannot find a file, it will ask the user for another name. On
+the other hand, all files are read and written sequentially, and I have got
+rid of all uses of the default |case| statement. The intention is that all the
+system-dependent stuff goes near the top of the file, and all
+printer-dependent stuff at the end. Then with any luck you can merely
+concatenate Change files for the local system and the local printer, instead
+of having to merge them. All the code that is known to be non-Standard has
+been carefully segregated from the rest of the program. It amounts to about 20
+lines out of 750.
+@.System dependencies@>
+
+It is clearly impossible to predict what difficulties will appear in trying
+to install \.{Crudetype} on other systems, it would seem to be advisable to
+get the line printer version working before trying to adapt it for any other
+printer. To try to ease the process, I propose to distribute several test
+files with the program. These are of the form SAMPLE.TEX, SAMPLE.DVI and
+SAMPLE.PRI (the line printer output).
+
+Although `crude' printers differ very much in their capacities, one thing
+they nearly all have in common is that they cannot feed the paper backwards.
+Some printers cant |Backfeed| at all; some tear the paper, and others let the
+paper slip and so lose position. Therefore it seems to be essential to process
+each page as follows: first copy the page into a suitable structure, then sort
+it by vertical and horizontal position, then print it.
+
+@* Main Program.
+
+@d banner=='This is Crudetype, Version 1, copyright, experimental'
+{printed when the program starts}
+
+@p program crudetype
+ @<Declarations@>
+ begin
+ @<Initialize@>;
+ read_BOP; {AKT: was at start of For each page...}
+ repeat
+ @<For each page of \.{DVI}, print it if desired@>
+ until time_to_stop ;
+ @<Clean up afterwards@>;
+ 666: {AKT: come here instead of crashing!}
+ end.
+
+@ Now here are some of the messy things we must do to satisfy the rules of
+\PASCAL.
+
+@<Declarations @>=
+ (@<Files @>) ;
+ label 666; {AKT}
+ const @< Constants in the outer block @>
+ type @< Types in the outer block @>
+ var @< Globals in the outer block @>
+ @<Forward declarations @>
+ @<Lowest level procedures @>
+ @<Medium level procedures @>
+ @<Top level procedures@>
+
+@ @<Initialize@>=
+ @<Set initial values@>
+ @<Open terminal channels@>
+ @<Assign character codes@>
+ @<Dialog with user@>
+ @<Open \.{DVI} file@>
+ @<Open |printfile|@>
+ @<Read \.{DVI} preamble @>
+
+@ @<Glob...@>=
+ in_i, in_j :integer; {loop index for initializations}
+
+@ Next, here are some macros for common programming idioms.
+
+@d incr(#) == #:=#+1 {increase a variable by unity}
+@d decr(#) == #:=#-1 {decrease a variable by unity}
+@d do_nothing == {empty statement}
+@d exit == 732
+@d return == goto exit
+ {Go here when a loop ends abnormally}
+
+@ The next two procedures are very primitive debugging aids. All internally
+detected errors call |error|. Then they can be caught (in VMS) by the debugger
+command\begintt
+ DBG>set break error \endtt
+If a fatal error occurs, then the program will force a crash. With the VMS
+debugger, you can then interrogate variables, etc. I chose the square root of
+$-1$ as this does not figure prominently in \TeX-related programs.
+@^square root@>
+
+@<Forward...@>=
+procedure error ; begin end;
+@#
+procedure crash;
+var u: real;
+begin
+ goto 666; {AKT: was
+ u := -1 ;
+ error;
+ u := sqrt(u) ;
+ }
+end;
+
+@* Interface to Operating System, 1: Material specific to VAX/VMS.
+
+The purpose of these sections is to try to give a reasonable interface between
+the operating system and the rest of the program, which is supposed to be
+Standard \PASCAL. Nearly all the non-Standard code is concerned with file
+handling and the lowest level of I/O. This is an area where Standard \PASCAL\
+seems to be particularly weak. This particular section contains all of the
+most system dependent code, and it will probably have to be entirely rewritten
+for any other machine. It is hoped that most of the later sections will work
+on a wide range of machines. Everything here is system dependent, so there is
+no point in indexing each module separately.
+@.System dependencies@>
+
+@ The character set. I have here deleted all the code from \.{DVItype} that
+translates from characters to small integers and back. This is because we have
+to do a quite different translation anyway. If it is necessary to put that
+code back in, then it will probably be necessary to insert \begintt
+define zchr(#) == xchr[#] \endtt
+because of the different brackets.
+
+@d zchr == chr
+@d zord == ord
+
+@ Here are some other system-dependent types. We use double length arithmetic.
+The VMS-specific function |dble| converts its argument to double precision.
+Integers are 32 bits in VMS. Normally, I use |integer| whenever the bit length
+is unimportant, but I use subranges in the |page_record| type, as this allows
+packing and may improve the program's performance.
+
+@d real_num == double
+@d make_double == dble {convert a |real| to double length}
+@d max_half = 32767
+
+@<Types...@>=
+ byte = 0..255 ;
+ i_word = -max_half-1 .. max_half ;
+
+@ Here we consider the lowest level of file handling. The main input file is
+the |dvi_file|. Output for printing goes to |printfile| and diagnostic output
+to |term_out|. The terms |display| and |print| are used instead of |write| so
+that output may be redirected if desired. Throughout the program, it is
+assumed that |@!write| appends its argument(s) to the current record of the
+selected file, and |@!write_ln| ends that record and sends it off; this
+behaviour is specified in the \PASCAL\ Standard. If these assumptions are
+false, it will require major restructuring of the program.
+
+These macros describe how we use the terminal. VMS actually opens the terminal
+channels for us, but we get a better style of output by re-opening it, and
+using these funny-looking macros to write to it. We can then print a stream of
+progress reports without falling foul of a finite record length.
+
+@d term_in==input {the terminal, considered as an input file}
+@d term_out==output {and output}
+@d i_reset_terminal == do_nothing {Switch terminal to input}
+@d o_rewrite_terminal == do_nothing
+ {and back to output. VMS does all this automatically}
+
+@d display(#)==write_ln(term_out, #)
+@d display_ln(#)==write_ln(term_out, #, chr(13), chr(10))
+@d c_con == @=carriage_control@>
+
+@d warn (#)==begin display_ln('Error: ', #); error; end
+@d abort(#)==begin display_ln('Fatal: ', #); crash; end
+@d bad_dvi(#)==abort('Bad DVI file: ',# )
+
+@<Files @>= term_in, term_out
+
+@ @<Open terminal channels@>=
+ open(term_out, 'SYS$OUTPUT', c_con := none) ;
+
+@ @<Const...@>= can_interact = true ;
+
+@ The printed output goes to |@!printfile|.
+
+@d print(#)==write(printfile, #)
+@d print_ln ==write_ln(printfile )
+
+@<Files@>= , printfile
+
+@ @<Glob...@>= printfile : text ;
+ print_name: var_string ;
+
+@ In this section we generate a name for the printed file. Essentially, this
+involves deleting the `.DVI' at the end of the \.{DVI} filename and adding
+`.PRI' instead. But: the \.{DVI} file might be in a funny place, and it might
+have a funny extension. Of course, this code all depends crucially on the VMS
+file name format, and it will probably be a lot more complicated on systems
+that do not allow the elaborate facilities of the VMS |open| command.
+
+@<Open |printfile|@>=
+ print_name := dvi_name ;
+ chop_tail(print_name, ':') ;
+ chop_tail(print_name, ']') ;
+ {Chop off directory, disc, and perhaps a logical name}
+ chop_top(print_name, '.') ;
+ chop_top(print_name, ';') ;
+ {and extension and version number}
+ append(print_name, print_end) ; {In VMS, usually `.PRI'}
+ display ('Output is '); {AKT: was PRINTFILE IS}
+ string_show(print_name);
+ display_ln(' ') ;
+ @.Printfile is...@>
+
+@ VMS \PASCAL\ allows 3 types of carriage control, called |list|, |fortran|,
+or |none|. No doubt other systems will have other peculiar types of carriage
+control. In VMS, |none| is to be used if at all possible, but some printers
+insist on a line feed after every carriage return. Roughly speaking, |@!list|
+directs the operating system to put a CR--LF at the end of each record when the
+file is printed. |@!fortran| means that a Fortran-type carriage control
+character must be put at the start of each record, and \.{Crudetype} assumes
+that this must be inserted explicitly. One type of run-time error that causes
+a lot of trouble occurs if you try to write too many characters onto one
+record of the |printfile|. I have tried to defeat this by declaring a very
+long record length.
+
+@ @d r_len == @=record_length@>
+
+@<Open |printfile|@>=
+ if fortran then
+ open (printfile, print_name.data, c_con := fortran, r_len := 30000 )
+ else if list then
+ open(printfile, print_name.data, c_con := list, r_len := 30000 )
+ else
+ open(printfile, print_name.data, c_con := none, r_len := 30000 ) ;
+ rewrite(printfile) ;
+
+@ Now here is the lowest-level procedure for opening binary files. This will
+have to be rewritten to run on any other system. The VMS |open| procedure
+tries to open the file with the given name; if bits of the name are missing,
+it can obtain them from the |default| parameter. It generates a non-zero
+|status| if it fails.
+
+@d close_binary(#)==
+ close(# , @=error := continue@> )
+
+@<Lowest...@>=
+ function open_binary
+ (var f_f: byte_file; name: var_string; other_name: def_name_type): boolean;
+ var s: integer;
+ begin
+ close_binary(f_f );
+ {in case the file was left open}
+ open(f_f, name.data , readonly, ,, fixed, default := other_name,
+ @=error := continue@> );
+ s := status(f_f) ;
+ if s <> 0 then open_binary := false
+ else begin
+ reset(f_f , @=error := continue@> );
+ s := status(f_f) ;
+ open_binary := (s = 0 );
+ end;
+ end;
+
+@ Here we define system-dependent properties of these files. The easiest way
+to tell VMS where to look for files is by giving them default names. These
+should all be the same length if possible.
+
+@d block_length = 512
+
+@<Const...@>=
+ dvi_def = ' *.DVI' ;
+ tfm_def = 'TEX$FONTS:*.TFM' ;
+ pxl_def = 'TEX$PIXLDIR:*.*' ;
+
+@ @<Types...@>= def_name_type = packed array [1..15] of char ;
+
+@ Here are macros for the adaptable merge sort. See the section on sorting for
+explanation.
+
+@d image(#) == pool[#]
+@d create == incr(cell)
+@d link_type == page_i
+@d first_cell == cell := 0
+@d wipe_out(#) ==
+@d declare_pool == pool: array [page_i] of page_record;
+@d garbage == cell := zzz ;
+
+@ These upper bounds are put in to catch runaway arguments.
+
+@<Const...@>=
+ page_max = 10000 ;
+ max_line_size = 1000;
+
+@* Interface to Operating System, 2: Terminal input and output.
+
+When \.{DVItype} begins, it engages the user in a brief dialog so that the
+options will be specified. This version of \.{Crudetype} does the same. This
+requires nonstandard \PASCAL\ constructions to handle the online interaction.
+So it may be necessary on some systems to omit the dialog. If so, the
+installer must find some way to get the \.{DVI} file name into the |@!buffer|.
+
+@<Dialog...@>=
+ {AKT: removed display_ln(banner, ' --- ', device_ID) ;}
+ buffer := blank ;
+ repeat
+ ask_prompt('DVI file name? ');
+ dvi_name := buffer ;
+ until dvi_name.len > 0 ;
+ ask_prompt('First page? (default = 0) ' );
+ first_page := get_number(0 ) ;
+ ask_prompt('maximum no. of pages? (default = 1000000) ' ) ;
+ max_pages := get_number(1000000) ;
+ @.DVI file name?@>@.First page?@>@.max. no. of pages?@>
+
+@ Most characters in \TeX\ fonts are narrower than line-printer characters. So
+we must spread them out to make them fit. Originally, this was done by
+multiplying \.{DVI} distances by a constant factor |h_fudge|. This is all
+right for one size of type but it tends to fail for other sizes because if the
+predominant type size is larger than expected, then rounding with a constant
+factor makes everything\qquad\ very\qquad\ badly\qquad\ spread\qquad\ out. It
+seemed that the least bad way to tackle this is to allow the user to specify
+an extra magnification factor.
+
+@<Dialog...@>=
+ {AKT: removed
+ if can_interact then display_ln(
+ 'What magnification? This must be an integer, and is a percentage');
+ }
+ ask_prompt ( 'Default = 100% = DVI file magnification ') ;
+ extra_mag := get_number(100)/100.0 ;
+ @.What magnification?@>
+
+@ 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.) Here, we assume that the system-dependent
+macro |@!i_reset_terminal| (defined above) will do whatever is necessary to
+switch the terminal from output to input. Likewise, |@!o_rewrite_terminal|
+must switch it from input to output. Note that the program assumes that the
+terminal is normally in output mode, and explicitly calls these macros when it
+wants input. If the system does not allow this, then |@!can_interact| should
+be set false.
+
+Here is how the program prompts for input: the argument of |ask_prompt| is the
+prompt text. Because of the anomalous behaviour of |write|, this ought to work
+with arguments of any length, even on versions of \PASCAL\ that only allow
+fixed length strings.
+
+@d ask_prompt(#) == if can_interact then begin
+ {AKT: removed display_ln(#) ;} read_terminal ; end;
+
+@<Lowest...@>=
+ procedure read_terminal;
+ var k: byte ;
+ begin i_reset_terminal;
+ buffer := blank ;
+ if not eof(term_in ) then begin
+ if eoln(term_in) then read_ln(term_in);
+ k:=0;
+ while not eoln(term_in) do
+ begin incr(k); buffer.data[k]:=term_in^; get(term_in);
+ end;
+ buffer.len := k ;
+ upcase(buffer) ;
+ end;
+ o_rewrite_terminal ;
+ end;
+
+@ @<Glob...@>=
+ @!buffer: var_string ; {for terminal input}
+ @!extra_mag:real_num ;
+
+@ The next function reads an integer from the |buffer|. It assumes a previous
+call of |ask_prompt| and returns the default if the input is unrecognisable.
+BODGE: this cant handle negative numbers.
+
+@<Lowest...@>=
+ function get_number(default: integer): integer;
+ var k, m : integer; c:byte ;
+ begin
+ k := 0 ;
+ repeat
+ incr(k) ;
+ c := zord(buffer.data[k] ) ;
+ until (k > buffer.len)
+ or ((c <> " " ) and (c <> "+" ) ) ;
+ if (k > buffer.len) or (c < "0") or (c > "9" )
+ then get_number := default
+ else
+ begin
+ m:=0;
+ while (c >="0") and (c <="9") do begin
+ m:=10*m+ c -"0"; incr(k);
+ c := zord(buffer.data[k] ) ;
+ end;
+ get_number := m ;
+ end;
+ end;
+
+@ If the printer is actually a VDU, then possibly the user will want to pause
+at intervals.
+
+@<Check pause@>=
+ if can_interact and do_pause and (PR_v >= next_pause) then begin
+ display_ln(pause_ask);
+ i_reset_terminal;
+ read_ln (term_in );
+ o_rewrite_terminal ;
+ string_show(pause_after);
+ next_pause := next_pause + pause_steps ;
+ end;
+
+@ @<Pause reset@>=
+ if do_pause then
+ begin next_pause := pause_steps; @<Check pause@> end;
+
+@ @<Const...@>=
+ @<Pause constants, to be defined by the printer change file@>
+
+@ @<Glob...@>=
+ next_pause: integer;
+ pause_after: var_string ;
+
+@* Interface to Operating System, 3: Input from binary files.
+
+The main input file is the \.{DVI} file. Logically, this is just a stream of
+8-bit bytes, with no record or block structure. However VMS \PASCAL\
+apparently cannot handle files of this type; so I have adopted the blocking
+scheme (due to D.R.Fuchs) from the VMS \.{DVItype} change file. But a lot of
+the code has been rewritten. Some other operating systems use similar
+blocking schemes; so this code may possibly work without much change. The
+program deals with two binary file variables: |@!dvi_file| is the main input
+file that we are printing, and |@!tfm_file| the current font metric file from
+which character-width information is being read. Each of these has a name and
+a counter, declared here; also a default name (system dependent, and so
+declared previously). As an initial attempt at downloading, we declare a
+|@!pxl_file|.
+@^Fuchs, D.R.@>
+
+@<Files...@>= , dvi_file, tfm_file, pxl_file
+
+@ @<Glob...@>=
+ dvi_file, tfm_file, pxl_file: byte_file ;
+ dvi_name, tfm_name, pxl_name: var_string ;
+ dvi_indx, tfm_indx, pxl_indx: integer ;
+ font_ok: boolean ;
+
+@ @<Types...@>=
+ @!byte_block=packed array [0..block_length-1] of byte ;
+ @!byte_file= packed file of byte_block;
+
+@ This code opens the \.{DVI} file; clearly, a failure is fatal.
+
+@<Open \.{DVI} file@>=
+ {AKT: removed display_ln ('Opening DVI file ' );}
+ if not open_and_ask(dvi_file, dvi_indx, dvi_name, dvi_def)
+ then abort('Could not open DVI file!');
+ @.Fatal: couldnt open@>@.Opening DVI file@>
+
+@ But when we come to open a font file, we merely report a failure:
+
+@<Open font file@>=
+ font_ok := open_and_ask (tfm_file, tfm_indx, tfm_name, tfm_def) ;
+
+@ Here is the procedure that actually opens files. It searches for a file
+called |name|, supplying missing bits from the default file-specification in
+|other_name|. |f_f| is the file being opened, and |f_c| is its counter.
+
+@<Medium...@>=
+ function open_and_ask
+ (var f_f: byte_file; var f_c: integer; var name: var_string;
+ other_name: def_name_type ) : boolean ;
+ label exit ;
+ var success : boolean;
+ begin
+ success := false;
+ repeat
+ success := open_binary(f_f, name, other_name) ;
+ if success then f_c := 0
+ else
+ @<Try to get an alternative name, |return| if this fails@>
+ until success ;
+ exit: open_and_ask:= success ;
+ end;
+
+@ If this fails, then ask the user for another name. If the operating system
+forbids this, or if the user refuses, then return |false| to indicate failure.
+
+@<Try to get...@>=
+ begin
+ return; {AKT: don't ask user for another name}
+ warn ('Couldnt open file, search name was, ' );
+ string_show(name) ;
+ display_ln (' ') ;
+ display_ln ('default name was ' , other_name );
+ if can_interact then begin
+ ask_prompt('Please type a replacement or NO to abandon search' ) ;
+ name := buffer ;
+ if (name.len = 2) and
+ (name.data[1] = 'N') and (name.data[2] = 'O')
+ then return;
+ end else return;
+ end ;
+ @.error: couldnt open@>@.Please type...@>
+
+@ \.{DVItype} has seven functions for reading integers from the \.{DVI} file
+and two more for the \.{TFM} file. I have condensed these. In order for
+these procedures to work, they must all have as parameters, both the file and
+its attached counter. These macros generate the procedure calls.
+
+@d read_end(#) == # @=)@>
+@d skip(#) == skip_bytes @=(@> # @& file, # @& indx, read_end
+@d get_integer(#) == read_integer @=(@> # @& file, # @& indx, read_end
+@d get_byte(#) == read_byte(# @& file, # @& indx)
+@d get_real(#) == read_real(# @& file, # @& indx)
+
+@<Lowest...@>=
+ function read_byte(var f_file: byte_file; var f_indx: integer) : byte;
+ begin
+ if eof(f_file) then
+ warn('fallen off end of file' )
+ @.error: fallen off...@>
+ else begin
+ read_byte := f_file^[f_indx] ;
+ incr(f_indx);
+ if f_indx =block_length then begin
+ get(f_file );
+ f_indx:=0;
+ end;
+ end;
+ end ;
+
+@#
+ procedure skip_bytes(var f_file: byte_file; var f_indx: integer; n:integer);
+ {discard n bytes from |f_file|}
+ begin
+ if n < 0 then abort('skip_bytes called with negative number');
+ f_indx := f_indx + n;
+ while f_indx >= block_length do
+ begin
+ if eof(f_file) then
+ warn('fallen off end of file' )
+ else get(f_file );
+ f_indx := f_indx - block_length ;
+ end ;
+ end;
+ @.error: fallen off...@> @.Fatal: skip_bytes called...@>
+
+@ The next function reads an integer from a file. |k| specifies the type.
+|abs(k)| is the number of bytes, and the integer will be signed if |k<0|.
+
+@<Lowest...@>=
+ function read_integer
+ (var f_file: byte_file; var f_indx: integer; k: integer): integer;
+ var a, i : byte; n: integer;
+ begin n := get_byte(f );
+ if (k < 0) and (n > 127) then n := n-256 ;
+ for i := 1 to abs(k) - 1 do
+ begin
+ a := get_byte(f ) ;
+ n := n*256 + a ;
+ end ;
+ read_integer := n ;
+ end;
+
+@ A real number is stored in the file as 2 integers, numerator first.
+
+@<Medium...@>=
+ function read_real(var f_file: byte_file; var f_indx: integer ): real_num;
+ var a, b: integer;
+ begin a := get_integer(f )(-4);
+ b := get_integer(f )(-4);
+ if b <= 0 then
+ begin
+ warn('denominator <= 0! '); read_real:= 1;
+ end
+ else read_real:= make_double(a)/make_double(b) ;
+ end;
+ @.error: denominator...@>
+
+@* Page selection.
+
+We have now disposed of all the code that is known to be system-dependent, so
+we can resume a proper top-down description of the program. The basic method
+for processing each page is that all printable characters are written onto a
+structure called a `page image'. This is a list of things called `page
+records'. Each page record represents one printable character, and contains
+two fields giving the intended position on the page. Eventually the image will
+be sorted and then copied to the |printfile|. This means that \.{Crudetype}
+has to remember three sets of coordinates. In order to help to keep track of
+many global variables, we use prefixes. \.{DVI} variables are prefixed with
+|D_|, page image variables with |IM_|, and the printer's variables with |PR_|.
+When this module starts, the \.{DVI} file should be positioned at or before a
+BOP.
+
+@<For each page...@>=
+ {AKT: moved first read_BOP before repeat loop}
+ if (counter[0] >= first_page) then start := true ;
+ {AKT: what if counter[0] is < 0???}
+ if start and (max_pages > 0 )
+ then begin
+ decr(max_pages);
+ display('[', counter[0]:1, ']' ); {Progress report}
+ Read_one_page ;
+ @<Sort the page@>
+ Send_page ;
+ {AKT: removed @<Formfeed@>;}
+ end
+ else if max_pages > 0 then Skip_page
+ else time_to_stop := true;
+ {AKT: only call Formfeed BETWEEN pages; this requires some hackery}
+ read_BOP; {sets max_pages to -1 if no more}
+ time_to_stop := max_pages <= 0;
+ if (not time_to_stop) and start then begin
+ @<Formfeed@>;
+ end;
+
+@ This program only gives a small subset of the page-selection facilities of
+\.{DVItype}. The most you can do is to specify the starting page and the
+maximum number of pages to print. This will be controlled by these variables:
+
+@<Glob...@>=
+ start, time_to_stop: boolean;
+ first_page, max_pages: integer;
+ counter: array[0..9] of integer ;
+
+@ @<Set init...@>=
+ start := false ; time_to_stop := false;
+ for in_i := 0 to 9 do counter[ in_i ] := 0 ;
+
+@ |@!D_com| is the \.{DVI} command byte, |@!D_par| its first parameter.
+
+@<Top level...@>=
+ procedure Read_one_page ;
+ var D_com: byte; D_par: integer; end_page: boolean ;
+ begin end_page := false;
+ @<Set up an empty page image@>
+ repeat
+ @<Get \.{DVI} command |D_com|, and do it@>
+ until end_page;
+ end ;
+
+@#procedure Skip_page ;
+ var D_com: byte; D_par: integer; end_page: boolean ;
+ begin
+ end_page := false;
+ repeat
+ @<Skip \.{DVI} command, but we must process any |font_def|@>
+ until end_page;
+ end ;
+
+@* Translating the device-independent file, 1: The big switch.
+
+Refer to \.{DVItype} or to \.{TUG}boat (Vol.3, No.2) for a description of the
+\.{DVI} file format. As in \.{DVItype}, we process each \.{DVI} command via a
+big |case| statement. But 192 of the cases are very similar, so lets dispose
+of them first.
+ @.TUGboat@>
+
+@d id_byte=2 {identifies the kind of \.{DVI} files described here}
+@d move_right ==
+ D_h := D_h + D_dis ;
+ IM_h := IM_h + IM_dis
+
+@<Get \.{DVI} command...@>=
+ D_com := get_byte(dvi);
+ if D_com < 128 then begin
+ set_character(D_com); move_right ;
+ end
+ else if (D_com >= 171) and (D_com <= 234) then
+ change_font(D_com - 171)
+ else
+
+@ @<Skip \.{DVI} command...@>=
+ D_com := get_byte(dvi);
+ if (D_com < 128)
+ or ((D_com <= 234) and (D_com >= 171))
+ then do_nothing
+ else
+
+@ Now we come to the |case| statement proper. This section of the program is
+long and complicated, and I have tried to clean it up. Some commands want an
+unsigned parameter, called |D_par|, to be read from the file. We use
+|four_cases| for those. Others want a signed parameter; they are all
+movements. We use |move_cases| for those.
+
+@d four_case_end(#) == # ; end
+@d four_cases(#)==
+ #,#+1,#+2,#+3: begin D_par := get_integer(dvi)( D_com - # + 1 );
+ four_case_end
+@d move_cases(#)==
+ #,#+1,#+2,#+3: begin D_par := get_integer(dvi)( # - D_com - 1 );
+ four_case_end
+@#
+
+@<Get \.{DVI} command...@>=
+case D_com of
+ four_cases(128) (set_character(D_par); move_right );
+ 132: begin set_rule; move_right ; end;
+ four_cases(133) (set_character(D_par) );
+ 137: set_rule ;
+ 138: do_nothing ;
+@#
+ 140: end_page := true ;
+ 141: push;
+ 142: pop;
+ move_cases(143) (D_h := D_h+D_par);
+ 147:{W0} D_h := D_h+D_w ;
+ move_cases(148) (D_w := D_par; D_h := D_h+D_w );
+ 152:{X0} D_h := D_h+D_x ;
+ move_cases(153) (D_x := D_par; D_h := D_h+D_x );
+ move_cases(157) (move_down(D_par));
+ 161:{Y0} move_down(D_y);
+ move_cases(162) (D_y := D_par; move_down(D_y) );
+ 166:{Z0} move_down(D_z);
+ move_cases(167) (D_z := D_par; move_down(D_z) );
+@#
+ four_cases(235) (change_font(D_par) );
+ four_cases(243) (define_font(D_par) );
+@#
+ @<Fourteen illegal cases: print suitable error messages@>
+end ;
+
+@ When skipping a page, we must throw away parameters instead of using them.
+
+@d four_throw(#) ==
+ #,#+1,#+2,#+3: skip(dvi)(D_com - # + 1 )
+@#
+
+@<Skip \.{DVI} command...@>=
+case D_com of
+ four_throw(128);
+ 132, 137: skip(dvi)(8); {sizes of a rule}
+ four_throw(133);
+ 138: ;
+ 140: end_page := true ;
+ 141,142: ;
+ four_throw(143);
+ 147: ;
+ four_throw(148);
+ 152: ;
+ four_throw(153);
+ four_throw(157);
+ 161: ;
+ four_throw(162);
+ 166: ;
+ four_throw(167);
+@#
+ four_throw(235);
+ four_cases(243) (define_font(D_par) );
+@#
+@<Fourteen illegal...@>
+end ;
+
+@ Finally, there are 14 illegal values of |D_com| that generate various errors.
+
+@<Fourteen illegal...@>=
+ four_cases(239)
+ ({AKT: removed warn('ignoring \special') ;} skip(dvi)(D_par) );
+ 139, 247, 248, 249:
+ bad_dvi('byte: ', D_com:1 , ' out of context inside page' ) ;
+ 250,251,252,253,254,255:
+ bad_dvi('Illegal command byte, ', D_com ) ;
+ @.error: cant do xxx@>
+ @.Fatal: Bad DVI file@>
+
+@* Translating the device-independent file, 2: Paging and the stack.
+
+The definition of \.{DVI} files refers to six registers, (|D_h, D_v, D_w,
+D_x, D_y, D_z|), which hold integer values in \.{DVI} units. We shall need
+additional registers in order to calculate a rounded position. From time to
+time, we save the current values of these on a stack, represented by the
+following arrays.
+
+@d max_stack = 100 {\.{DVI} files shouldn't |push| beyond this depth}
+
+@<Glob...@>=
+ D_h,D_v,D_w,D_x,D_y,D_z : integer; {current \.{DVI} state values}
+ D_h_stack, D_v_stack, D_w_stack, D_x_stack, D_y_stack, D_z_stack:
+ array [0..max_stack+2] of integer; {pushed down values }
+ @!stack_ht: 0..max_stack; {current stack depth}
+ just_pushed: boolean;
+
+@ @<Set up an empty page image@>=
+ D_h := 0 ; D_v := 0 ;
+ D_w := 0 ; D_x := 0 ;
+ D_y := 0 ; D_z := 0 ;
+ stack_ht := 0 ;
+ rail_base := 0 ;
+ just_pushed := false ;
+
+@ Here is how \.{DVI}type manipulates the stack: The first |push| on a page
+fills the zeroth place on the stack and sets |stack_ht| = 1. So the used
+places are numbered |0..stack_ht- 1|. Now |push| and |pop| do the obvious
+things.
+
+@<Lowest...@>=
+ procedure push;
+ var x: real_num ;
+ begin if stack_ht=max_stack then
+ warn('Capacity exceeded (stack size=', max_stack:1,')')
+ else begin
+ D_h_stack[stack_ht]:=D_h; D_v_stack[stack_ht]:=D_v;
+ D_w_stack[stack_ht]:=D_w; D_x_stack[stack_ht]:=D_x;
+ D_y_stack[stack_ht]:=D_y; D_z_stack[stack_ht]:=D_z;
+ @<Some adjustments are needed here for rounding@>
+ incr(stack_ht); just_pushed := true ;
+ end;
+ end;
+ @.error: Capacity exceeded @>
+
+@# procedure pop;
+ begin if stack_ht=0 then warn('POP illegal at level zero')
+ else begin
+ decr(stack_ht);
+ D_h:=D_h_stack[stack_ht]; D_v:=D_v_stack[stack_ht];
+ D_w:=D_w_stack[stack_ht]; D_x:=D_x_stack[stack_ht];
+ D_y:=D_y_stack[stack_ht]; D_z:=D_z_stack[stack_ht];
+ IM_h := IM_h_stack[stack_ht];IM_v := IM_v_stack[stack_ht];
+ @<Set |rail_base|@>
+ end;
+ end;
+ @.error: POP illegal...@>
+
+@ This procedure gets called when we expect to read a new page. It looks for
+the next |BOP|; if it finds the postamble instead, it sets |max_pages < 0| as
+a signal.
+
+@d POST = 248
+@d NOP = 138
+@d BOP = 139
+
+@<Top level...@>=
+ procedure read_BOP;
+ var k: byte ; D_par:integer ;
+ begin
+ repeat k:= get_byte(dvi);
+ if (k>= 243)and(k <= 246 ) then {a |font_def|}
+ begin D_par:=get_integer(dvi) (k-242 ); define_font(D_par); k:=NOP;
+ end;
+ until k<>NOP;
+ if k=POST then
+ max_pages := -1
+ else if k<>BOP then bad_dvi('byte is not BOP')
+ @.Fatal: Bad DVI file@>
+ else begin
+ for k:=0 to 9 do counter[k]:= get_integer(dvi)(-4);
+ skip(dvi)(4);
+ end;
+ end;
+
+@ A \.{DVI}-reading program that reads the postamble first need not look at the
+preamble; but \.{Crudetype} reads the \.{DVI} file sequentially.
+
+@d PRE=247 {preamble}
+
+@<Read \.{DVI} preamble@>=
+ bbb:= get_byte(dvi); {fetch the first byte}
+ if bbb<>PRE then bad_dvi('First byte isn''t start of preamble!');
+ @.Fatal: Bad DVI file@>
+ bbb:= get_byte(dvi); {fetch the identification byte}
+ if bbb<>id_byte then
+ warn('identification byte should be ',id_byte:1,', it is actually', bbb:1 );
+ @.error: identification...@>
+ @<Compute the conversion factors@>;
+ bbb:= get_byte(dvi); {fetch the length of the introductory comment}
+ {AKT: removed display(' ');}
+ for nnn := 1 to bbb do
+ {AKT: was display(zchr(get_byte(dvi)));}
+ bbb := get_byte(dvi);
+ {AKT: removed display_ln(' ');}
+
+@ The conversion factor |h_conv| is figured as follows: There are exactly
+|n/d| decimicrons per \.{DVI} unit and 254000 decimicrons per inch, and
+|h_resolution| |h_steps| per inch.
+
+@<Glob...@>=
+ dvi_factor, h_conv, v_conv, magnification : real_num;
+ nnn:integer; {general purpose register}
+ bbb: byte ;
+
+@ @<Compute the conversion factors@>=
+ dvi_factor := get_real(dvi)/254000.0 ;
+ magnification := get_integer(dvi)(4) / 1000 ;
+ dvi_factor := dvi_factor * magnification ;
+ {This converts \.{DVI} units to inches (on an ideal device) }
+ h_conv:= dvi_factor * h_resolution * h_fudge * extra_mag;
+ v_conv:= dvi_factor * v_resolution * v_fudge * extra_mag ;
+
+@* Translating the device-independent file, 3: Setting a Rule.
+
+|D_p| is the height and |D_q| is the width. A rule has to be assembled from
+the available characters. First: is the rule to be set at all? Second: is it
+horizontal or vertical? (Because of the limited name lengths, we call them
+|Post| and |Rail|.) The test applied here is quite arbitrary.
+
+@<Medium...@>=
+ procedure set_rule;
+ var D_p,D_q: integer;
+ begin
+ D_p:=get_integer(dvi) (-4);
+ D_q:=get_integer(dvi)(-4);
+ if (D_p<=0)or(D_q<=0) then
+ {an invisible rule! Dont ask me why \TeX\ wants to do this}
+ else if D_p*v_conv <= post_height/2
+ then do_rail(D_p, D_q)
+ else do_post(D_p, D_q);
+ end;
+
+@ Setting a vertical rule is simple: we just fill all the space with the
+relevant character.
+
+@<Lowest...@>=
+ procedure do_post(D_rul_ht, D_rul_width: integer);
+ var vn, vi, hn, hi, post_v, rule_hp : integer;
+ rule_cod: code_object ;
+ begin
+ @<|Post| set sizes@>;
+ for vi := vn - 1 downto 0 do
+ begin
+ post_v := IM_v - vi * post_height ;
+ for hi := 1 to hn do
+ begin
+ rule_hp := IM_h + (hi - 1) * post_width ;
+ do_set_char(post_v, rule_hp, rule_cod);
+ end;
+ end;
+ end;
+
+@ Note that whereas \.{DVItype} rounds all sizes up, \.{Crudetype} rounds to
+nearest integer. This seems more likely to work on a crude resolution.
+But we force the rounded size to be |>= 1| .
+
+@<|Post| set...@>=
+ round_IM_h ( 0);
+ hn := round(D_rul_width * h_conv / post_width );
+ vn := round(D_rul_ht * v_conv / post_height);
+ if hn <= 0 then hn := 1;
+ if vn <= 0 then vn := 1;
+ rule_cod := post_char;
+
+@ A horizontal rule is more complicated, as there is then a selection of
+characters. This matters if the printer has only a very coarse vertical
+positioning. For example, a line printer has only minus and underscore, but a
+VT-100 has 5 bars at different heights. |@!rail_types| should be set to the
+number of different horizontal bars that the printer can draw within one
+|v_step|. We measure the vertical position of a rule in |rail_steps|,
+which are smaller than |v_steps| in the same ratio.
+
+@<Glob...@>=
+ rail_chars : packed array [1..rail_types] of code_object ;
+ {Number from bottom of page up; so no. 1 might be an underscore}
+ rail_base : integer ;
+ {Position of bottom edge of a rule in |rail_steps|}
+ post_char : code_object ;
+
+@ @<Const...@>=
+ @<Rule setting constants@>
+ {Printer-dependent, so they must go at the end of the file}
+
+@ @<Lowest...@>=
+ procedure do_rail(D_rul_ht, D_rul_width: integer);
+ var vn, vi, hn, hi,
+ rail_v, {Current position in |rail_steps|}
+ char_vp, {Position in |v_steps| where a rule char will be set}
+ rule_hp: integer;
+ rule_cod: code_object ;
+ char_i : 1..rail_types ; {indicates which character to be used}
+ begin
+ @<|Rail| set sizes@>
+ for vi := vn-1 downto 0 do begin
+ rail_v := rail_base - vi ;
+
+@ Now to assign |char_i| and |char_vp|. The easiest way is to consider a simple
+example. Suppose |rail_types = 5| and |rail_v = 50|. This addresses the
+underscore at the bottom edge of a text character at |10 v_steps|.
+So |char_i| wants to be 1 and |char_vp| 10. So...
+
+@<Lowest...@>=
+ char_vp := ((rail_v - 1) div rail_types ) + 1 ;
+ char_i := rail_types - ((rail_v - 1) mod rail_types ) ;
+ rule_cod := rail_chars [ char_i] ;
+ for hi := 1 to hn do begin
+ rule_hp := IM_h + (hi-1) * rail_width ;
+ do_set_char(char_vp, rule_hp, rule_cod) ;
+ end;
+end;
+end;
+
+@ @<|Rail| set...@>=
+ round_IM_h ( 0);
+ hn := round(D_rul_width * h_conv/ rail_width);
+ vn := round(D_rul_ht * v_conv * rail_types/ rail_height );
+ if hn <= 0 then hn := 1;
+ if vn <= 0 then vn := 1;
+
+@ Now consider how to set |rail_base|. Horizontal rules are mostly used for
+underlining text, and then they should be aligned with the underscore
+character on the same line of text. So normally, we just do the following. The
+exception occurs when the \.{DVI} file does an explicit vertical move.
+
+@<Set |rail_base|@>=
+ rail_base := IM_v * rail_types ;
+
+@* Translating the device-independent file, 4: Changing and defining Fonts.
+
+The following tables describe all the \TeX\ fonts that \.{Crudetype}
+currently knows about.
+
+@<Glob...@>=
+ nf: D_font_ptr ;
+ {The number of fonts so far defined. These will be numbered |0..nf-1| }
+ @!font_num, {external font numbers}
+ @!font_space, {boundary between ``small'' and ``large'' spaces}
+ @!scheme, {pointer to coding scheme}
+ @!first_ch, {First character in the font}
+ @!last_ch: {and last}
+ array [D_font_ptr] of integer;
+ D_width: array[D_font_ptr, D_char_ptr ] of integer ;
+ {character widths, as given in \.{TFM} file, should be in \.{DVI} units}
+ @!D_check, {the font checksum must be global for HPGF}
+ thin_space, D_font, cur_scheme: integer ; {The current values}
+
+@ @<Type...@>=
+ D_font_ptr = 0..max_D_fonts;
+ D_char_ptr = 0..max_D_char;
+
+@ The size of the tables can be altered at compile time to extend or reduce
+\.{Crudetype}'s capacity.
+
+@<Constants...@>=
+ @!max_D_fonts=100; {maximum number of distinct fonts per \.{DVI} file}
+ @!max_D_char =255; {AKT: was 127 but we want to handle PostScript fonts}
+
+@ Initially, all these tables are blank.
+
+@<Set init...@>=
+ nf:=0;
+ for in_i := 0 to max_D_fonts do
+ begin
+ font_num[in_i ] := 0 ;
+ scheme[in_i ] := 0 ;
+ first_ch[in_i ] := 0 ;
+ last_ch[in_i ] := 0 ;
+ font_space[in_i]:= 0 ;
+ end;
+
+@ @<Set up an empty page image@>=
+ D_font := nf ;
+ cur_scheme := 0 ;
+
+@ @<Medium...@>=
+ procedure change_font (D_new: integer);
+ begin
+ D_font := 0 ;
+ font_num[nf]:=D_new;
+ while font_num[D_font]<>D_new do incr(D_font);
+ if D_font = nf then
+ warn('Undefined font called for, number ', D_new:1 );
+ @.error: Undefined font@>
+ cur_scheme := scheme[D_font] ;
+ thin_space := font_space[D_font] ;
+ end;
+
+@ The following procedure is called whenever we read a |font_def| command from
+the \.{DVI} file. In general, any error while defining a font causes a jump to
+label |bad_font|, leaving the new font undefined.
+
+@d bad_font = 9999
+@d good_font = 9998
+@d font_error(#) == begin
+ warn(#); display_ln('font number ', D_new:1, ' cannot be loaded') ;
+ goto bad_font ;
+end
+
+@<Medium...@>=
+ procedure define_font (D_new:integer );
+ label bad_font , good_font ;
+ var @<|font_def| vars@>
+ begin
+ @<Read the font parameters from the \.{DVI} file,
+ calculate scaling factors@>;
+ @<Try to load the new font, unless there are problems@>;
+ good_font:
+ @<Final checks; various mild errors which often are symptoms of bugs@>
+ incr(nf) ; {the new font is officially present}
+ bad_font: if font_ok then close_binary(tfm_file);
+ end;
+
+@ First we read the parameters from the \.{DVI} file. Whatever errors are
+found, we must try to do this, or we lose place in the file.
+
+@<|font_def| vars@>=
+ scale_size, design_size, k, f : integer;
+ dir_len, {length of the area/directory spec}
+ nam_len:byte; {length of the font name proper}
+ font_mag: real_num;
+
+@ @<Read the font parameters...@>=
+ @!D_check := get_integer(dvi)(-4) ;
+ scale_size:= get_integer(dvi)( -4) ;
+ design_size:= get_integer(dvi)(-4) ;
+ dir_len:= get_integer(dvi)(1) ;
+ nam_len:= get_integer(dvi)(1) ;
+ nam_len := nam_len + dir_len ;
+ if nam_len = 0 then
+ font_error('null font name! ')
+ @.error: null font name@>
+ else if nam_len >= string_length then
+ font_error('too-long font name! length = ', nam_len:1 ) ;
+ @.error: too-long font name@>
+ tfm_name := blank ;
+ for k:=1 to nam_len do begin
+ tfm_name.data[k] := zchr(get_byte(dvi)) ;
+ end;
+ tfm_name.len := nam_len ;
+ upcase(tfm_name) ;
+ {AKT: removed
+ display_ln(' ');
+ string_show(tfm_name);
+ display(' ');
+ }
+
+@ Next, check that the sizes are reasonable:
+
+@<Read the font parameters...@>=
+ if (scale_size<=0)or(scale_size>=@'1000000000) then
+ font_error('--- bad scale (',scale_size:1,')!')
+ @.error: bad scale@>
+ else if (design_size<=0)or(design_size>=@'1000000000) then
+ font_error('--- bad design size (',design_size:1,')!') ;
+ @.error: bad design size@>
+ font_mag := scale_size/design_size ;
+ if (font_mag > 1000) or (font_mag < 0.001) then
+ warn('thats a very unusual font magnification!!! ', font_mag) ;
+ @.error: unusual font mag...@>
+ if nf=max_D_fonts then
+ abort('Crudetype capacity exceeded (max fonts=', max_D_fonts:1,')!');
+ @.Fatal: Capacity exceeded... @>
+ font_num[nf]:=D_new; f:=0;
+ while font_num[f]<>D_new do incr(f);
+ if f<nf then font_error('---this font was already defined!');
+ @.error: font already defined@>
+ font_space[nf] := scale_size div 6 ; {a `thin space' }
+
+@* Loading the font file.
+
+See \.{TFTOPL} or \TeX 82 for details of the \.{TFM} file format. The
+description given in \.{TUGboat} (Vol.2, no. 1) is apparently no longer
+accurate. The only difference that I have seen is that all words of the font
+header array after the first 2 are now apparently regarded as optional.
+@.TFTOPL@> @.TeX82@> @.TUGboat@>
+
+@<Try to load...@>=
+ @<Open font file@>
+ if not font_ok then
+ font_error('---TFM file can''t be opened!');
+ @.error: TFM file cant be opened@>
+ @<Read past the header data, leave the file pointer just after the header@>
+ @<Read the character-width indices@>
+ @<Read the widths, copy them into the font array@>
+
+@ @<|font_def| vars@>=
+ @!TFM_check,
+ @!lh, {length of the header data, in four-byte words}
+ @!nw:integer; {number of words in the width table}
+
+@ @<Read past the header...@>=
+ skip(tfm)(2); lh:= get_integer(tfm)(2);
+ first_ch[nf]:=get_integer(tfm)(2); last_ch[nf]:=get_integer(tfm)(2);
+ if (last_ch[nf]<first_ch[nf]) or (last_ch[nf] > max_D_char) then
+ font_error(
+ 'Illegal values for first_char and/or last_char, first_char = ',
+ first_ch[nf]:1 , ' last_char = ', last_ch[nf]:1 );
+ @.error: Illegal value@>
+ nw:=get_integer(tfm)(2);
+ if (nw=0)or(nw>256) then
+ font_error('Illegal value for nw, nw= ', nw );
+ @.error: Illegal value@>
+ skip(tfm)(14);
+ TFM_check := get_integer(tfm)(-4);
+ skip(tfm)(4);
+ @<Get coding scheme and re-align file, then see if the printer knows it@>
+
+@ The header contains |4*lh| bytes, of which 8 have been read so far. If it
+conforms to the \.{TUGboat} format, then the next byte (|@!ck|, say) is the
+number of bytes in the coding scheme name. So, first we must try to see if a
+scheme is present; if so, then we will read |ck+1| bytes and chuck the rest.
+If no coding scheme is present, we simply skip the rest of the header.
+Internally, scheme names are represented by |var_string|s.
+
+@<Get cod...@>=
+ TFM_scheme := blank ;
+ if lh < 2 then font_error( ' Header must have at least 2 words')
+ else if lh = 2 then do_nothing
+ else begin
+ ck := get_byte(tfm);
+ if ( ck >= 40 ) or ( ck > 4*lh - 9) then
+ skip(tfm)(4*lh - 9)
+ {there is something here, but not a coding scheme}
+ else begin
+ TFM_scheme.len := ck ;
+ for j := 1 to ck do
+ TFM_scheme.data[j] := zchr(get_byte(tfm)) ;
+ skip(tfm)(4*lh - ck - 9);
+ upcase(TFM_scheme) ;
+ end;
+ end;
+
+@ @<|font_def| vars@>=
+ j , ck : byte ;
+ @!coding_scheme, TFM_scheme: var_string ; {coding scheme of current font}
+
+@ Now we can start reading the character widths.
+
+@<|font_def| vars@>=
+ @!in_width:array[byte] of integer; {\.{TFM} width data in \.{DVI} units}
+ @!wid_ptr: array[byte] of byte ; {pointers into |in_width|}
+ b3,b2,b1,b0: byte; {bytes from \.{TFM} file}
+ @!alpha,@!beta, @!z :integer;
+
+@ @< Read the character-width indices...@>=
+ for k:=first_ch[nf] to last_ch[nf] do
+ begin wid_ptr[k] := get_byte(tfm); skip(tfm)(3);
+ if wid_ptr[k] > nw then font_error('impossible width ' , wid_ptr[k]);
+ end;
+ @.error: impossible width @>
+
+@ Here is the width computation. This code is copied from \.{DVItype}. See
+that program for an explanation.
+
+@<Read the font parameters...@>=
+ z := scale_size ;
+ alpha:=16*z; beta:=16;
+ while z>=@'40000000 do
+ begin z:=z div 2; beta:=beta div 2;
+ end;
+
+@ @<Read the widths...@>=
+ for k:=0 to nw-1 do
+ begin
+ b0 := get_byte(tfm); b1 := get_byte(tfm);
+ b2 := get_byte(tfm); b3 := get_byte(tfm);
+ in_width[k]:=
+ (((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta;
+ if b0 = 255 then in_width[k]:=in_width[k]-alpha
+ else if b0 <> 0 then
+ font_error('Out-of-bounds value for b0') ;
+ @.error: font: Out-of-bounds |b0|@>
+ end ;
+
+@ Rounding widths. This bit of \.{DVItype} is changed, because \.{Crudetype}
+has to calculate rounded positions by a completely different method.
+
+@<Read the widths...@>=
+ if in_width[0]<>0 then font_error('the first width should be zero ');
+ @.error: first width...@>
+ for k:= first_ch[nf] to last_ch[nf] do
+ D_width[nf, k] := in_width[ wid_ptr[k]] ;
+
+@ Then there are various erroneous states that do not necessarily show that
+the font is corrupt, but may indicate bugs in the program. In principle, a
+character might have negative width, but I do not believe it.
+
+@d bad_char = -32766 {Indicates an unprintable character}
+@d foot == 50000000 {about a foot}
+
+@<Final checks...@>=
+ for k:= first_ch[nf] to last_ch[nf] do
+ if (D_width[nf, k] < 0) or (D_width[nf, k] > foot) then begin
+ warn('Way-out width = ', D_width[nf,k]:1,
+ 'DVI units, character number ', k:1 );
+ codes[ scheme[nf], k].breadth := bad_char ;
+ end;
+ if (D_check<>0)and(TFM_check<>0)and(D_check<>TFM_check) then
+ begin warn('check sums do not agree!');
+ @.error: check sums...@>
+ display_ln('DVI check was: ', D_check, ' TFM check was: ', TFM_check);
+ display(' ');
+ end;
+ {AKT: removed display_ln('---loaded at size ',scale_size:1,' DVI units');}
+ font_mag := (font_mag -1) * 100.0 ;
+ {AKT: removed
+ if abs(font_mag) > 1 then
+ begin display_ln(' ');
+ display_ln(' (this font is magnified ', round(font_mag):1,'%)');
+ end;
+ }
+ @.this font is magnified@>@.error: Way-out width@>
+
+@* Coding schemes.
+
+In this section we describe the mapping from characters in \TeX\ fonts to
+characters in the printer's fonts (which are presumably much fewer). All
+characters on a crude printer are the same size. We therefore need one piece
+of data, not for each \TeX\ font, but for each coding scheme. The mapping is
+defined in an array called |codes|. For each character |c| in a \TeX\ font
+whose coding scheme has internal number |s|, |codes[s,c]| describes the
+corresponding printer character. Also |known_schemes[s]| is a character
+string which usually contains the coding scheme of that \TeX\ font.
+|max_codes| is the number of coding schemes the program knows about. First,
+define that structure:
+
+@ @<Glob...@>=
+ @!known_schemes: array[code_ptr] of var_string ;
+ @!codes: array[code_ptr, D_char_ptr] of code_object;
+ no_char: code_object ;
+
+@ @<Types...@>=
+ code_object = packed record
+ breadth: i_word ;
+ case boolean of
+ true: (IM_font: byte ; IM_char: byte );
+ {Printers font and character}
+ false: (multi: i_word) ;
+ end;
+ @! code_ptr = 0..max_codes;
+ {0 is a coding scheme the printer doesnt know about}
+
+@ Initially, all these tables are blank. If |c| is a |code_object|, then
+|c.breadth| will usually be its printed width in |h_steps|. |c.breadth =
+bad_char| indicates that the character is unprintable. |bad_char| can be any
+large negative value. Other negative values of |@!breadth| indicate other
+types of peculiar characters.
+
+@d down_loaded = -32765
+
+@<Set init...@>=
+ no_char.breadth := bad_char ;
+ no_char.IM_font := 0 ;
+ no_char.IM_char := 0 ;
+ for in_i := 0 to max_codes do
+ for in_j := 0 to max_D_char do begin
+ codes[in_i, in_j] := no_char ;
+ end;
+
+@ So when a font is read in, we try to assign the right value to its |scheme|.
+If the printer is not absolutely crude, then it might have italic or bold
+fonts. Then we might want a coding scheme to correspond to a single \TeX\
+font. So first we look at the actual font name and see if that matches any
+of the |known_schemes|. But if the printer is |fixed_width|, then all fonts
+of the same face are the same size, so we drop the font size digits off the
+end of the name.
+
+@<Get cod...@>=
+ k := tfm_name.len ;
+ if fixed_width then
+ while (zord(tfm_name.data[k]) >= "0" ) and
+ (zord(tfm_name.data[k]) <= "9" ) do
+ decr(k) ;
+ coding_scheme:= tfm_name;
+ chop_length(coding_scheme, k) ;
+ j := max_codes ;
+ while (j > 0) and (coding_scheme.data <> known_schemes[j].data ) do decr(j);
+ scheme[nf] := j ;
+ if j = 0 then
+
+@ If the font name is not in |known_schemes|, then we try again with the
+scheme given in the \.{TFM} file. If that fails, then try if we can download
+the font. If that fails, then the font is deemed to be unprintable, so we do
+not load it.
+
+@<Get cod...@>=
+ begin
+ j := max_codes ;
+ while (j > 0) and (TFM_scheme.data <> known_schemes[j].data ) do decr(j);
+ scheme[nf] := j ;
+ end;
+ if (j = 0) and can_dl_font then
+ @<Download a whole font@>
+ else if j = 0 then begin
+ scheme[nf] := 9 ; {AKT: handle PostScript font }
+ {AKT: was
+ display ('Scheme is: ') ;
+ string_show(TFM_scheme) ;
+ font_error(' That coding scheme is unknown' );
+ AKT}
+ end;
+ @.error: unknown coding scheme @>
+
+@ This procedure sets a character. The character to be set is number |@!c_num|
+in the current font. I have deleted the bit of \.{DVItype} that deals with
+oriental fonts, as I dont believe that crude printers can support them.
+
+@<Medium...@>=
+ procedure set_character(c_num: integer );
+ var cod: code_object;
+ d_i, d_j : integer; {Used for downloading}
+ begin
+ if cur_scheme = 0 then
+ else if (c_num < first_ch[D_font] ) or (c_num > last_ch[D_font] )
+ then begin
+ warn('character ',c_num:1,' invalid in font number ',
+ font_num[ D_font]:1 );
+ @.error: character invalid...@>
+ end
+ else begin
+ cod := codes[ cur_scheme, c_num];
+ if cod.breadth <> bad_char then begin
+ round_IM_h( c_num) ;
+ if cod.breadth = down_loaded then
+ @<Enter a download request for |cod| and adjust its |breadth|@> ;
+ do_set_char(IM_v, IM_h, cod ) ;
+ @<Do messy things to adjust the positions |D_h|, |IM_h|, etc@>;
+ end;
+ end;
+ end;
+
+@ @<Forw...@>=
+ procedure do_set_char(Set_v, Set_h: i_word; cod: code_object ); forward;
+
+@ @<Lowest...@>=
+ procedure do_set_char ;
+ var k_i, k_k, temp_v, temp_h: i_word ;
+ m_c: code_object ;
+ k_ptr: 1..max_ligs;
+ begin
+ if cod.breadth >= 0 then begin
+ @<Check the position@>
+ @<Add the record to the page image@>
+ end
+ else if cod.breadth = bad_char then do_nothing
+ else @<Set multi-character command@> ;
+ end;
+
+@* Multiple-character commands.
+
+Several crude printers (e.g. daisy-wheels) have only a limited set of
+characters, which cannot be extended. Sometimes you can generate more
+characters by overstriking. \.{Crudetype} can be programmed to do this, by
+placing suitable entries into a table called |ligatures|. The name is chosen
+by analogy with the |lig_kern| programs in \.{TFM} files, but the data is
+completely different. When one \TeX\ character maps onto several printer
+characters, we call the image a `multi-character' command.
+
+@<Const...@>=
+ max_ligs = 10000 ;
+
+@ @<Glob...@>=
+ ligatures : array[1..max_ligs] of lig_thing;
+ top_of_ligs: 0..max_ligs ; {highest used point in |ligatures|}
+
+@ @<Types...@>=
+ trio = 1..3 ;
+ lig_thing = packed record
+ case trio of
+ 1: (v_move: i_word ;
+ h_move: i_word) ;
+ 2: (code: code_object) ;
+ 3: (num : i_word ;
+ guard : i_word) ;
+ end;
+
+@ @<Set init...@>=
+ top_of_ligs := 0;
+ for in_i := 1 to max_ligs do ligatures[ in_i].code := no_char ;
+
+@ The |code_object| addresses a multiple character when its |breadth| is
+negative, and not one of the special classes defined above. It must then be
+the |false| variant, and its |multi| field (which must be |>0|) points to the
+corresponding entry in |ligatures|. Suppose that field is |c| . Then
+|ligatures[c]| is the last entry of a string of items that defines the
+replacement text of the |code|. It should be of the third variant; The |num|
+field of this entry counts the number of characters that |code| expands into.
+The |guard| field is an arbitrary impossible value called |sentry| to give a
+check on the data in |ligatures| .
+
+@d sentry = -32767
+
+@<Set multi...@>=
+ begin
+ if (cod.multi <= 0) or (cod.multi > top_of_ligs) then
+ warn('Illegal value of char in multi-character command')
+ @.error: Illegal value@>
+ else begin
+ k_ptr := cod.multi ;
+ if ligatures[k_ptr].guard <> sentry then
+ warn('Sentry not found in Kerns ' ) ;
+ @.error: Sentry ...@>
+ k_i := ligatures[k_ptr].num ;
+ k_ptr := k_ptr - 2*k_i ;
+ if (k_i <= 0) or (k_ptr < 0 ) then
+ warn('Illegal value of k_i in multi_character command');
+ @.error: Illegal value@>
+ for k_k := 1 to k_i do
+ @<Get that character and write it @>;
+ end;
+ end
+
+@ Each character of a multi-character command needs 2 entries in |ligatures|.
+The first defines the position, the second defines the character. |v_move| and
+|h_move| are relative to the current (rounded) position |Set_v, Set_h| and use
+the same units. A multi-character command can call another one recursively.
+
+@<Get that character ...@>=
+ begin
+ temp_v := Set_v + ligatures[k_ptr].v_move ;
+ temp_h := Set_h + ligatures[k_ptr].h_move ;
+ incr(k_ptr);
+ m_c := ligatures[k_ptr].code ;
+ do_set_char(temp_v, temp_h, m_c ) ;
+ incr(k_ptr);
+ end;
+
+@* Getting data into the |codes| array.
+
+This is clearly a very long and error-prone job, so the next procedures are put
+in to reduce this. First suppose that: in the \TeX\ coding scheme with number
+|s|, a run of |length| characters starting from |start| maps onto a run of
+consecutive characters in printer font |PR_font|, starting at |PR_first|. This
+procedure will enter the whole run at one go.
+
+@<Lowest...@>=
+ procedure alphabet
+ (start, length: byte; s: code_ptr ; PR_font, PR_first : byte );
+ var i:integer; ccc:code_object;
+ begin @<Check alphabet data@>;
+ ccc.IM_font := PR_font ;
+ ccc.breadth := char_width ;
+ for i := 0 to length-1 do begin
+ ccc.IM_char := PR_first +i;
+ codes[s, start+i] := ccc ;
+ end; end;
+
+@ @<Check alph...@>=
+ if (s < 1) then abort('alphabet: scheme < 1 ')
+ else if (s > max_codes) then abort('alphabet: scheme too large')
+ else if (PR_first < 0) then abort('alphabet: negative first')
+ else if (start < 0) then abort('alphabet: negative start')
+ else if (length < 0) then abort('alphabet: negative length')
+ else if (start + length -1 > max_D_char) then abort('alphabet: overflow')
+ @.Fatal: alphabet...@>
+
+@ Clearly, |alphabet| will only cover a very small part of the problem. The
+next procedure enters data into a subset of the |codes| array corresponding to
+a single row of a \TeX\ font. In the standard font tables, row number |m| is
+the subrange |8*m..8*m+7| of a font. It is hoped that when the calls of
+procedure |row| are written out in a program, the result will be (just about)
+legible, whereas a flood of statements like \begintt
+ codes[i,j].IM_font := 121; \endtt
+is certainly not legible.
+
+The parameters are as follows. |@!row_spec| specifies what characters are to
+go into the row. |@!scheme | is the number assigned to the \TeX\ coding scheme
+within the program. |@!row_num | is the number of the row in that scheme
+(starting from 0). |@!first_font| is the initial printer font.
+
+@<Top...@>=
+ procedure row
+ (row_spec: row_str; scheme, row_num: integer; first_font: i_word );
+ var n :integer; codd: code_object;
+ begin
+ incr(row_count);
+ row_pt := 1 ;
+ row_font := first_font ;
+ row_string := row_spec;
+ for n := 8*row_num to 8*row_num + 7 do begin
+ row_char (0 ,codd);
+ if codd.breadth = bad_char then do_nothing
+ else codes[ scheme, n ] := codd ;
+ end;
+ end;
+
+@ \.{TANGLE} imposes a limit of 69 on the length of quoted strings. This is a
+considerable nuisance, as we could make the |row_spec| strings look much better
+if they could be longer.
+
+@<Const...@>=
+ row_length = 69 ;
+
+@ @<Types...@>=
+ row_str = packed array [1..row_length] of char ;
+
+@ @<Glob...@>=
+ row_pt: integer; {Points to next char from |row_spec|}
+ row_font: i_word; {printer font being addressed during the |row| procedure}
+ row_string: row_str ;
+ row_count: integer ;
+
+@ @<Set init...@>=
+ row_count:= 0 ;
+
+@ In order to help debugging, error messages will print |row_string| and a
+pointer. The diagnostics of |row| are known to be very poor; I have not
+bothered to fix them because up to now they have been adequate, and they are
+really meant for the installer rather than the end user.
+
+@d row_warn(#) == begin
+ display_ln(row_string ) ;
+ display_ln('^' : row_pt-1 ) ;
+ warn('Row: ', #);
+ return;
+end
+
+@ The overall format of the |row_spec| is a set of 8 character specifiers
+separated by one or more spaces. The procedure |row_char| reads one character
+specifier from the |row_string|, and constructs the specified |code_object|.
+Logically, |row_char| should be a function and return that |code_object| as
+its value. \PASCAL\ does not permit this. So we assemble the result in the
+variable parameter |value|.
+
+@<Medium...@>=
+ procedure row_char(context: integer; var value: code_object);
+ label exit ;
+ const @<|Row_char| constants@>
+ var c :byte; @<Row locals@>
+ begin
+ value.breadth := char_width ; {default}
+ value.IM_font := row_font ; {default font}
+ c := row_get ;
+ if ( context = 0) and ( c <> " " ) and ( c <> "Z") then
+ row_warn('Character specifiers must start with at least one space') ;
+ while ( c = " " ) do c := row_get ;
+ @<Escape sequences in the |row_spec| @>
+ else value.IM_char := c ;
+ exit: end;
+ @.error: Row: Character spec...@>
+
+@ There are several escape sequences that need to go into the |rowstring|.
+Since all the PLAIN.TEX coding schemes (except the math extension one) have
+the upper case Roman characters in their ASCII positions, these characters
+will surely be inserted into |codes| by the |alphabet| procedure. So they are
+available as flag characters. But the brackets are also used as flags, as they
+are so much more perspicuous than anything else. Here is a list of the
+characters currently used as escapes: \begintt
+
+A C D E F K L M N S Q U W Z \endtt
+
+This list should be updated if other escapes are added .
+@.Escape sequences@>@.ASCII@>
+
+@ Some characters, called `bad', have most undesirable effects when used in
+\.{WEB} strings. So the following upper-case letters stand for them. The
+actual characters may not be used, so they generate errors.
+
+@<Esc...@>=
+ if c = "A" then value.IM_char := 64 {at sign}
+ else if c = "S" then value.IM_char := 32 {a space}
+ else if c = "Q" then value.IM_char := 39 { a single quote char}
+ else if c = "W" then value.IM_char := 34 { a double quote char }
+ else if c = "E" then value.IM_char := 127 { a delete char }
+ else if (c = "'") or (c = """") or (c = "@@") or (c = 127)
+ then row_warn( 'Bad character---Rejected' )
+ else if (c = " ")
+ then row_warn('space found out of context')
+ @.Error: Row: Bad character@>@.error: Row: space found...@>
+
+@ Then the `Z' escape is provided to generate a do-nothing code. This would
+be used if a previous call (say, of |alphabet|) had left a row partly
+incorrect. Then you might issue a call of |row| to change that row. Typing `Z'
+at the positions occupied by correct characters would leave them alone.
+
+@<Esc...@>=
+ else if c = "Z" then value.breadth := bad_char
+
+@ Since many letters and brackets are used as escapes, the `L' escape is
+needed to enable them to be used Literally. `LL' generates `L'.
+
+@<Esc...@>=
+ else if c = "L" then value.IM_char := row_get
+
+@ In order to address printer characters in the range 0..32, where ASCII has
+no graphics, here is a Control escape. This simply reads the next character
+from the |row_spec| and reduces it modulo 32. It is best to use the lower case
+alphabet (the range 95..126) as this avoids all the `bad' characters (and
+their escapes). So control-A should be typed `Ca' , not `CA' .
+
+Then the Meta escape addresses meta-characters, i.e. those in the range
+128..255. We cannot just read a character and add 128, as we might want to
+Mutate the ASCII controls, or the `bad' characters. So `M' must read a complete
+|code_object| (respecting the escapes given above) and add 128 to its |IM_char|
+field. So we must say `MS' for `meta-space' = 160 , and `MLS' for `meta-S'
+= 211 .
+@.ASCII@>
+
+@d M_con == 250
+ {Context while reading a Meta character}
+
+@<Esc...@>=
+ else if c = "C" then
+ value.IM_char := row_get mod 32
+ else if context >= M_con then value.IM_char := c
+ {During a Meta, forbid any of the later escapes}
+ else if c = "M" then
+ begin
+ row_char(M_con , value ) ;
+ value.IM_char := value.IM_char + 128 ;
+ end
+
+@ A |narrow| character is one with zero width. To generate one, precede it
+with an `N' . To mark a character as down-loadable, precede it with `D'. A
+character cannot be both narrow and down-loadable.
+
+@d N_con == 230
+ {Context while reading a Narrow or |down_loaded| character}
+
+@<Esc...@>=
+ else if ((c = "N" ) or (c = "D" )) and (context >= N_con) then
+ row_warn('Narrow or Down escape out of context')
+ @.error: Row: Narrow escape...@>
+ else if c = "N" then
+ begin
+ row_char(N_con, value ) ;
+ value.breadth := 0 ;
+ end
+ else if c = "D" then
+ begin
+ row_char(N_con, value ) ;
+ value.breadth := down_loaded ;
+ end
+
+@ Changing printer fonts in the middle of a |row| is done by inserting an `F'
+character, followed by an integer. This is the printer font to be used, from
+now on till the next `F' . Note that the initial font was passed as the 3rd
+parameter to |row|.
+
+@<Esc...@>=
+ else if c = "F" then begin
+ row_font := row_integer ;
+ if context = 0 then row_char(1, value)
+ else row_char(context, value);
+ end
+
+@* Assembling a multi-character in |row|.
+
+Now we come to the difficult part, which is assembling a multiple-character
+command into the |ligatures| array. For this purpose, we use brackets. Curly
+brackets mean that the characters inside are to be overstruck, square brackets
+mean they are to be typed horizontally, and angle brackets mean that they are
+to be typed vertically above each other. Finally the `U' escape (which must
+come immediately after a |<| ) means to raise the (logical) cursor before
+starting the vertical list.
+
+ Warning!! I use the numerical (\.{ASCII}) values of these chars
+@.System dependencies@>@.ASCII@>
+
+@<|Row_char| const...@>=
+ o_bra = "{" ; o_ket = "}" ;
+ h_bra = "[" ; h_ket = "]" ;
+ v_bra = "<" ; v_ket = ">" ;
+ {`o' means overstrike, `h' means horizontal, and `v' vertical}
+
+@ So if we want to generate a Macsyma style summation sign, which looks like
+this: \begintt
+. ====
+. \
+. >
+. /
+. ====
+\endtt
+we have to insert the following mess into the |row_spec| string: \begintt
+ <S[====]\[SL>]/[====]>
+\endtt
+The `S' is needed to get correct vertical alignment. The `L' is needed to
+prevent the following |>| being taken as a |ket|. See the lineprinter change
+file for examples.
+
+@ In order to keep some control over all these escape sequences, I have made a
+special rule of syntax. The escape sequences in |row_char| may only be nested
+in a definite order. That order is: (bad characters or Control or Literal)
+inside Meta inside (Narrow or Down-loadable) inside Font inside over-lists
+inside |h_list|s inside |v_list|s. The parameter |context| keeps track of
+this. The innermost constructions have the highest values of |context|. If
+these rules are broken the user should get an error message saying `Row' and
+some diagnostics.
+
+@<Esc...@>=
+ else if (c = o_bra) or (c = h_bra) or (c = v_bra)
+ then begin
+ if context >= c then
+ row_warn('Illegal nesting of brackets in row_spec');
+ @.error: Row: Illegal nesting @>
+ @<Assemble characters into |lig_buff| until we read the matching |ket|@>;
+ @<Copy |lig_buff| into |ligatures| and return a pointer to it@>;
+ end
+
+@ |hoister| and |ender| are arbitrarily selected impossible classes for a
+character, indicating respectively that a |v_list| has to be raised one
+|char_ht| or that a |ket| has been read.
+
+@d hoister = -32764
+@d ender = -32763
+
+@<Assemble...@>=
+ for i := 1 to max_buf do lig_buff[ i].code := no_char ;
+ buf_len := 0; delta_h := 0; delta_v := 0;
+ repeat
+ row_char(c ,row_cod ) ;
+ @<Do suitable action if |row_cod| is peculiar@>
+ else begin
+ incr(buf_len);
+ lig_buff[buf_len].v_move := delta_v ;
+ lig_buff[buf_len].h_move := delta_h ;
+ incr(buf_len);
+ lig_buff[buf_len].code := row_cod ;
+ if c = v_bra then delta_v := delta_v + char_ht;
+ if c = h_bra then delta_h := delta_h + char_width ;
+ end;
+ until row_cod.breadth = ender;
+
+@ @<Row loc...@>=
+ lig_buff: array[1..max_buf] of lig_thing ;
+ buf_num: 0..max_buf ;
+ {Number of characters (or multi-characters) in current list}
+ buf_len: 0..max_buf ;
+ {Number of used locations in |lig_buff|: should be |2*buf_num|}
+ delta_h, delta_v: i_word;
+ i : integer;
+ row_cod: code_object ;
+
+@ @<Const...@>=
+ max_buf = 201;
+
+@ @<Do suitable action...@>=
+ if row_cod.breadth = hoister then delta_v := delta_v - char_ht
+ else if row_cod.breadth = ender then
+ else if buf_len + 3 > max_buf then
+ abort('overflowed lig_buff array')
+ @.Fatal: overflowed |lig_buff|@>
+
+@ @<Esc...@>=
+ else if (c = "U" ) and (context = v_bra) then value.breadth := hoister
+ else if (c = "U" ) then
+ row_warn('U escape out of context')
+ @.error: Row: U escape...@>
+ else if ((c = o_ket) or (c = h_ket) or (c = v_ket)) and (context = c-2)
+ then value.breadth := ender
+ else if (c = o_ket) or (c = h_ket) or (c = v_ket)
+ then row_warn('mismatching brackets ')
+ @.error: Row: mismatching brackets@>
+
+@ Yet another escape is the |kern| escape. If the printer has reasonable
+positioning resolution, we may want to move the parts of a multi-character
+about to make them fit together better. So a |kern| takes an integer parameter
+and moves the next component of the current list by that many |steps| against
+the current direction. The reason for going back is that one can easily move
+forwards by setting a blank space.
+
+@d h_kern = -32762
+@d v_kern = -32761
+
+@<Esc...@>=
+ else if (c = "K") and (context = h_bra) then value.breadth := h_kern
+ else if (c = "K") and (context = v_bra) then value.breadth := v_kern
+ else if (c = "K")
+ then row_warn('Kern escape out of context' )
+ @.error: Row: Kern escape @>
+
+@ @<Do suitable action...@>=
+ else if row_cod.breadth = h_kern then delta_h := delta_h - row_integer
+ else if row_cod.breadth = v_kern then delta_v := delta_v - row_integer
+
+@ @<Copy...@>=
+ buf_num := 0 ;
+ if buf_len = 0 then value.breadth := bad_char
+ else if top_of_ligs + buf_len + 1 >= max_ligs then
+ abort ('ligature array overflowed, must recompile with larger array')
+ @.Fatal: ligature overflowed@>
+ else begin
+ for i := 1 to buf_len do
+ ligatures[ top_of_ligs + i ] := lig_buff[i] ;
+ top_of_ligs := top_of_ligs + buf_len + 1 ;
+ buf_num := buf_len div 2 ;
+ ligatures[top_of_ligs].num:= buf_num ;
+ ligatures[top_of_ligs].guard := sentry ;
+ value.multi := top_of_ligs ;
+ value.breadth := -20000 ;
+ {Provisional: a nonsense value to make sure the correct value does get
+ inserted later}
+ end;
+
+@ Finally, here are the two functions that actually read the |row_spec|. The
+first is |row_integer|. This reads an integer parameter for the |font| and
+|kern| escapes. The parameter may have a minus sign and is terminated by the
+next non-digit. (If there needs to be another digit immediately after the
+parameter, then prefix it with an `L').
+
+@<Lowest...@>=
+function row_integer: integer;
+ label exit ;
+ var neg: boolean ; n: integer ; b: byte ;
+ begin
+ n := 0 ; neg := false ; b := row_get ;
+ if (b = "-") then begin
+ b := row_get ; neg := true;
+ end
+ else if ( b="+") then b := row_get ;
+ if (b < "0" ) or (b > "9" ) then
+ row_warn( 'no digits found by row_integer')
+ @.error: Row: no digits @>
+ else
+ repeat
+ n := n*10 + b - "0" ;
+ b := row_get ;
+ until (b < "0" ) or (b > "9" ) ;
+ if neg then n := -n ;
+ exit: row_integer := n ; decr( row_pt) ;
+ end;
+
+@ And this function gets the next character from the |row_spec|. I always have
+great difficulty with this sort of program, so will go carefully. Recall:
+|row_pt| points to the next character we are going to read. |b| is that
+character, translated into a byte by |zord|.
+
+@<Forw...@>=
+function row_get : byte; forward;
+
+@ @<Lowest...@>=
+ function row_get;
+ label exit ;
+ var b: byte ;
+ begin
+ b:= 127 ; {any bad character}
+ if row_pt > row_length then
+ row_warn('fallen off end of row_string')
+ @.error: Row: fallen off end@>
+ else begin
+ b:= zord(row_string[row_pt]) ;
+ incr(row_pt);
+ end;
+ exit: row_get := b ;
+ end;
+
+@* Character strings.
+
+In this section I have tried to provide some tolerable string-handling
+facilities in despite of the restrictions of \PASCAL. This does not seem to
+belong in any particular place in the program, but in view of the horrible
+gruesome things that will happen in the next section, it seemed a good idea to
+give some light relief. That is why this section is inserted here.
+
+The |var_string| type is principally used for file names and to send command
+sequences to the printer. Logically, these procedures should all be functions
+and return the results, but stupid \PASCAL\ does not allow this. It would of
+course be much cleaner to use the VMS |varying| type, but that would make the
+program non-portable.
+
+@<Const...@>= string_length = 100 ; {a guess, of course}
+
+@ @<Types...@>=
+ s_ptr = 1..string_length ;
+ s_dat = packed array[ s_ptr] of char ;
+ var_string= packed record
+ len: byte;
+ data: s_dat ;
+ end ;
+
+@ |@!blank| is used for initialising strings. It should not be altered
+anywhere but here.
+
+@<Set init...@>=
+ blank.len := 0 ;
+ for in_i := 1 to string_length do
+ blank.data[in_i] := ' ' ;
+
+@ @<Glob...@>=
+blank: var_string ;
+
+@ Here are two small procedures for printing strings.
+
+@<Lowest...@>=
+ procedure string_show(ss: var_string);
+ var s_n: byte ;
+ begin
+ for s_n := 1 to ss.len do display(ss.data[s_n]) ;
+ end;
+
+@#
+ procedure string_print(ss: var_string);
+ var s_n: byte ;
+ begin
+ for s_n := 1 to ss.len do print(ss.data[s_n]) ;
+ end;
+
+@ @<Forw...@>=
+ procedure upcase(var s: var_string) ; forward;
+ {convert to upper case}
+
+@ @<Lowest...@>=
+ procedure upcase ;
+ var i: s_ptr; k: byte ;
+ begin
+ for i := 1 to s.len do begin
+ k:= zord(s.data[i]) ;
+ if (k >= "a" ) and (k <= "z" ) then
+ s.data[i] := zchr(k + "A" - "a" ) ;
+ end;
+ end;
+
+@ We also use |var_strings| for command strings to be sent to the printer.
+These nearly always use unprintable ASCII characters, typically ESCAPE. So we
+need a special procedure to initialise them. It copies the |in_data| into the
+|result|, but changes each |flag| into the character |zchr(escape)|. We
+determine the length by assuming that the |in_data| string is padded with some
+character; then we run backwards along it until we hit the latest non-pad
+character. Since \.{TANGLE} imposes a limit of 69 on the length of quoted
+strings, we use the |row_str| type defined elsewhere.
+ @.ASCII@>
+
+@<Lowest...@>=
+ procedure set_string
+ (var result: var_string; in_data: row_str;
+ flag: char; escape: byte );
+ var i: byte ; last:char ;
+ begin
+ result := blank ;
+ i := row_length ;
+ last := in_data[i] ;
+ while (in_data[i] = last) and (i > 1 ) do decr(i) ;
+ if (i=1) and (in_data[1] = last ) then i := 0 ;
+ {YEUCH! but if I write this in a natural way, it crashes when |i=0|}
+ result.len := i ;
+ for i := 1 to result.len do
+ if in_data[i] = flag then result.data[i] := zchr(escape)
+ else result.data[i] := in_data[i] ;
+ end;
+
+@#
+ procedure add_char(var s: var_string; c: char) ;
+ begin
+ if s.len >= string_length then warn('string too long')
+ else begin
+ incr(s.len) ;
+ s.data[s.len] := c ;
+ end;
+ end;
+ @.error: string too long@>
+
+@#
+ procedure append(var head: var_string; tail: var_string) ;
+ var k: integer;
+ begin
+ if head.len + tail.len > string_length
+ then warn('string too long')
+ else begin
+ for k := 1 to tail.len do
+ head.data[ k + head.len] := tail.data[ k] ;
+ head.len := head.len + tail.len ;
+ end;
+ end;
+ @.error: string too long@>
+
+@ The next procedures generate substrings. If the character |c| is present in
+|s|, then |chop_top| deletes the first |c| from |s|, and all successive
+characters. |chop_tail| deletes the last |c| and all previous characters.
+|chop_length| chops the string to the stated length.
+
+@<Lowest...@>=
+ procedure chop_top(var s: var_string; c:char );
+ var t: var_string; i,n: byte ;
+ begin
+ n := 1 ;
+ while (n <= s.len ) and (s.data[n] <> c) do incr(n);
+ if n <= s.len then begin
+ t := blank ;
+ for i := 1 to n-1 do t.data[i] := s.data[i ];
+ t.len := n-1 ;
+ s := t ;
+ end;
+ end;
+
+@#
+ procedure chop_tail(var s: var_string; c:char );
+ var t: var_string; i,n: byte ;
+ begin
+ n := s.len ;
+ while (n >= 1 ) and (s.data[n] <> c) do decr(n);
+ if n >= 1 then begin
+ t := blank ;
+ for i := 1 to s.len - n do t.data[i] := s.data[n+i];
+ t.len := s.len - n;
+ s := t ;
+ end;
+ end;
+
+@#
+ procedure chop_length(var s: var_string; k: integer);
+ var n: integer;
+ begin
+ if (k < 0 ) or (k > s.len) then
+ warn ('Impossible length supplied to chop_length' )
+ else begin
+ for n:= k+1 to s.len do s.data[n] := ' ' ;
+ s.len := k ;
+ end;
+ end;
+ @.error: impossible length@>
+
+@ Printer commands usually have the format (prefix)(parameter)(suffix). These
+procedures print the parameter. |s| is one character, and may have the values
+`B'(yte), `D'(ecimal), `H'(exadecimal), `O'(ctal), or `W' (a 16-bit signed
+word, in twos-complement notation).
+
+@<Lowest...@>=
+ procedure string_base(var result:var_string; n:integer; s:byte) ;
+ { |n| to base |s| . Note that the integer is appended to |result|}
+ var nh : integer ;
+ begin
+ nh := n ;
+ if nh < 0 then begin add_char(result, '-'); nh := - nh ; end ;
+ if nh >= s then begin
+ string_base(result, nh div s, s) ;
+ nh := nh mod s ;
+ end ;
+ if nh >= 10 then add_char(result, zchr(nh - 10 + "A" ))
+ else add_char( result, zchr(nh + "0" )) ;
+ end;
+
+@#
+ procedure string_integer (var ss: var_string; n:integer; c:char);
+ var nn: integer ;
+ begin if c = 'O' then string_base(ss, n, 8)
+ else if c = 'H' then string_base(ss, n, 16)
+ else if c = 'D' then string_base(ss, n, 10 )
+ else if (c = 'B') and (n >= 0) and (n <= 255)
+ then add_char(ss, zchr(n))
+ else if c = 'B' then warn('out-of-range byte')
+ else if c='W' then begin
+ if (n>= 0) and (n <= 32767) then begin
+ add_char(ss, zchr(n div 256));
+ add_char(ss, zchr(n mod 256));
+ end
+ else if (n<0 ) and (n> -32768) then begin
+ nn := n + 65536 ;
+ add_char(ss, zchr(nn div 256));
+ add_char(ss, zchr(nn mod 256));
+ end
+ else warn('out-of-range word') ;
+ end
+ @<Hook for weird parameter types@>
+ else warn('string_integer called with illegal type') ;
+ end;
+ @.error: out-of-range...@> @.error: string_integer called...@>
+
+@#
+ procedure print_integer (n:integer; c:char);
+ var ss: var_string;
+ begin ss := blank ;
+ string_integer(ss, n, c) ;
+ string_print(ss) ;
+ end;
+
+@ @<Hook for weird ...@>=
+
+@* Translating the device-independent file, 5: Movements.
+
+This section considers the problem of deciding where each character has to be
+printed on the printer's page. This is by far and away the most difficult (and
+unsatisfactory) part of \.{Crudetype}. The current version is not a properly
+designed algorithm; it is merely a bodge, obtained by a lot of trial and
+error. It does seem to give tolerable results on \.{WEB} files, lineprinter,
+and VMS. The main variables are: |@!D_h| is `\TeX's cursor'. It gives the
+`exact' horizontal position (in \.{DVI} units) generated by \.{DVI} commands.
+This is always updated exactly as in \.{DVItype}. |@!IM_h| is the `page
+image's cursor'. It marks the position (in |h_steps|) where the next character
+will be set.
+
+The procedure |round_IM_h| is called immediately before we set a character or
+a rule. We have to take account of all the movements that occurred since the
+last previous character was set.
+
+@<Forw...@>= procedure round_IM_h( code: byte); forward ;
+
+@ @<Lowest...@>=
+ procedure round_IM_h ;
+ var
+ s_top, diff, n, m,
+ delta, new_IM_h, rounded_h : integer ;
+ begin
+ @<Find the new position |new_IM_h|@>
+ IM_h := new_IM_h ;
+ end;
+
+@ The obvious method is to multiply |D_h| by a factor |h_conv| and round to
+nearest integer. This gives extremely bad results, because the characters in
+\TeX\ fonts vary enormously in width, while many crude printers have
+|fixed_width| characters. If |h_conv| is too large, then you get spaces in the
+middle of words. If |h_conv| is too small, then successive characters in a
+word get printed on top of each other. With an intermediate value of |h_conv|,
+you get both effects at once; in other words, the characters in \TeX\ fonts
+vary so much in width that the `too large' and `too small' values of |h_conv|
+overlap. In this situation, a great deal of jiggery-pokery is needed to get a
+tolerable result (sometimes! I have not been able to make this code work in
+general.)
+ For a start, here is the algorithm used in \.{DVItype}. |D_h_right| and
+|IM_h_right| give the latest value of |D_h| and |IM_h| after the latest
+previous character or rule was set. If the horizontal motion is small, like a
+kern, |IM_h| changes by rounding the kern; but when the motion is large,
+|IM_h| changes by rounding the true position |D_h| so that accumulated
+rounding errors disappear. Also, we insist that the total amount of drift is
+bounded.
+
+@d h_step_round(#) == round(h_conv * # )
+@d max_drift == 2
+
+@<Find the new position |new_IM_h|@>=
+ rounded_h := h_step_round(D_h) + l_margin ;
+ delta := D_h - D_h_right ;
+ if (delta > thin_space) or (delta <= -4*thin_space) then
+ new_IM_h := rounded_h
+ else new_IM_h := IM_h_right + h_step_round(delta);
+ if not fixed_width then begin
+ if new_IM_h > rounded_h + max_drift
+ then new_IM_h := rounded_h + max_drift
+ else if new_IM_h < rounded_h - max_drift
+ then new_IM_h := rounded_h - max_drift ;
+ end else
+
+@ Calculating |IM_h| on a |fixed_width| printer is very hairy. If we are not
+careful, then the spaces between words will sometimes get rounded to 0. Since
+we round `large' movements by rounding |D_h|, the space may even get rounded to
+a negative value, if there was previously a lot of drift. So we must re-round
+|new_IM_h|. The next idea is that whenever \TeX\ moves right by an amount that
+seems large enough to be a space between words, we force |IM_h| to increase.
+
+@<Find the new position |new_IM_h|@>=
+ if (delta > thin_space) and (new_IM_h < IM_h_right + gap_width)
+ @<Except in some special cases@>
+ then new_IM_h := IM_h_right + gap_width
+ else if (delta > thin_space) then do_nothing
+ else if (delta > - 2*thin_space)
+ then new_IM_h := IM_h_right
+ else
+
+@ Here are two little fudges which improve the result. First, when \TeX\ puts
+out a thin space, it sometimes is a bit too small to be recognised as such. So
+we reduce the |font_space| when a font is defined.
+
+@<Read the font parameters...@>=
+ font_space[nf] := round(font_space[nf] * 0.99 ) ;
+
+@ The next fudge is needed to handle tables of contents. \TeX\ prints these by
+putting out long streams of dots with small spaces in between. If these spaces
+all get expanded to a whole character width, the right hand columns get thrown
+right off the paper. So dont expand if the next character is a stop or comma.
+
+@<Except in some special cases@>=
+ and not ( ( ( code = ".") or ( code = ",") ) and
+ ( ( cur_scheme > 0) and ( cur_scheme <= max_plain )))
+
+@ When these alternatives fail, we have lost contact between |D_h| and
+|D_h_right|. This happens when \TeX\ makes a large backspace; in fact \TeX\
+seems nearly always to do large backspaces by |pop| rather than an explicit
+move left. \TeX\ often expresses boxes by a sequence like this:
+
+\centerline{\tt{
+PUS\markarrow{H} Move right ------------>
+\markarrow{[}set characters] \markarrow{P}OP }}
+
+followed by zero or more |push|es, then by a move either to one of the
+positions marked by the arrows, or close by. I try to deal with this by
+dropping markers at each of the arrowed positions. The markers are labelled
+|D_h_right|, etc, and each marker has a corresponding value of |IM_h|
+attached.
+
+@<Glob...@>=
+ D_h_left, IM_h_left, D_h_mid, IM_h_mid, D_h_right, IM_h_right, {the markers}
+ IM_h, IM_v, D_dis, IM_dis: integer;
+ IM_h_stack, IM_v_stack:
+ array [0..max_stack+2] of integer; {pushed down values }
+
+@ Suppose that we are about to set a character, and |D_h-D_h_right| is large
+and negative. Then we compare the current value of |D_h| with all the markers.
+Let |m| be the closest of these, and |mm| the corresponding rounded value.
+Then we re-round |new_IM_h| to force it to lie on the `correct' side of |mm|.
+This seems to work fairly often, but it does sometimes slip. First put the
+markers on top of the stack...
+
+@<Find the new position |new_IM_h|@>=
+ begin s_top := stack_ht ;
+ D_h_stack[s_top] := D_h_left;
+ IM_h_stack[s_top] := IM_h_left;
+ incr(s_top) ;
+ D_h_stack[s_top] := D_h_mid;
+ IM_h_stack[s_top] := IM_h_mid;
+ incr(s_top) ;
+ D_h_stack[s_top] := D_h_right;
+ IM_h_stack[s_top] := IM_h_right;
+
+@ ...then look for the stacked value closest to |D_h|...
+
+@<Find the new position |new_IM_h|@>=
+ m := s_top ;
+ for n := s_top downto 1 do begin
+ diff := D_h - D_h_stack[n] ;
+ if abs(diff) <= abs(delta) then
+ begin m := n ; delta := diff; end ;
+ end;
+
+@ ...then adjust |new_IM_h| by reference to this point on the stack.
+
+@<Find the new position |new_IM_h|@>=
+ if (delta > thin_space ) and ( new_IM_h < IM_h_stack[m] + gap_width) then
+ new_IM_h := IM_h_stack[m] + gap_width
+ else if (delta < -thin_space )
+ and ( new_IM_h > IM_h_stack[m] - gap_width)
+ then new_IM_h := IM_h_stack[m] - gap_width
+ else if abs(delta) <= thin_space then new_IM_h := IM_h_stack[m];
+end;
+
+@ We must assign values to these markers. When we start a page, all the
+markers that were left over from the previous page are irrelevant. So we reset
+them. This is a good place to consider margins. The standard arrangement given
+in the \TeX book (Chapter 23) is that \.{DVI} point $(0,0)$ is about an inch
+in from the top and left edges of the paper. But a negative {\tt \BS hoffset}
+allows \.{DVI} to address points with negative coordinates, which should still
+be on the paper. It seems that the least messy way to implement this is by
+adding |l_margin| to |IM_h|, whenever this is set to an absolute value.
+@.TeXbook@>@.Margins@>
+
+@<Set up an empty page image@>=
+ IM_h := @!l_margin ;
+ IM_v := @!top_margin ;
+ D_h_left := 0 ; IM_h_left := l_margin ;
+ D_h_mid := 0 ; IM_h_mid := l_margin ;
+ D_h_right := 0 ; IM_h_right := l_margin ;
+
+@ So now we consider the three arrows in turn. The left hand arrow will be
+marked by |@!D_h_left|. It records the latest horizontal position to be
+|push|ed. There might have been a |pop| since then, so it is not necessarily
+the value at the top of the stack. If we just record |IM_h| whenever we
+|push|, that would give a wrong value whenever there was a sequence
+|push..move_right..push|. So we must rectify the pushed value of |IM_h|.
+
+@ @<Some adjustments...@>=
+ IM_h_stack[stack_ht]:=IM_h;
+ IM_v_stack[stack_ht]:=IM_v;
+ if just_pushed and (stack_ht > 0) then begin
+ x := h_conv*(D_h_stack[stack_ht] - D_h_stack[stack_ht - 1] );
+ if abs(x) > 1.5 {a guess!} then
+ IM_h_stack[stack_ht] := IM_h_stack[stack_ht] + round(x) ;
+ end;
+ D_h_left := D_h ;
+ IM_h_left := IM_h_stack[stack_ht] ;
+
+@ The centre arrow will be marked by |@!D_h_mid|. This is defined as the value
+of |D_h| just before setting the first character after the latest |push|.
+
+@<Find the new position |new_IM_h|@>=
+ if just_pushed then begin
+ D_h_mid := D_h ;
+ IM_h_mid := new_IM_h;
+ just_pushed := false;
+ end;
+
+@ The right hand arrow is marked by |@!D_h_right|. At any time, this is
+defined as the right hand edge of the latest previous character (or rule) that
+has just been set. This equals |D_h + D_dis|, where |D_dis| is the \TeX\ width
+of the character. Usually there will follow a |move_right| that updates |D_h|,
+but |D_h_right| must be updated even if there is no |move_right|. Now
+|@!IM_h_right| must be aligned with the right hand edge of the printed
+representation of the character. The idea is that this will usually be the
+exact place where the next character has to be set. We hope that all the
+characters in each word will be correctly placed next to one another and the
+accumulated drift will appear in spaces between the words. So whenever a
+character is set, we must assign values to |D_dis| and |IM_dis|. The character
+is described by |cod|, and its printed width is written into its |breadth|
+field; but if it is a multiple character, then the |breadth| is the negative
+of the width.
+
+@<Do messy things...@>=
+ D_dis := D_width[D_font, c_num] ;
+ if cod.breadth = bad_char then IM_dis := 0
+ else IM_dis := abs(cod.breadth) ;
+ @<Set |rail_base|@>
+ D_h_right := D_h + D_dis ;
+ IM_h_right := IM_h + IM_dis ;
+
+@ So the procedure |row| must give the |breadth| field the right value when
+assembling a |multi| character. Recall that that character can be either an
+|o_list| or an |h_list| or a |v_list|, and |c| tells us which it is. An
+|o_list| is assumed to have a width of one |char_width| and the width of a
+|v_list| is the width of its widest component. The width of a |h_list| gets
+accumulated in |delta_h| as the list is assembled.
+
+@<Copy |lig_buff|...@>=
+ if c = o_bra then print_width := char_width
+ else if c = h_bra then print_width := delta_h
+ else begin
+ print_width := char_width ;
+ for i := 1 to buf_num do
+ with lig_buff[2*i].code do
+ if (print_width < -breadth ) and (breadth > -30000 )
+ then print_width := -breadth ;
+ end;
+ value.breadth := - print_width ;
+
+@ @<Row locals...@>=
+ print_width: integer ;
+
+@ We must do the same thing when setting a rule.
+
+@<|Post| set...@>=
+ D_dis := D_rul_width ;
+ IM_dis := hn * post_width ;
+ D_h_right := D_h + D_dis ;
+ IM_h_right := IM_h + IM_dis ;
+
+@ @<|Rail| set...@>=
+ D_dis := D_rul_width ;
+ IM_dis := hn * rail_width ;
+ D_h_right := D_h + D_dis ;
+ IM_h_right := IM_h + IM_dis ;
+
+@ \.{DVItype} handles vertical motion in the same sort of way as horizontal.
+
+@d v_step_round(#) == round(v_conv * # )
+
+@<Medium...@>=
+ procedure move_down(ddd: integer);
+ var new_IM_v , delta : integer;
+ begin
+ D_v:=D_v+ddd;
+ delta := v_step_round(ddd) ;
+ @<Find a vertical position |new_IM_v|@>
+ end;
+
+@ @<Find a vert...@>=
+ if delta >= big_drop then begin
+ new_IM_v := v_step_round(D_v) + top_margin ;
+ if new_IM_v < IM_v + big_drop then
+ IM_v := IM_v + big_drop
+ else IM_v := new_IM_v ;
+ rail_base := IM_v * rail_types ;
+ end
+ else if delta <= -big_drop then begin
+ new_IM_v := v_step_round(D_v) + top_margin ;
+ if new_IM_v > IM_v - big_drop then
+ IM_v := IM_v - big_drop
+ else IM_v := new_IM_v ;
+ rail_base := IM_v * rail_types ;
+ end else
+
+@ The above calculation fails for small motions. Because \TeX\ expects
+subscripts to be about half the size of the main line, it drops them by only a
+small amount; with a crude printer, this small amount gets rounded to zero. If
+the move is smaller than |@!tiny_drop| \.{DVI} units, we ignore it. If not,
+then we force the new value of |IM_v| to be different from the old.
+
+@<Find a vert...@>=
+ begin
+ IM_v := IM_v + delta ;
+ rail_base := rail_base + v_step_round(ddd * rail_types) ;
+ if (ddd > tiny_drop) and ( delta = 0) then IM_v := IM_v + 1
+ else if (ddd < -tiny_drop) and ( delta = 0) then IM_v := IM_v - 1
+ else rail_base := IM_v * rail_types ;
+ end;
+
+@ The next bit is put in to help catch bugs. Sometimes the \.{DVI} file tries
+to address an absurd position; for example, I contrived to make \TeX\ generate
+a {\tt \BS hbox} that was 9000 points wide. If we do nothing about this,
+\.{Crudetype} will probably crash with an arithmetic error, which is
+unacceptable. So any character falling outside the limits |h_min..h_max| and
+|v_min..v_max| will generate an error report.
+
+@<Check the position@>=
+ if (Set_h < h_min) or ( Set_h > h_max )
+ then begin
+ warn('out of bounds position') ;
+ Set_h := h_min ;
+ {Chuck the character somewhere, hopefully out of the way}
+ end;
+ if (Set_v < v_min) or ( Set_v > v_max )
+ then begin
+ warn('out of bounds position') ;
+ Set_v := v_min ;
+ end;
+ @.error: out of bounds@>
+
+@ @<Set init...@>=
+ h_max := h_resolution * 100 ;
+ v_max := v_resolution * 100 ;
+ h_min := -10 * h_resolution ;
+ v_min := -10 * v_resolution ;
+
+@ Note that since the position fields of a |page_record| are subranges,
+|h_max| etc. must be of the same type.
+
+@<Glob...@>= h_max, v_max , h_min, v_min : i_word ;
+
+@* Sorting the page.
+
+Once we have assembled the complete page image, we must sort it. The method
+used here is a merge sort based on the country dance called Grand March.
+
+@<Sort the page@>=
+ @<The dancers form a long line up the middle of the hall and march
+ up towards the Presence@>
+ repeat
+ @<At the top they split, and alternate groups go to the left and right and
+ march down the sides@>
+ @<At the bottom of the hall, each group coming from the right hand side
+ merges with a group from the left side, and they go up again @>
+ until sorted;
+
+@ Since the data being sorted is of unpredictable size and sequentially
+processed, it logically ought to be a |file|. But this turned out to make the
+program spectacularly slow. So I use linked lists instead--- a sacrifice of
+logic to economy. But I continue to use file-like language.
+
+@d send_one_set_to( #)==
+ copy_from( mid ) ( # )
+
+@<At the top...@>=
+ L_reset( mid) ;
+ L_rewrite( left) ;
+ L_rewrite( right) ;
+ repeat
+ send_one_set_to( left) ;
+ if not L_eof( mid) then
+ send_one_set_to( right) ;
+ until L_eof( mid);
+
+@ Eventually everybody comes together in one enormous set and the dance is
+finished. The easiest way to detect this is to let it go round one more time.
+Then the left side of the hall will be full and the right hand side empty.
+
+ @<At the bottom...@>=
+ L_rewrite( mid) ;
+ L_reset( left) ;
+ L_reset( right) ;
+ sorted := L_eof( right) ;
+ if sorted then
+ page_ptr := son( next( left))
+ else repeat
+ if L_eof( right) then copy_from( left) ( mid)
+ else if L_eof( left) then copy_from( right) ( mid)
+ else @<Merge one group from each side@>
+ until L_eof( left) and L_eof( right) ;
+
+@ The natural way to assemble the page image is to throw everything into one
+huge list, then start sorting. But the code for merging two simple lists was
+horribly complicated. (The code given here merely merges two runs.) So the
+page image is a list of lists (another sacrifice of logic to economy). Each
+top-level entry has a |son|, which points to a sub-list. This is a sorted
+subset (a ``run'') of the data. One advantage of the list-of-lists structure
+is that we can take advantage of the fact that \TeX\ output is very ``runny''.
+I found that this made \.{Crudetype} run at least 3 times faster than before.
+
+@d Add_run == new_tail( mid_ptr) ; son( mid_ptr) := run_ptr ;
+
+@<Merge one group...@>=
+ begin
+ L_rewrite( run) ;
+ L_run_ptr := son( left_ptr) ;
+ R_run_ptr := son( right_ptr) ;
+ repeat
+ if @<The person on the left is more eligible@>
+ then copy_from( L_run) ( run)
+ else copy_from( R_run) ( run) ;
+ until L_eof( R_run) and L_eof( L_run) ;
+ step_wipe( left_ptr) ;
+ step_wipe( right_ptr) ;
+ L_reset( run) ;
+ Add_run ;
+ end;
+
+@ So while the page image is being assembled, it must be divided into runs.
+
+@<Add the record...@>=
+ begin
+ if out_of_sequence then begin {create a new run}
+ L_reset( run) ;
+ Add_run ;
+ L_rewrite( run) ;
+ end;
+ new_tail( run_ptr ) ;
+ with image( run_ptr) do begin {write the data into it}
+ hpos := Set_h ; Old_h := Set_h ;
+ vpos := Set_v ; Old_v := Set_v ;
+ data := cod ;
+ end;
+ incr(page_size) ;
+ if page_size >= page_max then abort(
+ 'overflowed page: either a bug, or recompile with larger page_max' ) ;
+ end
+ @.Fatal: overflowed page@>
+
+@ Once the lists are all assembled, we must |reset| them before sorting.
+
+@<The dancers...@>=
+ sorted := false;
+ L_reset( run) ;
+ Add_run ;
+
+@ Now we must specify the desired order!! That is: increasing |vpos| and
+|hpos|, |vpos| is more significant.
+
+@d out_of_sequence ==
+ ( ( Old_v > Set_v) or ( ( Old_v = Set_v) and ( Old_h > Set_h)))
+
+@<The person on the left is more eligible@>=
+ ( ( image( L_run_ptr).vpos < image( R_run_ptr).vpos) or
+ ( ( image( L_run_ptr).vpos = image( R_run_ptr).vpos)
+ and ( image( L_run_ptr).hpos <= image( R_run_ptr).hpos)))
+
+@ And here we get it all started. Since |garbage| wipes out everything in the
+|pool| array above |zzz|, the following code effectively makes |mid..run|
+permanent.
+
+@<Set init...@>=
+ first_cell ;
+ make_new( mid );
+ make_new( left );
+ make_new( right );
+ make_new( run );
+ make_new( zzz );
+ image(zzz).vpos := max_half;
+ next(zzz) := zzz ;
+ mid_ptr := zzz ;
+ run_ptr := zzz ;
+
+@ @<Set up an empty page image@>=
+ garbage ;
+ L_rewrite( mid) ;
+ L_rewrite( run) ;
+ page_size := 0 ;
+ Old_v := -max_half ;
+
+@ @<Glob...@>=
+ zzz, cell, tempp, page_ptr,
+ mid, mid_ptr, run, run_ptr,
+ left, left_ptr, L_run_ptr ,
+ right, right_ptr, R_run_ptr : link;
+ page_size: page_i ;
+ Old_v, Old_h : i_word ;
+ sorted: boolean ;
+ declare_pool
+
+@ Now we must define lots of machinery for handling lists. We could represent
+a list by either a big array or dynamic storage. Neither is ideal, because an
+array is bound to be either too big or too small; and some \.{PASCAL}s
+apparently do not implement pointers. So I have expressed everything in terms
+of certain macros, defined in the system dependent part of the program. In
+theory, you can switch \.{Crudetype} from array to heap merely by redefining
+these as follows:
+\begintt
+ define image(#) == #^
+ define create == new(cell)
+ define first_cell ==
+ define link_type == ^page_record
+ define wipe_out(#) == dispose(#) ; { release data piecemeal}
+ define garbage ==
+ define declare_pool ==
+\endtt
+
+Both array and heap seem to work in VMS. I prefer to use an array because in
+VMS, there seems to be no shortage of store, and an array is easier to debug.
+Assuming these lowest-level macros, here is some machinery for handling lists.
+We must deallocate cells after use. When using arrays, the |garbage| command
+does it all in one go. Pointers must be |dispose|d one at a time, and the
+obvious time is just after the data was used.
+
+@d next(#) == image(#).prox
+@d advance(#) == # := next(#)
+@d make_new( #) == create; # := cell ;
+@d new_tail( #) ==
+ create; next( #) := cell; # := cell ;
+@d step_wipe( #) ==
+ tempp := # ; advance( #) ; wipe_out( tempp)
+
+@ Suppose |L| is a list; then the actual variable |L| points to a permanently-
+allocated cell which in turn points to the head of the list. |L_ptr| points to
+the active end. After the list has been assembled, we first mark the tail, by
+attaching a special element called |zzz|. Then we move the |L_ptr| round to
+the head. |copy_from| must be used in the combination
+{\tt copy\_from(A) ( B)}. It copies one element from the head of |A| to the
+tail of |B|.
+
+@d L_rewrite( #) ==
+ #@&ptr := # ; next( #) := zzz
+@d L_reset( #) ==
+ next ( # @& ptr) := zzz ; #@&ptr := next( #)
+@d L_eof( #) ==
+ ( # @& ptr = zzz)
+@d copy_end( #) ==
+ next( #@&ptr) := tempp ; advance( #@&ptr) ; end
+@d copy_from( #)==
+begin
+ tempp := #@&ptr ;
+ advance( #@&ptr ) ;
+ copy_end
+
+@ Each top-level entry has the |false| type below; the |prox| field points to
+the next top-level entry and the |down| field to a sub-list.
+
+@d son(#) == image(#).down
+
+@<Types...@>=
+ page_i = 0..page_max ;
+ link = link_type ;
+ page_record = packed record
+ prox: link ;
+ case boolean of
+ true: ( hpos : i_word;
+ vpos: i_word;
+ data: code_object ) ;
+ false: ( down : link) ;
+ end;
+
+@* Processing a page of output.
+
+The output of \.{Crudetype} is done by the procedure |Send_page|, which takes
+the page and translates it for the printer. We shall process it a `line' at a
+time, meaning all |page_records| with the same |vpos|. Initially |PR_font|
+gets an impossible value so as to force an explicit |set_PR_font|.
+
+@<Top level...@>=
+ procedure Send_page;
+ var line: link ;
+ begin
+ @<Pause reset@>;
+ PR_font := sentry;
+ PR_h := 0;
+ PR_v := 0;
+ repeat
+ line := read_line ;
+ do_line(line);
+ until L_eof( page) ;
+ end;
+
+@ The function |read_line| runs along the page image until the vertical
+position changes. It returns a pointer to a sublist which is the next line on
+the page. As side effects, it moves the printer into position for this line,
+advances |page_ptr| to the first record of the next line, and updates
+|PR_v| and |PR_v_next|.
+
+@<Medium...@>=
+ function read_line : link ;
+ var head, tail: link ; size: integer;
+ begin
+ head := page_ptr ;
+ size := 0 ;
+ PR_v_next := image(page_ptr).vpos ;
+ @<Move printer vertically to |PR_v_next|, update |PR_v| @>;
+ repeat
+ tail := page_ptr ;
+ advance(page_ptr) ;
+ PR_v_next := image(page_ptr).vpos ;
+ incr(size) ;
+ if size = max_line_size then
+ warn('excessively long line, probably this is a bug') ;
+ @.error: excessively long line@>
+ until ( ( L_eof( page) )
+ or (PR_v_next <> PR_v ) ) ;
+ next(tail) := zzz ;
+ read_line := head ;
+ end;
+
+@ These variables all denote the printer fonts, etc.
+
+@<Glob...@>=
+ PR_v, PR_v_next,
+ PR_h, PR_h_next,
+ PR_font : i_word ;
+
+@ This procedure tries to print a line. The main difficulties are: we dont
+want to |Backfeed| unless absolutely necessary; and we may have to deal with
+overstruck characters. One possible way is to shunt them aside somewhere, then
+print the |overflow| after the main line has been printed.
+
+@<Medium...@>=
+ procedure do_line (line_ptr: link);
+ var overflow : link;
+ begin
+ overflow := zzz ;
+ while line_ptr<>zzz do
+ @<Process the character that |line_ptr| points to, and |advance| to
+ the next@>;
+ @<End the line, trying very hard not to over-feed the paper and print
+ the |overflow|@>;
+ @<Check pause@>;
+ end;
+
+@ We are actually getting almost in sight of the printer!!! Before we can
+actually print a character, we must first check if it has to go to the
+|overflow|...
+
+@<Process the char...@>=
+ with image(line_ptr) do begin
+ PR_h_next := hpos ;
+ if not b_space_absolute and not b_space_by_string and
+ (PR_h_next < PR_h) then
+ begin
+ {AKT: ignore overflow stuff
+ next(overflow) := line_ptr ;
+ advance(overflow) ;
+ AKT}
+ advance(line_ptr) ;
+ end
+ else begin
+ @<Set horizontal position for the next character@>;
+ if data.IM_font <> PR_font then set_PR_font(data.IM_font);
+ print(zchr(data.IM_char )) ;
+ PR_h := PR_h + data.breadth ;
+ step_wipe(line_ptr ) ;
+ end ;
+ end;
+
+@* Downloading. Not started yet.
+
+@<Download a whole font@>= do_nothing
+
+@ @<Enter a download...@>=
+
+@* Carriage control.
+
+Once the superior software has decided where the printer has to move to next,
+this section has the job of translating the desired position into elementary
+printer commands. Clearly this mapping depends very much on the range of
+functions that the printer can perform. So this section is controlled by
+several boolean constants; each asserts that the printer can do the
+corresponding action. Here is a list of the most important ones:\item
+|@!c_r_feed_dist| is the distance in |v_steps| by which a carriage-return
+feeds the paper.\item
+|@!w_l_feed_dist| ditto, |write_ln|. Similarly for the other |dist|
+values.\item
+|@!feed_absolute| says the printer has an absolute position command that takes
+a parameter |IM_y|, say, and moves to position |IM_y v_steps| down the
+page.\item
+|@!b_feed_absolute| ditto, backfeeding.\item
+|@!b_feed_by_string| says the printer has a |Backfeed| character that moves it
+back by a fixed number |b_feed_dist| of |v_steps|. These booleans should not
+be set true unless the printer can backfeed reliably.\item
+|@!space_absolute| etc., Ditto, horizontal moves.\item
+|@!abs_is_incr| says that in the absolute position commands, the parameter is
+actually an incremental move.\item
+|@!w_l_does_c_r| says that |write_ln| forces a carriage return.
+
+As mentioned above, it is essential to avoid premature line feeds as much as
+possible. Also, many operating systems will choke if the output record gets
+too long, so we must do a |print_ln| at intervals. This program tries to
+accommodate various types of carriage control, some of which are not in use at
+the author's site. This means that several pieces of code have not been
+tested. Installers may find that the procedures defined here will need to be
+carefully studied in conjunction with the I/O section of their \PASCAL\
+manual.
+
+@<Const...@>= @<Carriage control constants@>
+
+@ Now consider what happens at the end of each line. We will want to do a
+subset of the following things: carriage-return, print the |overflow|, line
+feed, split output records. We must keep a clear separation between these
+tasks, and we want to do them in the stated order (but we cannot if
+|fortran|). This order puts most of the carriage controls to the ends of the
+output records, and (on our machine) makes it easier to examine the output
+file with an editor. So first: do we want to do carriage-return? If so, then
+the natural way is to print a carriage-return, but not if it will over-feed
+the paper.
+
+@<End the line...@>=
+ if not w_l_does_c_r {Return is compulsory}
+ or (c_r_feed_dist = 0) {Return is harmless }
+ or b_space_absolute
+ or ((not want_split or (overflow <> zzz ) ){We can choose C-R or W-L}
+ and (c_r_feed_dist < w_l_feed_dist))
+ then begin
+ if b_space_absolute and ((c_r_feed_dist > 0) or (l_margin > 0)) then
+ set_h_abs(0)
+ else begin
+ if fortran then print_ln ;
+ if not list then {AKT: only send CR if not list}
+ print(c_r_char);
+ PR_h := 0;
+ PR_v := PR_v + c_r_feed_dist ;
+ end;
+
+@ Now for the |overflow|. We will split records if that is harmless.
+
+@<End the line...@>=
+ if overflow <> zzz then begin
+ if not fortran and (w_l_feed_dist = 0) then print_ln ;
+ {AKT: must do this if list???}
+ next(overflow) := zzz ;
+ overflow := next(zzz) ;
+ do_line(overflow);
+ end;
+ @<Reset printer at end of line, if necessary @>
+end;
+
+@ @<Reset printer...@>= {hook}
+
+@ Now we decide whether to do any |line_feed|s. But first, we may have to
+attempt to |Backfeed|. Sometimes the program will fail; it should not do so
+unless the \.{DVI} file calls for overstruck characters and the printer
+genuinely cannot do them. If |b_feed_scream|, then print an error message.
+
+@<Move printer...@>=
+ if want_split then PR_v_next := PR_v_next - w_l_feed_dist ;
+ if (PR_v_next < PR_v) then begin
+ if b_feed_absolute then set_v_abs(PR_v_next)
+ else if b_feed_by_string then
+ while PR_v_next < PR_v do @<Backfeed@>
+ else if b_feed_scream then begin
+ warn('this printer cant feed backwards');
+ @.error: printer cant...@>
+ display_ln('approximate vertical position is: ', PR_v_next);
+ display_ln(' printing over-fed line on line below');
+ display_ln(' ');
+ PR_v := PR_v_next;
+ end;
+ end;
+
+@ If we avoided over-feeding, we may want to feed forwards.
+
+@<Move printer...@>=
+ if PR_v_next > PR_v then begin
+ if feed_absolute then set_v_abs(PR_v_next)
+ else begin
+ while PR_v_next >= PR_v + feed_dist do @<Line feed@>;
+ while PR_v_next > PR_v do @<Tiny feed@> ;
+ end;
+ end;
+ if want_split then begin
+ PR_v := PR_v + w_l_feed_dist ;
+ PR_v_next := PR_v_next + w_l_feed_dist ;
+ if not list then print_ln; {AKT: was just print_ln;}
+ if w_l_does_c_r then PR_h := 0 ;
+ end;
+
+@ We set the horizontal position in a similar way, but we do not need to be so
+paranoid about backspacing as about back-feeding.
+
+@<Set horiz...@>=
+ if PR_h_next = PR_h then
+ else begin
+ if (PR_h_next < PR_h) then begin
+ if b_space_absolute then set_h_abs(PR_h_next)
+ else if b_space_by_string then
+ while PR_h_next < PR_h do @<Backspace@>;
+ end;
+ if space_absolute and (PR_h_next > PR_h )
+ then set_h_abs(PR_h_next)
+ else begin
+ while PR_h_next >= PR_h + space_dist do @<Space@>;
+ while PR_h_next > PR_h do @<Tiny space@> ;
+ end;
+ end;
+
+@* Low level modules for printer control.
+
+Now we have to translate these elementary printer commands into actual strings
+of characters to be put into |printfile|. Here is the command for setting a
+new printer's font.
+
+@<Lowest...@>=
+ procedure set_PR_font(new:integer) ;
+ begin
+ if (new = PR_font) or only_one_font then
+ else
+ begin
+ string_print(font_prefix) ;
+ print_integer(new, param_type);
+ string_print(font_suffix) ;
+ PR_font := new ;
+ end;
+ end;
+
+@ Now for |absolute| movements, if the printer can do them. The procedure
+|set_v_abs| moves the printer to position |mm h_steps| below the top of the
+paper. If |abs_is_incr| then the printers `absolute' command is actually an
+incremental command. So the parameter sent to the printer must be decreased by
+|PR_v|.
+
+@<Forward...@>=
+ procedure set_v_abs(mm: integer) ; forward ;
+ procedure set_h_abs(mm: integer) ; forward ;
+
+@ @<Lowest...@>=
+ procedure set_v_abs;
+ var new_pos :integer ;
+ begin
+ if abs_is_incr then
+ new_pos := mm - PR_v
+ else new_pos := mm ;
+ string_print (v_abs_prefix) ;
+ print_integer(new_pos, param_type) ;
+ string_print (v_abs_suffix) ;
+ PR_v := mm ;
+ end;
+
+@#
+ procedure set_h_abs;
+ var new_pos :integer ;
+ begin
+ if abs_is_incr then
+ new_pos := mm - PR_h
+ else new_pos := mm ;
+ string_print (h_abs_prefix) ;
+ print_integer(new_pos, param_type) ;
+ string_print (h_abs_suffix) ;
+ PR_h := mm ;
+ end;
+
+@ Now consider commands for printers that can only do simple movements. A
+|tiny| movement is usually a movement of one |h_step| or |v_step|. All these
+modules should be protected, so they cannot be called unless the printer can
+actually do the stated movement. Normally, the command strings for these are
+only simple characters, so we can just |print| them.
+
+@<Formfeed@>=
+ {AKT: was page(printfile);}
+ print_ln;
+ print_ln;
+ print_ln;
+ print('-------------------- [new page] --------------------');
+ print_ln;
+ print_ln;
+ if is_header then
+ string_print (page_top);
+
+@ @<Backfeed@>=
+ begin string_print(b_feed_string);
+ PR_v:=PR_v - b_feed_dist;
+ end
+
+@ @<Line feed@>=
+ begin
+ if fortran then begin
+ print_ln ;
+ PR_v := PR_v + w_l_feed_dist;
+ end;
+ if list then {AKT: was just print(feed_char);}
+ print_ln
+ else
+ print(feed_char);
+ PR_v:=PR_v+feed_dist;
+ end;
+
+@ @<Tiny feed@>=
+ begin print(t_feed_char); PR_v:=PR_v+t_feed_dist; end;
+
+@ @<Backspace@>=
+ begin print (b_space_char); PR_h:=PR_h-b_space_dist; end;
+
+@ @<Space@>=
+ begin print (space_char); PR_h:=PR_h+space_dist; end;
+
+@ @<Tiny space@>=
+ begin print (t_space_char); PR_h:=PR_h+t_space_dist; end;
+
+@* Default declarations for printer.
+
+Here we define a lot of printer-dependent material that is expected to be the
+same for most printers. Of course, these will have to be changed if |fortran|,
+or on a system that does not use ASCII codes. First, some command characters
+for simple movements. |feed| means a vertical movement and |space| horizontal.
+Each |thing_char| is the character needed to make the printer do the named
+action. Owing to the rules of \.{TANGLE}, the words |back| and |tiny| have to
+be abbreviated (to avoid identifier clashes). |c_r_char| etc. must be
+consistent with the value of |fortran|.
+ @.ASCII@>
+
+@<Set init...@>=
+ space_char := chr(32) ;
+ t_space_char := chr(32) ;
+ feed_char := chr(10) ;
+ t_feed_char := chr(10);
+ c_r_char := chr(13);
+ b_space_char := chr(8);
+
+@ @<Glob...@>=
+ space_char,
+ t_space_char,
+ feed_char ,
+ t_feed_char ,
+ c_r_char ,
+ b_space_char : char ;
+
+@ Next the distances that they normally move, always in |steps|.
+
+@<Carriage control const...@>=
+ space_dist = 1;
+ b_space_dist = 1;
+ t_space_dist = 1;
+ feed_dist = 1 ;
+ w_l_feed_dist = 0 ;
+ b_feed_dist = 0;
+ t_feed_dist = 1;
+ c_r_feed_dist = 0 ;
+ tiny_drop = 500000 ; {AKT: avoid E in TEX dropping to next line}
+ {tiny_drop = 50000 ; slightly less than a point}
+ big_drop = 4 ;
+
+@ |start_stuff| and |stop_stuff| get written into the start and end of
+|printfile|. They are intended to: set printer into correct state for \TeX\
+output, and reset printer to standard state afterwards. If the printer needs
+to be re-initialised in any way at the top of each page, then set |@!page_top|
+to the necessary data and set |is_header| to |true|.
+
+@<Open |printfile|@>=
+ string_print(start_stuff) ;
+ print_ln ;
+
+@ @<Clean up afterwards@>=
+ string_print(stop_stuff);
+
+@ @<Glob...@>=
+ start_stuff,
+ stop_stuff,
+ page_top,
+ b_feed_string ,
+ font_prefix,
+ font_suffix,
+ v_abs_prefix,
+ v_abs_suffix,
+ h_abs_prefix,
+ h_abs_suffix : var_string ;
+ print_end : var_string ;
+
+@* Printer dependent data.
+
+This section should define masses of data to describe how the printer behaves.
+In order to keep the size of each printer's change file within reasonable
+bounds, I have replaced this section by a blank. The missing data is given in
+the line printer change file. To set up for another printer, that file will
+have to be extensively edited.
+
+*** Attach printer change file here ***
+
+@* Index.
+
+Pointers to error messages appear here together with the section numbers
+where each identifier is used.
diff --git a/dviware/screenview/vms/screen.sty b/dviware/screenview/vms/screen.sty
new file mode 100644
index 0000000000..003b8f536a
--- /dev/null
+++ b/dviware/screenview/vms/screen.sty
@@ -0,0 +1,29 @@
+% SCREEN.STY is used to create a LaTeX document suitable for SCREENVIEWing.
+
+\oddsidemargin=0pt % for 1in left margin (which SCREENVIEW removes!)
+\evensidemargin=0pt % ditto
+\textwidth=400pt % so SCREENVIEW output will fit within 80 columns
+
+% redefine all font sizes to be \normalsize
+\def\tiny{\normalsize}
+\def\scriptsize{\normalsize}
+\def\footnotesize{\normalsize}
+\def\small{\normalsize}
+\def\large{\normalsize}
+\def\Large{\normalsize}
+\def\LARGE{\normalsize}
+\def\huge{\normalsize}
+\def\Huge{\normalsize}
+
+% redefine all font types to be \tt
+\def\em{\tt}
+\def\bf{\tt}
+\def\it{\tt}
+\def\sl{\tt}
+\def\sf{\tt}
+\def\sc{\tt}
+\def\rm{\tt}
+
+% now typeset document using \tt and \raggedright
+\tt
+\raggedright
diff --git a/dviware/screenview/vms/screenview.hlp b/dviware/screenview/vms/screenview.hlp
new file mode 100644
index 0000000000..19343d5df8
--- /dev/null
+++ b/dviware/screenview/vms/screenview.hlp
@@ -0,0 +1,43 @@
+! This help file assumes the SCREENVIEW symbol has been created.
+1 SCREENVIEW
+
+USAGE: SCREENVIEW input_file [first_page max_pages magnification]
+
+EXAMPLE: $ tex foo ! or latex foo
+ $ screenview foo.dvi
+ $ set term/width=132 ! depending on the text width
+ $ type foo.txt
+
+The SCREENVIEW command translates a given DVI file into a text file
+suitable for viewing on any sort of terminal. It can be useful for
+generating on-line documentation from a TeX or LaTeX source.
+To try and fit the output lines within 80 columns you will probably
+need to change the text width in the TeX or LaTeX source file.
+To do this in TeX simply include some commands near the top of your file:
+
+ \hoffset=0in % for a 1in left margin
+ \hsize=4.5in
+
+For LaTeX, a new style called "screen" can be used; simply add it after
+any other styles you use:
+
+ \documentstyle[uacs,screen]{article}
+
+The "screen" style sets up appropriate margins and text width, redefines
+all the type size commands to \normalsize and all the type style commands
+to \tt, then switches to \tt and uses \raggedright.
+
+The file created by SCREENVIEW can also be used to check line-breaks
+and page-breaks. Note that the horizontal and vertical positioning of
+text is only roughly approximated. In particular, the right margin
+of paragraphs will usually appear ragged. Also, characters from non-text
+fonts (such as math symbols) will appear incorrect.
+
+If you want to use the parameters after the file name then they must
+appear in the right order. For example, to select pages 5 and 6
+you would have to type "SCREENVIEW FOO 5 2".
+
+The magnification parameter is given as a percentage (default is 100)
+and controls the horizontal spacing of words. If the predominant type
+size is large, the magnification may need to be reduced, or else the
+words will be badly spread out.
diff --git a/dviware/screenview/vms/test.dvi b/dviware/screenview/vms/test.dvi
new file mode 100644
index 0000000000..dd3996bf56
--- /dev/null
+++ b/dviware/screenview/vms/test.dvi
Binary files differ
diff --git a/dviware/screenview/vms/test.tex b/dviware/screenview/vms/test.tex
new file mode 100644
index 0000000000..d369dd40c5
--- /dev/null
+++ b/dviware/screenview/vms/test.tex
@@ -0,0 +1,53 @@
+\documentstyle[screen]{article}
+
+\begin{document}
+
+\section{This is a section heading.}
+
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+
+\vskip 10pt
+
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+
+\vskip 10pt
+
+\tiny This is tiny text.\par
+\scriptsize This is scriptsize text.\par
+\footnotesize This is footnotesize text.\par
+\small This is small text.\par
+\normalsize This is normalsize text.\par
+\large This is large text.\par
+\Large This is Large text.\par
+\LARGE This is LARGE text.\par
+\huge This is huge text.\par
+\Huge This is Huge text.\par
+
+\normalsize
+\vskip 10pt
+
+\em This is em text.\par
+\bf This is bf text.\par
+\it This is it text.\par
+\sl This is sl text.\par
+\sf This is sf text.\par
+\sc This is sc text.\par
+\tt This is tt text.\par
+\rm This is rm text.\par
+
+\newpage
+
+\special{Crudetype ignores this.}
+\special{and this}
+
+Language/Utility \hfill Times used \hfill Application \hfill Times used
+
+\end{document}
diff --git a/dviware/screenview/vms/test.txt b/dviware/screenview/vms/test.txt
new file mode 100644
index 0000000000..48afe85fbd
--- /dev/null
+++ b/dviware/screenview/vms/test.txt
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+1 This is a section heading.
+
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+The cat sat on the mat. The cat sat on the mat. The cat sat on the mat.
+
+This is tiny text.
+This is scriptsize text.
+This is footnotesize text.
+This is small text.
+This is normalsize text.
+This is large text.
+This is Large text.
+This is LARGE text.
+This is huge text.
+This is Huge text.
+
+This is em text.
+This is bf text.
+This is it text.
+This is sl text.
+This is sf text.
+This is sc text.
+This is tt text.
+This is rm text.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+-------------------- [new page] --------------------
+
+
+
+
+
+
+
+
+Language/Utility Times used Application Times used
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2