diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /dviware/screenview/vms |
Initial commit
Diffstat (limited to 'dviware/screenview/vms')
-rw-r--r-- | dviware/screenview/vms/build.com | 8 | ||||
-rw-r--r-- | dviware/screenview/vms/crude.com | 21 | ||||
-rw-r--r-- | dviware/screenview/vms/crudetype.ch | 287 | ||||
-rw-r--r-- | dviware/screenview/vms/crudetype.web | 3282 | ||||
-rw-r--r-- | dviware/screenview/vms/screen.sty | 29 | ||||
-rw-r--r-- | dviware/screenview/vms/screenview.hlp | 43 | ||||
-rw-r--r-- | dviware/screenview/vms/test.dvi | bin | 0 -> 2048 bytes | |||
-rw-r--r-- | dviware/screenview/vms/test.tex | 53 | ||||
-rw-r--r-- | dviware/screenview/vms/test.txt | 131 |
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 Binary files differnew file mode 100644 index 0000000000..dd3996bf56 --- /dev/null +++ b/dviware/screenview/vms/test.dvi 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 |