% This program by D. E. Knuth is not copyrighted and can be used freely. % Version 1 was completed in September, 1982. % Slight changes were made in October, 1982, for version 0.7 of TeX. % Version 1.1 corrected minor bugs (May, 1983). % Version 2 was released with version 0.999 of TeX (July, 1983). % Version 2.1 corrected a bug when no fonts are present (September, 1983). % Version 2.2 corrected bugs in max_*_so_far and put1 (October, 1983). % Version 2.3 corrected spacing of accents (March, 1984). % Version 2.4 fixed rounding, changed oriental font conventions (April, 1984). % Version 2.5 improved the case of zero pages (May, 1984). % Version 2.6 introduced max_drift (June, 1984). % Version 2.7 had minor editorial changes (August, 1984). % Version 1.0 of DVItoLN03 was created (as a change file to DVItype) March 1986 % by Brian HAMILTON KELLY, Royal Military College of Science, % Shrivenham, SWINDON, SN6 8LA, United Kingdom % Tel: Swindon (0793) 785252 (Int'l +44-793-785252) % e-mail: (Janet) tex@@uk.ac.cranfield.rmcs % % This drew heavily upon Flavio Rose's DVI2LNG program, but also % included the work formerly performed as a separate pass by his % LN03TOPP program (which had bugs, and was written in PL/I, for % which we did not have a compiler!) % Version 1.1 corrected minor bugs in the port from PL/I and displayed page % numbers as each was processed April 1986 % Version 1.2 used .PXL files in separate directories for each magnification, % in the manner of Andrew Trevorrow's DVItoVDU July 1986 % Version 1.3 introduced minor cosmetic changes and increased |max_rules| to % 2048 July 1987 % Version 1.4 added support for including sixel graphics % through \special Sep 87 % Version 1.5 merged the original DVItype.WEB and the (now hopelessly huge) % change file, and `WEBified' the whole system. Also added proper % support for default filenames, so that one CAN reference an % older DVI file than the latest version. % Version 1.6 supports oversized glyphs (by making them into invisible % characters) and also outputs invisible characters as a single % pixel high row of white pixels of an appropriate width % Version 1.7 prints the glyphs of oversized characters as bitmap sixel dumps. % Version 2.0 reads PK files instead of .PXL (if available) % Version 2.1 corrects error reading invisible fonts from packed files and % with invisible characters of zero TFM width. % Version 3.0 includes wholesale rearrangement of some sections of code, to % improve its WEB-like quality. It also adds the following % functionality: % The packed and unpacked font files may be provided in % either a flat or a rooted directory structure; if logical % names are used to specify these locations (as in the .CLD % file provided), the files may be spread over a number of % different directories or volumes. % The error messages are improved over earlier versions of % the program, and are now all indexed in the woven % (WEAVEd?) WEB. % The program can now handle fonts with more then 128 % characters, up to TeX's limit of 256. Therefore, it can % now process documents which use Silvio Levy's Greek fonts. % Retention of the log (.TYP) file may now be forced, % suppressed, or allowed to be determined by the success of % the processing. % Minor revisions and corrections have been made, in % particular, it now correctly understands the physical % limitations to the imaging area. % Version 3.1 correctly computes the magnification of each font for the font % loading reports, and also ensures that all output files are % opened in the current default directory, even when logical % names are used for the input file. It also extracts and % reports the full file specification of the source (.DVI) file. % Also introduced the /OUTPUT qualifier and file spec on /LOG. % Version 3.1-1 adds the `special' commands that were in the original Rose % program; this code was developed independently of (and % simultaneously with) version 3.1 (see above), and it therefore % seemed appropriate to give it a DEC-style version number % extension... % Version 3.1-2 increases |max_rules| to 16384 to cope with the output of % BIGTeX. % Version 3.1-3 reports an error status if the opening of a \special sixel % file fails. The LN03 is also reset to pixel spacing units % after the sixel file has been included. % Version 3.1-4 avoids attempting to print non-existent glyphs; some font % designers use non-existent characters in ligature sequences % (for example, Georgia Tobin ligatures * with characters such % as i, j, r, s, to select a variant form of the letter). % With earlier versions of this program, having failed to % download a glyph for the non-existent character, an attempt % would be made to print it is a bitmap when processing the % pages. % Version 3.2 This version, and all others prior to V4.0, were never % released: they were purely internal to RMCS whilst % additional functionality was being incorporated. % This version tidied up Robin Fairbairns' code, particularly % for recognizing units. % Version 3.2-1 Correct code so that the arguments of \special are treated % as |ASCII_code| (they are not *necessarily* |char|s). % Version 3.3 Provide qualifiers to define all logicals used, so that % sites may chose to use TEX$FONTS or TEX_FONTS, etc. % Permit use of units other than pixel for specification % of /LEFT_MARGIN and /TOP_MARGIN. % Version 3.3-1 Permit /LEFT_MARGIN and /TOP_MARGIN to take dimensionless % quantity, with the assumption that it's in pixels. % Version 3.4 Look out for virtual fonts, and reject them % Version 3.4-1 Read VF file, and parse it % Version 3.4-2 Map virtual font characters to real fonts, and load them % Version 3.4-3 Now able to print simple characters from a VF % Version 3.5 Made do_page capable of recursion to support complex % sequences in VF files % Version 3.5-1 Revised DVI reading code for input from |vf| % Version 3.5-2 Scaled |dvi| movements taken from VF file % Version 3.5-3 Corrected computation of scaling of -ve movements % Version 3.5-4 Removed confusion caused by empty segments on |seg_list| % Version 3.5-5 Use font_scaled_size[vfno] for scaling physical fonts! % Version 3.5-6 Corrected storage of -ve parameters in |vf| array % Version 3.5-7 Terminate normal text segment preceding composite character % Version 3.6 Read TFM files only once; fold upper/lower case names % Version 3.6-1 Guard against infinite recursion with font recurse % Version 3.6-2 Reference to another virtual font can't map to LN03 font % Version 3.6-3 Correct spurious checksum error % Version 3.7 Supports landscape and portrait \specials % Version 3.7-1 Defers bitmap output of glyphs until |eop|, like rest % Version 3.7-2 Correct handling of bitmap glyphs in |out_list| % Version 3.8 Support mixture of orientations on same page % Version 3.8-1 Moved all |max...| definitions to near front of WEB % Version 3.8-2 Changed |varying [name_size] of char| into |file_spec| % Version 3.8-3 Included narrative describing TFM files % Version 3.8-4 Aggregate all |glyph_info| into one vector |glyphs| % Version 3.8-5 Cosmetic changes; errors/warnings not just to TYP file % Version 3.8-6 Preliminary moves to set rules for missing fonts % Version 3.9 Image glyph from missing font by a solid rule % Version 3.9-1 Integrate |pixel_width|, etc into |glyph_map| % Version 3.9-2 Integrate |width|, etc into |glyph_map| % Version 3.9-3 Further speed-up, by greater use of |with glyph_map| % Version 3.9-4 Speed up setting loop, by removing double indexing % Version 3.9-5 Defer text too wide message until eop; report phases % Version 3.9-6 Couple more little speed-ups, missed before % Version 3.9-7 Cosmetic amendments; improved indexing % Version 3.9-8 Move before Copy_sixel_file % Version 3.91 Start unravelling |print_mode| and trays at top of form % Version 3.91-1 Acknowledge efforts of Robin and Karsten % Version 3.91-2 Ensure |last_page_recto| set even first time through % Version 3.91-3 Use Karsten's |blank_follows| mechanism % Version 3.91-4 Trim trailing spaces from |cur_name| in error message % Version 3.91-5 Determine |ln03_master| from /DEVICE_TYPE setting % Version 3.91-6 Provide |rd_scaled_pt|, for decoding /hfuzz, /vfuzz % Version 3.91-7 In duplex, don't change tray until 3rd page % Version 3.91-8 Insert sixel dumps into |seg_list| % Version 4.0 Cosmetic changes to error messages - new release!!! \outer\def\N#1.#2.{\MN#1.\vfil\eject % beginning of starred section \def\rhead{\uppercase{\ignorespaces#2}} % define running headline \message{*\modno} % progress report {\def\.{\string\.}\def\BS{\string\BS\space}% Prevent expansion during contents write \edef\next{\write\cont{\Z{#2}{\modno}{\the\pageno}}}\next % to contents file }% \ifon\startsection{\bf\ignorespaces#2.\quad}\ignorespaces} % 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 \font\logo=logo10 % font used for the METAFONT logo \def\MF{{\logo META}\-{\logo FONT}} \def\PASCAL{P{\mc ASCAL}} \def\(#1){} % this is used to make section names sort themselves better \def\9#1{} % this is used for sort keys in the index \def\title{DVI$\,$\lowercase{to}$\,$LN03 V4.0} \def\contentspagenumber{0} \def\topofcontents{\null \def\titlepage{F} % include headline on the contents page \def\rheader{\mainfont\hfil \contentspagenumber} \vfill \centerline{\titlefont The {\ttitlefont DVItoLN03} processor} \vskip 15pt \centerline{(Version 4.0, 27th February 1991)} \centerline{(Vax/VMS Version)} \vfill} \def\botofcontents{\vfill \centerline{\mc Acknowledgements} \centerline{\hsize 5in\baselineskip9pt \vbox{\ninerm\noindent This program was originally based upon Flavio Rose's change file which, applied to David Fuch's original \.{DVItype} program, generated his \.{DVI2LNG} program. (The output produced by \.{DVI2LNG} was then further processed by \.{LN03TOPP} to yield a \.{LN3} file to drive a Digital LN03 laser printer.) Further extensions and enhancements were added, and when the \.{WEB} change file grew to twice the size of the original \.{DVITYPE.WEB} source, a manual \.{WEB}merge operation was performed!\endgraf Support for \.{\\changebar} specials was provided by Robin Fairbairns. The code for the newer DEClaser printers is due to Karsten Nyblad.}}} \pageno=\contentspagenumber \advance\pageno by 1 \let\maybe=\iftrue %\let\maybe=\iffalse \def\meta#1{{$\langle$\rm #1$\rangle$}} \def\9#1{}% Used with @@:sort entry}{printed entry@@> @* Introduction. The \.{DVItoLN03} utility program reads binary device-independent (``\.{DVI}'') files that are produced by document compilers such as \TeX, and converts them into a form suited to driving a Digital LN03 or LN03-Plus laser printer. The @^LN03 laser printer@> output file (with extension \.{LN3}) contains both the instructions to typeset @^LN3 file@> the required characters \&{and} the commands necessary to download the characters required (until DEC get around to providing the Computer Modern fonts in a ROM cartridge!). The LN03-Plus will accept, without complaint, any number of downloaded fonts: however, only the first 14 fonts (each consisting of up to 188 characters, in the \\{GL} and \\{GR} sets) are actually usable for imaging glyphs: attempts to image a glyph from fonts |15..| result in the display of a character taken from one of the first 14 fonts loaded. In view of these limitations, \.{DVItoLN03} only downloads the characters which are actually used within the document being processed; the maximum possible number of characters, even if taken from different \TeX\ fonts, are combined into each single `LN03 font'. The program was developed by Brian Hamilton Kelly at the Royal Military @^Hamilton Kelly, Brian@> College of Science. @^RMCS@> @^Royal Military College of Science@> The \.{DVI2LNG} program of Flavio Rose @^Rose, Flavio@> @:DVI2LNG program}{\.{DVI2LNG} program@> was used as a starting point; this had been distributed as a change file to be applied to the original \.{DVItype} program, @:DVItype Program}{\.{DVItype} program@> and ``translated'' the \.{DVI} file into two output files which were then further processed by his \.{LN03toPP} @:LN03toPP Program}{\.{LN03toPP} program@> program (written in PL/I), which required device-specific versions of unpacked pixel files. The initial step was to perform the two separate passes within a single program (therefore the present program still needs to make two passes through the \.{DVI} file). Later work added further functionality including use of ``ordinary'' pixel files and support for \TeX's \.{\BS special} command to read sixel graphics dump files and include them verbatim within the output file. More recent revisions have produced major versions 2, 3 and 4: \yskip\hang\&{V2.0} Provided capability to read pixel rasters from packed (\.{PK}) files as well as from the traditional \.{PXL} files. It also manages to handle the glyphs of characters which are too large to load into the LN03's memory. \yskip\hang\&{V3.0} Changed method of accessing font files, to allow users flexibility in the storage philosophy they adopt. Can now handle large (256-character) fonts, such as Levy's Greek. @^Levy, Silvio@> @^256-character fonts@> @:two hundred and fifty six character fonts}{256-character fonts@> \yskip\hang\&{V3.1} Robin Fairbairns of Laser Scan (Cambridge, UK) added @^Fairbairns, Robin@> support for Rose's \.{\BS special}s for changebar marking at V3.1-1, June 1989. \yskip\hang\&{V3.2} Tidied up recognition of \TeX\ \meta{physical unit}s. Removed final vestige of `hard-wired' logical names, \\{viz:} \.{TEX\$FONTS} @.TEX\$FONTS@> \yskip\hang\&{V3.3} Permitted \.{/LEFT\UL MARGIN} and \.{TOP\UL MARGIN} qualifiers to take dimensioned lengths, instead of being restricted to pixels. \yskip\hang\&{V3.4} Processed virtual font files, provided each character was only mapped to a single character from some other font; did not support virtual characters which mapped to a sequence of one or more characters from other physical fonts, nor could it handle font \.{recurse}. \yskip\hang\&{V3.5} Handled sequences in virtual font files; still couldn't handle font \.{recurse}. \yskip\hang\&{V3.6} Supports font \.{recurse}; folds multiple references to same font (name and scale) onto single invocation. \yskip\hang\&{V3.7} Supports landscape and portrait specials; whole page only. \yskip\hang\&{V3.8} Supports mixture of landscape and portrait on same page. \yskip\hang\&{V3.9} Prints glyphs from missing fonts as solid rules; added \.{/HFUZZ} and \.{/VFUZZ} qualifiers. \yskip\hang\&{V4.0} Supports the new DEClaser~2100 and~2200 printers (the LN05 and LN06) in addition to the traditional LN03. With the LN06, paper feed trays may be selected from the command line, and the duplexing facilities controlled also. Support for these printers was provided by Karsten Nyblad @^Nyblad, Karsten@> of TFL, the Danish Telecommunication Research Laboratory, and integrated into this new release with the other features mentioned above. One of the stated {\it raisons d'\^^Detre\/} for \.{DVItype} is ``to serve as an example of a program that reads \.{DVI} files correctly, for system programmers who are developing \.{DVI}-related software.'' The paradigms for calculation provided by this example have been followed closely in the present program (by importing them {\it verbatim\/} from \.{DVItype}!) To quote again from \.{DVItype}:\vskip 5pt \centerline{\hsize 5in\baselineskip9pt \vbox{\ninerm\noindent ``Programs for typesetting need to be especially careful about how they do arithmetic; if rounding errors accumulate, margins won't be straight, vertical rules won't line up, and so on. But if rounding is done everywhere, even in the midst of words, there will be uneven spacing between the letters, and that looks bad. Human eyes notice differences of a thousandth of an inch in the positioning of lines that are close together; on low resolution devices, where rounding produces effects four times as great as this, the problem is especially critical. Experience has shown that unusual care is needed even on high-resolution equipment; for example, a mistake in the sixth significant hexadecimal place of a constant once led to a difficult-to-find bug in some software for the Alphatype CRS, which has a resolution of 5333 pixels per inch (make that 5333.33333333 pixels per inch). The document compilers that generate \.{DVI} files make certain assumptions about the arithmetic that will be used by \.{DVI}-reading software, and if these assumptions are violated the results will be of inferior quality.'' }} The first \.{DVItype} program was designed by David Fuchs in 1979, and it @^Fuchs, David Raymond@> went through several versions on different computers as the format of \.{DVI} files was evolving to its present form. The \.{LN3} output file produced by DVItoLN03 always commences by including commands to down-load one or more font-files (each of a maximum of 188 characters) to define just those characters used in the document. The rest of the file consists of commands to set those characters where required. Because the mapping of source characters to those in the down-loaded fonts is arbitrary, the output file is unintelligible to human readers! Whilst many of the earlier changes tried to preserve the ``machine-independence'' of the program, no attempt has been made in this version, since it is assumed that the LN03 printer will be used on a VAX computer. In particular, |random_reading| is used on some of the input files, and is necessary. The |banner| string defined here should be changed whenever \.{DVItoLN03} gets modified. @d banner=='This is DVItoLN03, Vax/VMS Version 4.0' @ This program is written mostly in standard \PASCAL, except where it is necessary to use extensions; for example, \.{DVItoLN03} must read files whose names are dynamically specified, and that would be impossible in pure \PASCAL. Many features of VAX-\PASCAL\ have been incorporated, and no attempt has been made to ensure that all places where such nonstandard constructions are used have been listed in the index under ``system dependencies.'' @!@^system dependencies@> One of the extensions to standard \PASCAL\ that we shall deal with is the ability to move to a random place in a binary file; another is to determine the length of a binary file. Such extensions are not necessary for reading \.{DVI} files, and they are not important for efficiency reasons either. However, when creating the `font files' for downloading the necessary characters to the LN03, \.{DVItoLN03} \&{has} to be able to perform |random_reading|, to locate the glyph of each character within the pixel file. Therefore, if \.{DVItoLN03} is being used with \PASCAL s for which random file positioning is not efficiently available, and the following definition is changed from |true| to |false|, further work will still be required to re-code the processing of the pixel files. Another extension is to use a default |case| as in \.{TANGLE}, \.{WEAVE}, etc. @d random_reading==true {should we skip around in the file?} @d othercases == otherwise {Vax/VMS default for cases not listed explicitly} @d endcases == @+end {follows the default case in an extended |case| statement} @f othercases == else @f endcases == end @ The binary input comes from |dvi_file|, and the \.{LN3} file is written to |ln3_file|. \.{DVItoLN03} also produces a `log' file using |type_file|. Errors and some informational messages are written to this, and are also written to \PASCAL's standard |output| file. The term |print| is used instead of |write| when this program writes on |type_file|, so that all such output could easily be redirected if desired. Under Vax/VMS we use double-precision for all real values. Furthermore, since extensive reference is made to Vax/VMS system services and other library routines, we arrange for the \PASCAL\ program to `inherit' all their definitions (from \.{STARLET}). @d print(#)==write(type_file,#) @d print_ln(#)==write_ln(type_file,#) @d real==double @p @=[INHERIT ('SYS$LIBRARY:STARLET')]@> program DVI_to_LN03(@!dvi_file, @!tfm_file,@!type_file,@!output,@!ln3_file); label @@/ const @@/ type @@/ var @@/ procedure jump_out; forward;@# @t\2@>@@;@# procedure initialize; {this procedure gets things started properly} var @!i:integer; {loop index for initializations} begin @@/ print_ln(banner);@/ @@/ end;@# @@; @ If the program has to stop prematurely, it goes to the `|final_end|'. Another label, |done|, is used when stopping normally. @d final_end=9999 {label for the end of it all} @d done=30 {go here when finished with a subtask} @=final_end,done; @ The following parameters can be changed at compile time to extend or reduce \.{DVItoLN03}'s capacity. As previously mentioned, the LN03-plus is quite happy to accept any number of downloaded fonts, but is incapable of imaging glyphs from more than fourteen of them! (The basic LN03 won't accept more than 21 half-fonts.) Each such font has to be selected by mapping it to one of 10 different Select Graphic Renditions; this is reflected by the constant |max_SGR|. Whenever bitmaps are sent to the printer (in sixel format), it is necessary to establish the ``printing'' position 29 pixels above the current reference point. Despite repeated enquiry of DEC, it has not been established why this is necessary. Naturally, we call this 29 pixels |bitmap_offset|. This same offset also has to be applied to sixel graphics included through a \.{\\special} command. When the paper orientation is selected, we may define the |page_len| and |page_wid| in terms of the physical limits of the paper; these sizes are as follows (for European A4 paper). @^European A4 paper@> (These might later become variables, and be alterable by qualifiers in the command line.) There is, however, a further complication. In either printing orientation, the printable area is of these dimensions; however, this printing area starts 0.25~inches from the top left corner of the paper \\{in the orientation it enters the printer}. Therefore in portrait mode, we can actually print from (75,75) to (2475,3475), with (300,300) being the ``normal'' \TeX\ origin, but in landscape mode we can print (35,75) to (3435,2475), and \TeX's origin should still be at (300,300). The LN03 has a physical (apparently arbitrary) restriction on the maximum size of a downloadable glyph. For a glyph to be acceptable, it must satisfy the inequality $$ 2\times\lceil|num_cols|/2\rceil\times\lceil|num_rows|/8\rceil\le5700$$ The type |file_spec| is used for declaring data objects containing VAX/RMS file specifications, and for declaring procedures which handle them. @d max_line=200 {Maximum length of any line output to the LN03} @d max_print=175 {Maximum number of characters imaged on line} @# @d max_lnfonts=13 {Limitations of the LN03's hardware} @d max_SGR=9 @# @d max_special=300 {maximum size of a \.{\\special} command that we can deal with} @d max_points=256 {number of points for the \.{\\special} rule commands} @d max_strings=30 {Distinct string parameters of \.{\\special}s} @d pool_size=100 {Total characters in those strings} @# @d bitmap_offset=29 @# @d paper_ht=3400 {in pixels} @d paper_wid=2400 @d y_port_min=75 @d y_land_min=35 @d page_x_min=75 @d y_top=75 {Minimum addressable $y$ pixel} @d y_bot=paper_ht+y_port_min {Maximum addressable $y$ pixel} @# @d PFS_landscape=='?23 J' {Extended A4 paper in landscape orientation} @d PFS_portrait=='?22 J' {ditto in portrait; each preceded by |csi|} @# @d lnf_bufsize=2000 {Maximum block size of an LN03 ``font file'' in memory ($\approx1$MB)} @# @d largest_glyph=5700 @# @d max_fonts=100 {maximum number of distinct fonts per \.{DVI} file} @d max_glyphs=10000 {maximum number of different characters among all fonts} @# @d line_length=79 {bracketed lines of output will be at most this long} @d terminal_line_length=150 {maximum number of characters input in a single line of input from the command line interpreter} @# @d stack_size=100 {\.{DVI} files shouldn't |push| beyond this depth} @d name_size=1000 {total length of all font file names} @# @d name_length=255 {a file name shouldn't be longer than this} @# @d max_rules=16384 {maximum number of rules per page} @d max_lap_rules=3 {maximum under/over lap of rules} @# @d vf_size=10000 {maximum |dvi| commands stored from virtual font files} @# @d max_blank_pages=100 {blank \\{verso} pages between successive \\{recto}s} @= @!file_spec = varying [name_length] of char; @ Here are some macros for common programming idioms. @d incr(#) == #:=#+1 {increase a variable by unity} @d decr(#) == #:=#-1 {decrease a variable by unity} @d do_nothing == {empty statement} @ If the \.{DVI} file is badly malformed, the whole process must be aborted; \.{DVItoLN03} will give up, after issuing an error message about the symptoms that were noticed. Such errors might be discovered inside of subroutines inside of subroutines, so a procedure called |jump_out| has been introduced. This procedure, which simply transfers control to the label |final_end| at the end of the program, contains the only non-local |goto| statement in \.{DVItoLN03}. @^system dependencies@> @d crlf==chr(13)+chr(10) @d abort(#)==begin history := fatal_error; print_ln(' '); print_ln(' Fatal error: ',#); @!@.Fatal error ...@> write_ln(output,crlf,'Fatal error: ',#,crlf); jump_out; end @d bad_dvi(#)==abort('Bad DVI file: ',#,'!') @!@.Bad DVI file@> @!@:fatal error Bad DVI file}{\quad\.{Bad DVI file} \\{(q.v.)}@> @d capacity_exceeded(#)==abort('capacity exceeded [',#,']') @!@.capacity exceeded@> @!@:fatal error capacity exceeded}{\quad\.{capacity exceeded} \\{(q.v.)}@> @p procedure jump_out; begin goto final_end; end; @* The character set. Like all programs written with the \.{WEB} system, \.{DVItoLN03} can be used with any character set. But it uses ASCII code internally, because the programming for portable input-output is easier when a fixed internal code is used, and because \.{DVI} files use ASCII code for file names and certain other strings. The next few sections of \.{DVItoLN03} have therefore been copied from the analogous ones in the \.{WEB} system routines. They have been considerably simplified, since \.{DVItoLN03} need not deal with the controversial ASCII codes less than @'40. If such codes appear in the \.{DVI} file, they will be printed as question marks. All characters still occupy 8 bits of storage, to avoid overhead with |packed array|s. @= @!ASCII_code= @=[byte]@> " ".."~"; {a subrange of the integers} @ The original \PASCAL\ compiler was designed in the late 60s, when six-bit character sets were common, so it did not make provision for lower case letters. Nowadays, of course, we need to deal with both upper and lower case alphabets in a convenient way, especially in a program like \.{DVItoLN03}. So we shall assume that the \PASCAL\ system being used for \.{DVItoLN03} has a character set containing at least the standard visible characters of ASCII code (|"!"| through |"~"|). Some \PASCAL\ compilers use the original name |char| for the data type associated with the characters in text files, while other \PASCAL s consider |char| to be a 64-element subrange of a larger data type that has some other name. In order to accommodate this difference, we shall use the name |text_char| to stand for the data type of the characters in the output file. We shall also assume that |text_char| consists of the elements |chr(first_text_char)| through |chr(last_text_char)|, inclusive. The following definitions should be adjusted if necessary. @^system dependencies@> @d text_char == char {the data type of characters in text files} @d first_text_char=0 {ordinal number of the smallest element of |text_char|} @d last_text_char=255 {ordinal number of the largest element of |text_char|} @= @!text_file=packed file of text_char; @ The \.{DVItoLN03} processor converts between ASCII code and the user's external character set by means of arrays |xord| and |xchr| that are analogous to \PASCAL's |ord| and |chr| functions. @= @!xord: array [text_char] of ASCII_code; {specifies conversion of input characters} @!xchr: array [first_text_char..last_text_char] of text_char; {specifies conversion of output characters} @ Under our assumption that the visible characters of standard ASCII are all present, the following assignment statements initialize the |xchr| array properly, without needing any system-dependent changes. @= for i:=0 to @'37 do xchr[i]:='?'; xchr[@'40]:=' '; xchr[@'41]:='!'; xchr[@'42]:='"'; xchr[@'43]:='#'; xchr[@'44]:='$'; xchr[@'45]:='%'; xchr[@'46]:='&'; xchr[@'47]:='''';@/ xchr[@'50]:='('; xchr[@'51]:=')'; xchr[@'52]:='*'; xchr[@'53]:='+'; xchr[@'54]:=','; xchr[@'55]:='-'; xchr[@'56]:='.'; xchr[@'57]:='/';@/ xchr[@'60]:='0'; xchr[@'61]:='1'; xchr[@'62]:='2'; xchr[@'63]:='3'; xchr[@'64]:='4'; xchr[@'65]:='5'; xchr[@'66]:='6'; xchr[@'67]:='7';@/ xchr[@'70]:='8'; xchr[@'71]:='9'; xchr[@'72]:=':'; xchr[@'73]:=';'; xchr[@'74]:='<'; xchr[@'75]:='='; xchr[@'76]:='>'; xchr[@'77]:='?';@/ xchr[@'100]:='@@'; xchr[@'101]:='A'; xchr[@'102]:='B'; xchr[@'103]:='C'; xchr[@'104]:='D'; xchr[@'105]:='E'; xchr[@'106]:='F'; xchr[@'107]:='G';@/ xchr[@'110]:='H'; xchr[@'111]:='I'; xchr[@'112]:='J'; xchr[@'113]:='K'; xchr[@'114]:='L'; xchr[@'115]:='M'; xchr[@'116]:='N'; xchr[@'117]:='O';@/ xchr[@'120]:='P'; xchr[@'121]:='Q'; xchr[@'122]:='R'; xchr[@'123]:='S'; xchr[@'124]:='T'; xchr[@'125]:='U'; xchr[@'126]:='V'; xchr[@'127]:='W';@/ xchr[@'130]:='X'; xchr[@'131]:='Y'; xchr[@'132]:='Z'; xchr[@'133]:='['; xchr[@'134]:='\'; xchr[@'135]:=']'; xchr[@'136]:='^'; xchr[@'137]:='_';@/ xchr[@'140]:='`'; xchr[@'141]:='a'; xchr[@'142]:='b'; xchr[@'143]:='c'; xchr[@'144]:='d'; xchr[@'145]:='e'; xchr[@'146]:='f'; xchr[@'147]:='g';@/ xchr[@'150]:='h'; xchr[@'151]:='i'; xchr[@'152]:='j'; xchr[@'153]:='k'; xchr[@'154]:='l'; xchr[@'155]:='m'; xchr[@'156]:='n'; xchr[@'157]:='o';@/ xchr[@'160]:='p'; xchr[@'161]:='q'; xchr[@'162]:='r'; xchr[@'163]:='s'; xchr[@'164]:='t'; xchr[@'165]:='u'; xchr[@'166]:='v'; xchr[@'167]:='w';@/ xchr[@'170]:='x'; xchr[@'171]:='y'; xchr[@'172]:='z'; xchr[@'173]:='{'; xchr[@'174]:='|'; xchr[@'175]:='}'; xchr[@'176]:='~'; for i:=@'177 to 255 do xchr[i]:='?'; @ The following system-independent code makes the |xord| array contain a suitable inverse to the information in |xchr|. @= for i:=first_text_char to last_text_char do xord[chr(i)]:=@'40; for i:=" " to "~" do xord[xchr[i]]:=i; @* Device-independent file format. Before we get into the details of \.{DVItoLN03}, we need to know exactly what \.{DVI} files are. The form of such files was designed by David R. @^Fuchs, David Raymond@> Fuchs in 1979. Almost any reasonable typesetting device can be driven by a program that takes \.{DVI} files as input, and dozens of such \.{DVI}-to-whatever programs have been written. Thus, it is possible to print the output of document compilers like \TeX\ on many different kinds of equipment. A \.{DVI} file is a stream of 8-bit bytes, which may be regarded as a series of commands in a machine-like language. The first byte of each command is the operation code, and this code is followed by zero or more bytes that provide parameters to the command. The parameters themselves may consist of several consecutive bytes; for example, the `|set_rule|' command has two parameters, each of which is four bytes long. Parameters are usually regarded as nonnegative integers; but four-byte-long parameters, and shorter parameters that denote distances, can be either positive or negative. Such parameters are given in two's complement notation. For example, a two-byte-long distance parameter has a value between $-2^{15}$ and $2^{15}-1$. @.DVI {\rm files}@> A \.{DVI} file consists of a ``preamble,'' followed by a sequence of one or more ``pages,'' followed by a ``postamble.'' The preamble is simply a |pre| command, with its parameters that define the dimensions used in the file; this must come first. Each ``page'' consists of a |bop| command, followed by any number of other commands that tell where characters are to be placed on a physical page, followed by an |eop| command. The pages appear in the order that they were generated, not in any particular numerical order. If we ignore |nop| commands and \\{fnt\_def} commands (which are allowed between any two commands in the file), each |eop| command is immediately followed by a |bop| command, or by a |post| command; in the latter case, there are no more pages in the file, and the remaining bytes form the postamble. Further details about the postamble will be explained later. Some parameters in \.{DVI} commands are ``pointers.'' These are four-byte quantities that give the location number of some other byte in the file; the first byte is number~0, then comes number~1, and so on. For example, one of the parameters of a |bop| command points to the previous |bop|; this makes it feasible to read the pages in backwards order, in case the results are being directed to a device that stacks its output face up. Suppose the preamble of a \.{DVI} file occupies bytes 0 to 99. Now if the first page occupies bytes 100 to 999, say, and if the second page occupies bytes 1000 to 1999, then the |bop| that starts in byte 1000 points to 100 and the |bop| that starts in byte 2000 points to 1000. (The very first |bop|, i.e., the one that starts in byte 100, has a pointer of $-1$.) @= @!dvi_file:byte_file; {the stuff we are \.{DVItoLN03}ing} @ The \.{DVI} format is intended to be both compact and easily interpreted by a machine. Compactness is achieved by making most of the information implicit instead of explicit. When a \.{DVI}-reading program reads the commands for a page, it keeps track of several quantities: (a)~The current font |f| is an integer; this value is changed only by \\{fnt} and \\{fnt\_num} commands. (b)~The current position on the page is given by two numbers called the horizontal and vertical coordinates, |h| and |v|. Both coordinates are zero at the upper left corner of the page; moving to the right corresponds to increasing the horizontal coordinate, and moving down corresponds to increasing the vertical coordinate. Thus, the coordinates are essentially Cartesian, except that vertical directions are flipped; the Cartesian version of |(h,v)| would be |(h,-v)|. (c)~The current spacing amounts are given by four numbers |w|, |x|, |y|, and |z|, where |w| and~|x| are used for horizontal spacing and where |y| and~|z| are used for vertical spacing. (d)~There is a stack containing |(h,v,w,x,y,z)| values; the \.{DVI} commands |push| and |pop| are used to change the current level of operation. Note that the current font~|f| is not pushed and popped; the stack contains only information about positioning. The values of |h|, |v|, |w|, |x|, |y|, and |z| are signed integers having up to 32 bits, including the sign. Since they represent physical distances, there is a small unit of measurement such that increasing |h| by~1 means moving a certain tiny distance to the right. The actual unit of measurement is variable, as explained below. @ Here is a list of all the commands that may appear in a \.{DVI} file. Each command is specified by its symbolic name (e.g., |bop|), its opcode byte (e.g., 139), and its parameters (if any). The parameters are followed by a bracketed number telling how many bytes they occupy; for example, `|p[4]|' means that parameter |p| is four bytes long. \yskip\hang|set_char_0| 0. Typeset character number~0 from font~|f| such that the reference point of the character is at |(h,v)|. Then increase |h| by the width of that character. Note that a character may have zero or negative width, so one cannot be sure that |h| will advance after this command; but |h| usually does increase. \yskip\hang|set_char_1| through |set_char_127| (opcodes 1 to 127). Do the operations of |set_char_0|; but use the character whose number matches the opcode, instead of character~0. \yskip\hang|set1| 128 |c[1]|. Same as |set_char_0|, except that character number~|c| is typeset. \TeX82 uses this command for characters in the range |128<=c<256|. \yskip\hang|set2| 129 |c[2]|. Same as |set1|, except that |c|~is two bytes long, so it is in the range |0<=c<65536|. \TeX82 never uses this command, which is intended for processors that deal with oriental languages; but \.{DVItoLN03} will allow character codes greater than 255, assuming that they all have the same width as the character whose code is $c \bmod 256$. @^oriental characters@>@^Chinese characters@>@^Japanese characters@> \yskip\hang|set3| 130 |c[3]|. Same as |set1|, except that |c|~is three bytes long, so it can be as large as $2^{24}-1$. \yskip\hang|set4| 131 |c[4]|. Same as |set1|, except that |c|~is four bytes long, possibly even negative. Imagine that. \yskip\hang|set_rule| 132 |a[4]| |b[4]|. Typeset a solid black rectangle of height |a| and width |b|, with its bottom left corner at |(h,v)|. Then set |h:=h+b|. If either |a<=0| or |b<=0|, nothing should be typeset. Note that if |b<0|, the value of |h| will decrease even though nothing else happens. Programs that typeset from \.{DVI} files should be careful to make the rules line up carefully with digitized characters, as explained in connection with the |rule_pixels| subroutine below. \yskip\hang|put1| 133 |c[1]|. Typeset character number~|c| from font~|f| such that the reference point of the character is at |(h,v)|. (The `put' commands are exactly like the `set' commands, except that they simply put out a character or a rule without moving the reference point afterwards.) \yskip\hang|put2| 134 |c[2]|. Same as |set2|, except that |h| is not changed. \yskip\hang|put3| 135 |c[3]|. Same as |set3|, except that |h| is not changed. \yskip\hang|put4| 136 |c[4]|. Same as |set4|, except that |h| is not changed. \yskip\hang|put_rule| 137 |a[4]| |b[4]|. Same as |set_rule|, except that |h| is not changed. \yskip\hang|nop| 138. No operation, do nothing. Any number of |nop|'s may occur between \.{DVI} commands, but a |nop| cannot be inserted between a command and its parameters or between two parameters. \yskip\hang|bop| 139 $c_0[4]$ $c_1[4]$ $\ldots$ $c_9[4]$ $p[4]$. Beginning of a page: Set |(h,v,w,x,y,z):=(0,0,0,0,0,0)| and set the stack empty. Set the current font |f| to an undefined value. The ten $c_i$ parameters can be used to identify pages, if a user wants to print only part of a \.{DVI} file; \TeX82 gives them the values of \.{\\count0} $\ldots$ \.{\\count9} at the time \.{\\shipout} was invoked for this page. The parameter |p| points to the previous |bop| command in the file, where the first |bop| has $p=-1$. \yskip\hang|eop| 140. End of page: Print what you have read since the previous |bop|. At this point the stack should be empty. (The \.{DVI}-reading programs that drive most output devices will have kept a buffer of the material that appears on the page that has just ended. This material is largely, but not entirely, in order by |v| coordinate and (for fixed |v|) by |h|~coordinate; so it usually needs to be sorted into some order that is appropriate for the device in question. \.{DVItoLN03} does do such sorting.) \yskip\hang|push| 141. Push the current values of |(h,v,w,x,y,z)| onto the top of the stack; do not change any of these values. Note that |f| is not pushed. \yskip\hang|pop| 142. Pop the top six values off of the stack and assign them to |(h,v,w,x,y,z)|. The number of pops should never exceed the number of pushes, since it would be highly embarrassing if the stack were empty at the time of a |pop| command. \yskip\hang|right1| 143 |b[1]|. Set |h:=h+b|, i.e., move right |b| units. The parameter is a signed number in two's complement notation, |-128<=b<128|; if |b<0|, the reference point actually moves left. \yskip\hang|right2| 144 |b[2]|. Same as |right1|, except that |b| is a two-byte quantity in the range |-32768<=b<32768|. \yskip\hang|right3| 145 |b[3]|. Same as |right1|, except that |b| is a three-byte quantity in the range |@t$-2^{23}$@><=b<@t$2^{23}$@>|. \yskip\hang|right4| 146 |b[4]|. Same as |right1|, except that |b| is a four-byte quantity in the range |@t$-2^{31}$@><=b<@t$2^{31}$@>|. \yskip\hang|w0| 147. Set |h:=h+w|; i.e., move right |w| units. With luck, this parameterless command will usually suffice, because the same kind of motion will occur several times in succession; the following commands explain how |w| gets particular values. \yskip\hang|w1| 148 |b[1]|. Set |w:=b| and |h:=h+b|. The value of |b| is a signed quantity in two's complement notation, |-128<=b<128|. This command changes the current |w|~spacing and moves right by |b|. \yskip\hang|w2| 149 |b[2]|. Same as |w1|, but |b| is a two-byte-long parameter, |-32768<=b<32768|. \yskip\hang|w3| 150 |b[3]|. Same as |w1|, but |b| is a three-byte-long parameter, |@t$-2^{23}$@><=b<@t$2^{23}$@>|. \yskip\hang|w4| 151 |b[4]|. Same as |w1|, but |b| is a four-byte-long parameter, |@t$-2^{31}$@><=b<@t$2^{31}$@>|. \yskip\hang|x0| 152. Set |h:=h+x|; i.e., move right |x| units. The `|x|' commands are like the `|w|' commands except that they involve |x| instead of |w|. \yskip\hang|x1| 153 |b[1]|. Set |x:=b| and |h:=h+b|. The value of |b| is a signed quantity in two's complement notation, |-128<=b<128|. This command changes the current |x|~spacing and moves right by |b|. \yskip\hang|x2| 154 |b[2]|. Same as |x1|, but |b| is a two-byte-long parameter, |-32768<=b<32768|. \yskip\hang|x3| 155 |b[3]|. Same as |x1|, but |b| is a three-byte-long parameter, |@t$-2^{23}$@><=b<@t$2^{23}$@>|. \yskip\hang|x4| 156 |b[4]|. Same as |x1|, but |b| is a four-byte-long parameter, |@t$-2^{31}$@><=b<@t$2^{31}$@>|. \yskip\hang|down1| 157 |a[1]|. Set |v:=v+a|, i.e., move down |a| units. The parameter is a signed number in two's complement notation, |-128<=a<128|; if |a<0|, the reference point actually moves up. \yskip\hang|down2| 158 |a[2]|. Same as |down1|, except that |a| is a two-byte quantity in the range |-32768<=a<32768|. \yskip\hang|down3| 159 |a[3]|. Same as |down1|, except that |a| is a three-byte quantity in the range |@t$-2^{23}$@><=a<@t$2^{23}$@>|. \yskip\hang|down4| 160 |a[4]|. Same as |down1|, except that |a| is a four-byte quantity in the range |@t$-2^{31}$@><=a<@t$2^{31}$@>|. \yskip\hang|y0| 161. Set |v:=v+y|; i.e., move down |y| units. With luck, this parameterless command will usually suffice, because the same kind of motion will occur several times in succession; the following commands explain how |y| gets particular values. \yskip\hang|y1| 162 |a[1]|. Set |y:=a| and |v:=v+a|. The value of |a| is a signed quantity in two's complement notation, |-128<=a<128|. This command changes the current |y|~spacing and moves down by |a|. \yskip\hang|y2| 163 |a[2]|. Same as |y1|, but |a| is a two-byte-long parameter, |-32768<=a<32768|. \yskip\hang|y3| 164 |a[3]|. Same as |y1|, but |a| is a three-byte-long parameter, |@t$-2^{23}$@><=a<@t$2^{23}$@>|. \yskip\hang|y4| 165 |a[4]|. Same as |y1|, but |a| is a four-byte-long parameter, |@t$-2^{31}$@><=a<@t$2^{31}$@>|. \yskip\hang|z0| 166. Set |v:=v+z|; i.e., move down |z| units. The `|z|' commands are like the `|y|' commands except that they involve |z| instead of |y|. \yskip\hang|z1| 167 |a[1]|. Set |z:=a| and |v:=v+a|. The value of |a| is a signed quantity in two's complement notation, |-128<=a<128|. This command changes the current |z|~spacing and moves down by |a|. \yskip\hang|z2| 168 |a[2]|. Same as |z1|, but |a| is a two-byte-long parameter, |-32768<=a<32768|. \yskip\hang|z3| 169 |a[3]|. Same as |z1|, but |a| is a three-byte-long parameter, |@t$-2^{23}$@><=a<@t$2^{23}$@>|. \yskip\hang|z4| 170 |a[4]|. Same as |z1|, but |a| is a four-byte-long parameter, |@t$-2^{31}$@><=a<@t$2^{31}$@>|. \yskip\hang|fnt_num_0| 171. Set |f:=0|. Font 0 must previously have been defined by a \\{fnt\_def} instruction, as explained below. \yskip\hang|fnt_num_1| through |fnt_num_63| (opcodes 172 to 234). Set |f:=1|, \dots, |f:=63|, respectively. \yskip\hang|fnt1| 235 |k[1]|. Set |f:=k|. \TeX82 uses this command for font numbers in the range |64<=k<256|. \yskip\hang|fnt2| 236 |k[2]|. Same as |fnt1|, except that |k|~is two bytes long, so it is in the range |0<=k<65536|. \TeX82 never generates this command, but large font numbers may prove useful for specifications of color or texture, or they may be used for special fonts that have fixed numbers in some external coding scheme. \yskip\hang|fnt3| 237 |k[3]|. Same as |fnt1|, except that |k|~is three bytes long, so it can be as large as $2^{24}-1$. \yskip\hang|fnt4| 238 |k[4]|. Same as |fnt1|, except that |k|~is four bytes long; this is for the really big font numbers (and for the negative ones). \yskip\hang|xxx1| 239 |k[1]| |x[k]|. This command is undefined in general; it functions as a $(k+2)$-byte |nop| unless special \.{DVI}-reading programs are being used. \TeX82 generates |xxx1| when a short enough \.{\\special} appears, setting |k| to the number of bytes being sent. It is recommended that |x| be a string having the form of a keyword followed by possible parameters relevant to that keyword. \yskip\hang|xxx2| 240 |k[2]| |x[k]|. Like |xxx1|, but |0<=k<65536|. \yskip\hang|xxx3| 241 |k[3]| |x[k]|. Like |xxx1|, but |0<=k<@t$2^{24}$@>|. \yskip\hang|xxx4| 242 |k[4]| |x[k]|. Like |xxx1|, but |k| can be ridiculously large. \TeX82 uses |xxx4| when |xxx1| would be incorrect. \yskip\hang|fnt_def1| 243 |k[1]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. Define font |k|, where |0<=k<256|; font definitions will be explained shortly. \yskip\hang|fnt_def2| 244 |k[2]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. Define font |k|, where |0<=k<65536|. \yskip\hang|fnt_def3| 245 |k[3]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. Define font |k|, where |0<=k<@t$2^{24}$@>|. \yskip\hang|fnt_def4| 246 |k[4]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. Define font |k|, where |@t$-2^{31}$@><=k<@t$2^{31}$@>|. \yskip\hang|pre| 247 |i[1]| |num[4]| |den[4]| |mag[4]| |k[1]| |x[k]|. Beginning of the preamble; this must come at the very beginning of the file. Parameters |i|, |num|, |den|, |mag|, |k|, and |x| are explained below. \yskip\hang|post| 248. Beginning of the postamble, see below. \yskip\hang|post_post| 249. Ending of the postamble, see below. \yskip\noindent Commands 250--255 are undefined at the present time. @ @d set_char_0=0 {typeset character 0 and move right} @d set1=128 {typeset a character and move right} @d set_rule=132 {typeset a rule and move right} @d put1=133 {typeset a character} @d put_rule=137 {typeset a rule} @d nop=138 {no operation} @d bop=139 {beginning of page} @d eop=140 {ending of page} @d push=141 {save the current positions} @d pop=142 {restore previous positions} @d right1=143 {move right} @d w0=147 {move right by |w|} @d w1=148 {move right and set |w|} @d x0=152 {move right by |x|} @d x1=153 {move right and set |x|} @d down1=157 {move down} @d y0=161 {move down by |y|} @d y1=162 {move down and set |y|} @d z0=166 {move down by |z|} @d z1=167 {move down and set |z|} @d fnt_num_0=171 {set current font to 0} @d fnt1=235 {set current font} @d xxx1=239 {extension to \.{DVI} primitives} @d xxx4=242 {potentially long extension to \.{DVI} primitives} @d fnt_def1=243 {define the meaning of a font number} @d pre=247 {preamble} @d post=248 {postamble beginning} @d post_post=249 {postamble ending} @d undefined_commands==250,251,252,253,254,255 @ The preamble contains basic information about the file as a whole. As stated above, there are six parameters: $$\hbox{|@!i[1]| |@!num[4]| |@!den[4]| |@!mag[4]| |@!k[1]| |@!x[k]|.}$$ The |i| byte identifies \.{DVI} format; currently this byte is always set to~2. (Some day we will set |i=3|, when \.{DVI} format makes another incompatible change---perhaps in 1992.) The next two parameters, |num| and |den|, are positive integers that define the units of measurement; they are the numerator and denominator of a fraction by which all dimensions in the \.{DVI} file could be multiplied in order to get lengths in units of $10^{-7}$ meters. (For example, there are exactly 7227 \TeX\ points in 254 centimeters, and \TeX82 works with scaled points where there are $2^{16}$ sp in a point, so \TeX82 sets |num=25400000| and $|den|=7227\cdot2^{16}=473628672$.) @^sp@> The |mag| parameter is what \TeX82 calls \.{\\mag}, i.e., 1000 times the desired magnification. The actual fraction by which dimensions are multiplied is therefore $mn/1000d$. Note that if a \TeX\ source document does not call for any `\.{true}' dimensions, and if you change it only by specifying a different \.{\\mag} setting, the \.{DVI} file that \TeX\ creates will be completely unchanged except for the value of |mag| in the preamble and postamble. (Fancy \.{DVI}-reading programs allow users to override the |mag|~setting when a \.{DVI} file is being printed; this facility has not been implemented in \.{DVItoLN03}.) Finally, |k| and |x| allow the \.{DVI} writer to include a comment, which is not interpreted further. The length of comment |x| is |k|, where |0<=k<256|. @d id_byte=2 {identifies the kind of \.{DVI} files described here} @ Font definitions for a given font number |k| contain further parameters $$\hbox{|c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|.}$$ The four-byte value |c| is the check sum that \TeX\ (or whatever program generated the \.{DVI} file) found in the \.{TFM} file for this font; |c| should match the check sum of the font found by programs that read this \.{DVI} file. @^check sum@> Parameter |s| contains a fixed-point scale factor that is applied to the character widths in font |k|; font dimensions in \.{TFM} files and other font files are relative to this quantity, which is always positive and less than $2^{27}$. It is given in the same units as the other dimensions of the \.{DVI} file. Parameter |d| is similar to |s|; it is the ``design size,'' and it is given in \.{DVI} units that have not been corrected for the magnification~|mag| found in the preamble. Thus, font |k| is to be used at $|mag|\cdot s/1000d$ times its normal size. The remaining part of a font definition gives the external name of the font, which is an ASCII string of length |a+l|. The number |a| is the length of the ``area'' or directory, and |l| is the length of the font name itself; the standard local system font area is supposed to be used when |a=0|. The |n| field contains the area in its first |a| bytes. Font definitions must appear before the first use of a particular font number. Once font |k| is defined, it must not be defined again; however, we shall see below that font definitions appear in the postamble as well as in the pages, so in this sense each font number is defined exactly twice, if at all. Like |nop| commands and \\{xxx} commands, font definitions can appear before the first |bop|, or between an |eop| and a |bop|. @ The last page in a \.{DVI} file is followed by `|post|'; this command introduces the postamble, which summarizes important facts that \TeX\ has accumulated about the file, making it possible to print subsets of the data with reasonable efficiency. The postamble has the form $$\vbox{\halign{\hbox{#\hfil}\cr |post| |p[4]| |num[4]| |den[4]| |mag[4]| |l[4]| |u[4]| |s[2]| |t[2]|\cr $\langle\,$font definitions$\,\rangle$\cr |post_post| |q[4]| |i[1]| 223's$[{\G}4]$\cr}}$$ Here |p| is a pointer to the final |bop| in the file. The next three parameters, |num|, |den|, and |mag|, are duplicates of the quantities that appeared in the preamble. Parameters |l| and |u| give respectively the height-plus-depth of the tallest page and the width of the widest page, in the same units as other dimensions of the file. These numbers might be used by a \.{DVI}-reading program to position individual ``pages'' on large sheets of film or paper; however, the standard convention for output on normal size paper is to position each page so that the upper left-hand corner is exactly one inch from the left and the top. Experience has shown that it is unwise to design \.{DVI}-to-printer software that attempts cleverly to center the output; a fixed position of the upper left corner is easiest for users to understand and to work with. Therefore |l| and~|u| are often ignored. Parameter |s| is the maximum stack depth (i.e., the largest excess of |push| commands over |pop| commands) needed to process this file. Then comes |t|, the total number of pages (|bop| commands) present. The postamble continues with font definitions, which are any number of \\{fnt\_def} commands as described above, possibly interspersed with |nop| commands. Each font number that is used in the \.{DVI} file must be defined exactly twice: Once before it is first selected by a \\{fnt} command, and once in the postamble. @ The last part of the postamble, following the |post_post| byte that signifies the end of the font definitions, contains |q|, a pointer to the |post| command that started the postamble. An identification byte, |i|, comes next; this currently equals~2, as in the preamble. The |i| byte is followed by four or more bytes that are all equal to the decimal number 223 (i.e., @'337 in octal). \TeX\ puts out four to seven of these trailing bytes, until the total length of the file is a multiple of four bytes, since this works out best on machines that pack four bytes per word; but any number of 223's is allowed, as long as there are at least four of them. In effect, 223 is a sort of signature that is added at the very end. @^Fuchs, David Raymond@> This curious way to finish off a \.{DVI} file makes it feasible for \.{DVI}-reading programs to find the postamble first, on most computers, even though \TeX\ wants to write the postamble last. Most operating systems permit random access to individual words or bytes of a file, so the \.{DVI} reader can start at the end and skip backwards over the 223's until finding the identification byte. Then it can back up four bytes, read |q|, and move to byte |q| of the file. This byte should, of course, contain the value 248 (|post|); now the postamble can be read, so the \.{DVI} reader discovers all the information needed for typesetting the pages. Note that it is also possible to skip through the \.{DVI} file at reasonably high speed to locate a particular page, if that proves desirable. This saves a lot of time, since \.{DVI} files used in production jobs tend to be large. Unfortunately, however, standard \PASCAL\ does not include the ability to @^system dependencies@> access a random position in a file, or even to determine the length of a file. Almost all systems nowadays provide the necessary capabilities, so \.{DVI} format has been designed to work most efficiently with modern operating systems. As noted above, \.{DVItoLN03} would need to be modified to limit itself to the restrictions of standard \PASCAL\ if |random_reading| is defined to be |false|. @* Font metric data. The idea behind \.{TFM} files is that typesetting routines like \TeX\ need a compact way to store the relevant information about several dozen fonts, and computer centers need a compact way to store the relevant information about several hundred fonts. \.{TFM} files are compact, and most of the information they contain is highly relevant, so they provide a solution to the problem. The information in a \.{TFM} file appears in a sequence of 8-bit bytes. Since the number of bytes is always a multiple of 4, we could also regard the file as a sequence of 32-bit words; but \TeX\ uses the byte interpretation. Note that the bytes are considered to be unsigned numbers. @= @!tfm_file:byte_file; {a font metric file} @ The first 24 bytes (6 words) of a \.{TFM} file contain twelve 16-bit integers that give the lengths of the various subsequent portions of the file. These twelve integers are, in order: $$\vbox{\halign{\hfil#&$\null=\null$#\hfil\cr |@!lf|&length of the entire file, in words;\cr |@!lh|&length of the header data, in words;\cr |@!bc|&smallest character code in the font;\cr |@!ec|&largest character code in the font;\cr |@!nw|&number of words in the width table;\cr |@!nh|&number of words in the height table;\cr |@!nd|&number of words in the depth table;\cr |@!ni|&number of words in the italic correction table;\cr |@!nl|&number of words in the lig/kern table;\cr |@!nk|&number of words in the kern table;\cr |@!ne|&number of words in the extensible character table;\cr |@!np|&number of font parameter words.\cr}}$$ They are all nonnegative and less than $2^{15}$. We must have |bc-1<=ec<=255|, |ne<=256|, and $$\hbox{|lf=6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+nk+ne+np|.}$$ Note that a font may contain as many as 256 characters (if |bc=0| and |ec=255|), and as few as 0 characters (if |bc=ec+1|). Incidentally, when two or more 8-bit bytes are combined to form an integer of 16 or more bits, the most significant bytes appear first in the file. This is called BigEndian order. @ The rest of the \.{TFM} file may be regarded as a sequence of ten data arrays having the informal specification $$\def\arr$[#1]#2${\&{array} $[#1]$ \&{of} #2} \vbox{\halign{\hfil\\{#}&$\,:\,$\arr#\hfil\cr header&|[0..lh-1]stuff|\cr char\_info&|[bc..ec]char_info_word|\cr width&|[0..nw-1]fix_word|\cr height&|[0..nh-1]fix_word|\cr depth&|[0..nd-1]fix_word|\cr italic&|[0..ni-1]fix_word|\cr lig\_kern&|[0..nl-1]lig_kern_command|\cr kern&|[0..nk-1]fix_word|\cr exten&|[0..ne-1]extensible_recipe|\cr param&|[1..np]fix_word|\cr}}$$ The most important data type used here is a |@!fix_word|, which is a 32-bit representation of a binary fraction. A |fix_word| is a signed quantity, with the two's complement of the entire word used to represent negation. Of the 32 bits in a |fix_word|, exactly 12 are to the left of the binary point; thus, the largest |fix_word| value is $2048-2^{-20}$, and the smallest is $-2048$. We will see below, however, that all but one of the |fix_word| values will lie between $-16$ and $+16$. @ The first data array is a block of header information, which contains general facts about the font. The header must contain at least two words, and for \.{TFM} files to be used with Xerox printing software it must contain at least 18 words, allocated as described below. When different kinds of devices need to be interfaced, it may be necessary to add further words to the header block. \yskip\hang|header[0]| is a 32-bit check sum that \TeX\ will copy into the \.{DVI} output file whenever it uses the font. Later on when the \.{DVI} file is printed, possibly on another computer, the actual font that gets used is supposed to have a check sum that agrees with the one in the \.{TFM} file used by \TeX. In this way, users will be warned about potential incompatibilities. (However, if the check sum is zero in either the font file or the \.{TFM} file, no check is made.) The actual relation between this check sum and the rest of the \.{TFM} file is not important; the check sum is simply an identification number with the property that incompatible fonts almost always have distinct check sums. @^check sum@> \yskip\hang|header[1]| is a |fix_word| containing the design size of the font, in units of \TeX\ points (7227 \TeX\ points = 254 cm). This number must be at least 1.0; it is fairly arbitrary, but usually the design size is 10.0 for a ``10 point'' font, i.e., a font that was designed to look best at a 10-point size, whatever that really means. When a \TeX\ user asks for a font `\.{at} $\delta$ \.{pt}', the effect is to override the design size and replace it by $\delta$, and to multiply the $x$ and~$y$ coordinates of the points in the font image by a factor of $\delta$ divided by the design size. {\sl All other dimensions in the\/\ \.{TFM} file are |fix_word|\kern-1pt\ numbers in design-size units.} Thus, for example, the value of |param[6]|, one \.{em} or \.{\\quad}, is often the |fix_word| value $2^{20}=1.0$, since many fonts have a design size equal to one em. The other dimensions must be less than 16 design-size units in absolute value; thus, |header[1]| and |param[1]| are the only |fix_word| entries in the whole \.{TFM} file whose first byte might be something besides 0 or 255. @^design size@> \yskip\hang|header[2..11]|, if present, contains 40 bytes that identify the character coding scheme. The first byte, which must be between 0 and 39, is the number of subsequent ASCII bytes actually relevant in this string, which is intended to specify what character-code-to-symbol convention is present in the font. Examples are \.{ASCII} for standard ASCII, \.{TeX text} for fonts like \.{cmr10} and \.{cmti9}, \.{TeX math extension} for \.{cmex10}, \.{XEROX text} for Xerox fonts, \.{GRAPHIC} for special-purpose non-alphabetic fonts, \.{UNSPECIFIED} for the default case when there is no information. Parentheses should not appear in this name. (Such a string is said to be in {\mc BCPL} format.) @^coding scheme@> \yskip\hang|header[12..16]|, if present, contains 20 bytes that name the font family (e.g., \.{CMR} or \.{HELVETICA}), in {\mc BCPL} format. This field is also known as the ``font identifier.'' @^family name@> @^font identifier@> \yskip\hang|header[17]|, if present, contains a first byte called the |seven_bit_safe_flag|, then two bytes that are ignored, and a fourth byte called the |face|. If the value of the fourth byte is less than 18, it has the following interpretation as a ``weight, slope, and expansion'': Add 0 or 2 or 4 (for medium or bold or light) to 0 or 1 (for roman or italic) to 0 or 6 or 12 (for regular or condensed or extended). For example, 13 is 0+1+12, so it represents medium italic extended. A three-letter code (e.g., \.{MIE}) can be used for such |face| data. \yskip\hang|header[18..@twhatever@>]| might also be present; the individual words are simply called |header[18]|, |header[19]|, etc., at the moment. @ Next comes the |char_info| array, which contains one |char_info_word| per character. Each |char_info_word| contains six fields packed into four bytes as follows. \yskip\hang first byte: |width_index| (8 bits)\par \hang second byte: |height_index| (4 bits) times 16, plus |depth_index| (4~bits)\par \hang third byte: |italic_index| (6 bits) times 4, plus |tag| (2~bits)\par \hang fourth byte: |remainder| (8 bits)\par \yskip\noindent The actual width of a character is |width[width_index]|, in design-size units; this is a device for compressing information, since many characters have the same width. Since it is quite common for many characters to have the same height, depth, or italic correction, the \.{TFM} format imposes a limit of 16 different heights, 16 different depths, and 64 different italic corrections. Incidentally, the relation |width[0]=height[0]=depth[0]=italic[0]=0| should always hold, so that an index of zero implies a value of zero. The |width_index| should never be zero unless the character does not exist in the font, since a character is valid if and only if it lies between |bc| and |ec| and has a nonzero |width_index|. @ The remaining details of the format of \.{TFM} files need not concern us further; the interested reader is referred to Knuth's standard \.{TFtoPL} @^Knuth, D.~E.@> @.TFtoPL@> program for the whole picture. @* Input from binary files. We have seen that a \.{DVI} file is a sequence of 8-bit bytes. The bytes appear physically in what is called a `|packed file of 0..255|' in \PASCAL\ lingo. Packing is system dependent, and many \PASCAL\ systems fail to implement such files in a sensible way (at least, from the viewpoint of producing good production software). For example, some systems treat all byte-oriented files as text, looking for end-of-line marks and such things. Therefore some system-dependent code is often needed to deal with binary files, even though most of the program in this section of \.{DVItoLN03} is written in standard \PASCAL. @^system dependencies@> One common way to solve the problem is to consider files of |integer| numbers, and to convert an integer in the range $-2^{31}\L x<2^{31}$ to a sequence of four bytes $(a,b,c,d)$ using the following code, which avoids the controversial integer division of negative numbers: $$\vbox{\halign{#\hfil\cr |if x>=0 then a:=x div @'100000000|\cr |else begin x:=(x+@'10000000000)+@'10000000000; a:=x div @'100000000+128;|\cr \quad|end|\cr |x:=x mod @'100000000;|\cr |b:=x div @'200000; x:=x mod @'200000;|\cr |c:=x div @'400; d:=x mod @'400;|\cr}}$$ The four bytes are then kept in a buffer and output one by one. (On 36-bit computers, an additional division by 16 is necessary at the beginning. Another way to separate an integer into four bytes is to use/abuse \PASCAL's variant records, storing an integer and retrieving bytes that are packed in the same place; {\sl caveat implementor!\/}) It is also desirable in some cases to read a hundred or so integers at a time, maintaining a larger buffer. Although plain \.{DVItype} sticks to simple \PASCAL, for reasons of clarity, (even if such simplicity is sometimes unrealistic), we do things rather differently under Vax/VMS, reading each file one disk block at a time. @d VAX_block_length=512 @= @!eight_bits=[byte] 0..255; {unsigned one-byte quantity} @!sixteen_bits=[word] 0..65535; {unsigned two-byte quantity} @!byte_block=packed array [0..VAX_block_length-1] of eight_bits; @!byte_file=packed file of byte_block; @ To prepare these files for input, we |reset| them. An extension of \PASCAL\ is needed in the case of |tfm_file|, since we want to associate it with external files whose names are specified dynamically (i.e., not known at compile time). The following code uses the VAX-\PASCAL\ |open| statement; where |open(f,s)| opens the file associated with file variable |f| using the name provided by string variable |s|; once the file variable has been associated with the correctly named file, the |reset| statement positions the file for reading in the usual manner. In the case of opening the |tfm_file|, we firstly close any earlier one which may already have been opened. If |eof(f)| is true immediately after the file has been opened, we assume that no file named |s| is accessible. @^system dependencies@> To improve the visual appearance of various VAX-\PASCAL\ file manipulation statements, the following definition is used to represent the use of the `named parameter' passing mechanism which instructs that any errors detected shall be ignored, rather than causing a run-time error. @d VAX_continue==@=error@>:=@=continue@> @p procedure open_dvi_file; {prepares to read packed bytes in |dvi_file|} begin reset(dvi_file); dvi_count:=0; cur_block:=0; cur_loc:=0; end; @# procedure open_tfm_file; {prepares to read packed bytes in |tfm_file|} var @!trimmed_name:file_spec; {Holds trimmed version of |cur_name|} @!i,@!j:0..name_length; {Indices into |cur_name|} begin close(tfm_file,VAX_continue); trimmed_name:=''; {Initialize to empty string} i:=name_length; while cur_name[i]=' ' do decr(i); {we know that the name isn't all spaces!} for j:=1 to i do trimmed_name:=trimmed_name+cur_name[j]; open(tfm_file,cur_name,@=readonly@>,VAX_continue); if status(tfm_file)<>0 then abort('Font not loaded, TFM file ',trimmed_name,' can''t be opened'); @:fatal error Font not loaded TFM file}{\qquad\.{TFM file can't be opened}@> reset(tfm_file,VAX_continue); tfm_count:=0; end; @ If you looked carefully at the preceding code, you probably asked, ``What are |cur_loc| and |cur_name|?'' Good question. They're global variables: |cur_loc| is the number of the byte about to be read next from |dvi_file|, and |cur_name| is a string variable that will be set to the current font metric file name before |open_tfm_file| is called. When the \.{DVI} file is first |open|ed, we'll make use of the |@=user_action@>| facility of the VAX |open| procedure to note that actual file length in |dvi_size|. This can then be used as the value of the |dvi_length| function. Since we read a VAX/RMS blockfull of bytes from the \.{DVI} file, variable |dvi_count| allows us to keep track of which byte of the current block is the next to be ``read''. Similarly, |tfm_count| tracks the current byte of the block read from |tfm_file|. |cur_block| tracks which particular block of the whole file is currently in the buffer associated with |dvi_file|; this is used when |random_reading| is used to skip around in the file. No analogous variable is required for the |tfm_file|. @= @!cur_loc:integer; {where we are about to look, in |dvi_file|} @!cur_name:packed array[1..name_length] of char; {external name, with no lower case letters} @!dvi_size:integer; {Actual length of \.{DVI} file, in bytes} @!dvi_count:integer; {number of bytes read from current block of |dvi_file|} @!cur_block:integer; {relative position of current block of |dvi_file|} @!tfm_count:integer; {number of bytes read from current block of |tfm_file|} @ It turns out to be convenient to read four bytes at a time, when we are inputting from \.{TFM} files. The input goes into global variables |b0|, |b1|, |b2|, and |b3|, with |b0| getting the first byte and |b3| the fourth. @= @!b0,@!b1,@!b2,@!b3: eight_bits; {four bytes input at once} @ The |read_tfm_word| procedure sets |b0| through |b3| to the next four bytes in the current \.{TFM} file. @^system dependencies@> @d read_tfm_file(#)==begin if tfm_count=VAX_block_length then begin get(tfm_file,VAX_continue); tfm_count:=0; end; #:=tfm_file^[tfm_count]; incr(tfm_count); end @p procedure read_tfm_word; begin read_tfm_file(b0); read_tfm_file(b1); read_tfm_file(b2); read_tfm_file(b3); end; @ We shall use another set of simple functions to read the next byte or bytes from |dvi_file|. There are seven possibilities, each of which is treated as a separate function in order to minimize the overhead for subroutine calls. @^system dependencies@> Things aren't quite as simple as that, because when processing |dvi| commands to set characters on the page, it may prove necessary to take further |dvi| commands from a sequence read from a virtual font (\\{q.v.}). Therefore, these routines have the option of reading |dvi| bytes from |vf[vf_take]|. When reading |dvi| bytes from the \.{DVI} file, |vftake<0|. (\\{N.B.} |vf_take| and |vf_ptr| will be dealt with \&{much} later.) @d read_dvi_file(#)==if vf_take<0 then begin while dvi_count>=VAX_block_length do begin get(dvi_file,VAX_continue); dvi_count:=dvi_count-VAX_block_length; incr(cur_block); end; #:=dvi_file^[dvi_count]; incr(dvi_count); incr(cur_loc) end else begin {take ``input'' from stored sequence} if vf_take=vf_ptr then abort('no more virtual font bytes'); @:fatal error no more virtual font bytes}{\quad\.{no more virtual font bytes}@> #:=vf[vf_take]; incr(vf_take) end @p function get_byte:integer; {returns the next byte, unsigned} var b:eight_bits; begin if eof(dvi_file) then get_byte:=0 else begin read_dvi_file(b); get_byte:=b; end; end; @# function signed_byte:integer; {returns the next byte, signed} var b:eight_bits; begin read_dvi_file(b); if b<128 then signed_byte:=b @+ else signed_byte:=b-256; end; @# function get_two_bytes:integer; {returns the next two bytes, unsigned} var a,@!b:eight_bits; begin read_dvi_file(a); read_dvi_file(b); get_two_bytes:=a*256+b; end; @# function signed_pair:integer; {returns the next two bytes, signed} var a,@!b:eight_bits; begin read_dvi_file(a); read_dvi_file(b); if a<128 then signed_pair:=a*256+b else signed_pair:=(a-256)*256+b; end; @# function get_three_bytes:integer; {returns the next three bytes, unsigned} var a,@!b,@!c:eight_bits; begin read_dvi_file(a); read_dvi_file(b); read_dvi_file(c); get_three_bytes:=(a*256+b)*256+c; end; @# function signed_trio:integer; {returns the next three bytes, signed} var a,@!b,@!c:eight_bits; begin read_dvi_file(a); read_dvi_file(b); read_dvi_file(c); if a<128 then signed_trio:=(a*256+b)*256+c else signed_trio:=((a-256)*256+b)*256+c; end; @# function signed_quad:integer; {returns the next four bytes, signed} var a,@!b,@!c,@!d:eight_bits; begin read_dvi_file(a); read_dvi_file(b); read_dvi_file(c); read_dvi_file(d); if a<128 then signed_quad:=((a*256+b)*256+c)*256+d else signed_quad:=(((a-256)*256+b)*256+c)*256+d; end; @ Finally we come to the routines that are used only if |random_reading| is |true|. The driver program below needs two such routines: |dvi_length| should compute the total number of bytes in |dvi_file|, possibly also causing |eof(dvi_file)| to be true; and |move_to_byte(n)| should position |dvi_file| so that the next |get_byte| will read byte |n|, starting with |n=0| for the first byte in the file. @^system dependencies@> Such routines are, of course, highly system dependent. They are implemented in this program by \yskip\hang(1) utilizing the optional |user_action| parameter of the |open| procedure, this allows us to determine the length of the file when it is first opened; and \yskip\hang(2), using the VAX-\PASCAL\ |find| procedure to read a specific block of the file, and positioning to the correct byte by resetting the |byte_block| pointer appropriately. \noindent We also make extensive use of VAX-\PASCAL's |varying array| extension, which facilitates the handling of variable length character strings; without them, it would be necessary to keep track of the length of each such string, in the fashion used by \.{DVItype} to store font names. The VAX-\PASCAL\ |find(f,block)| procedure reads (into file variable |f|) the specified record of the file; these are numbered from 1 upwards, and, since we are working with block-oriented files, each record consists of an entire disk block. @f varying==array @d VAX_find_block==@= find@> @p function dvi_length:integer; begin dvi_length:=dvi_size; if dvi_size<0 then abort('Internal error (no dvi_length)'); @:fatal error internal error no dvi}{\qquad\.{(no dvi_length)}@> end; @# procedure move_to_byte(n:integer); begin if n div VAX_block_length <> cur_block then begin cur_block:=n div VAX_block_length; VAX_find_block(dvi_file,cur_block+1,VAX_continue) end; dvi_count:=n mod VAX_block_length; cur_loc:=n; end; @* Reading the font information. \.{DVI} file format does not include information about character widths, since that would tend to make the files a lot longer. But a program that reads a \.{DVI} file is supposed to know the widths of the characters that appear in \\{set\_char} commands. Therefore \.{DVItoLN03} looks at the font metric (\.{TFM}) files for the fonts that are involved. @.TFM {\rm files}@> The character-width data appears also in other files (e.g., in \.{GF} files that specify bit patterns for digitized characters); thus, it is usually possible for \.{DVI} reading programs to get by with accessing only one file per font. \.{DVItype} has a comparatively easy task in this regard, since it needs only a few words of information from each font; the \.{DVItoLN03} program has to go to some pains to deal with complications that arise when a large number of large font files all need to be accessed simultaneously. @ \.{DVItype} needed to know only two things about a given character |c| in a given font |f|: (1)~Is |c| a legal character in~|f|? (2)~If so, what is the width of |c|? It also needed to know the symbolic name of each font, so it could be printed out, and to know the approximate size of inter-word spaces in each font. For \.{DVItoLN03}, some further information is required, such as which character in a downloaded font is to be used to image character |c| in font |f|, and whether this imaging can be performed by the LN03 itself (from stored downloaded fonts) or whether the glyph needs to be imaged by downloading a bitmapped representation each time it occurs, or even replacing it by a solid rule if no suitable file of bitmaps is available. The answers to these questions appear implicitly in the following data structures. The current number of known fonts is |nf|. Each known font has an internal number |f|, where |0<=finvalid_width|. To cut down on page faulting with small working sets, the dimensions of each character are held within the |glyph_map| structure (see following section), so that all information relating to a particular glyph is held in one contiguous section of memory: the original \.{DVItype}, and earlier versions of \.{DVItoLN03}, used to have separate arrays |width[0..max_glyphs]|, etc., which could require references to locations separated by |max_glyphs| integers, making inefficient use of virtual memory. To obviate reloading of fonts with identical names and scaling, a further array |font_map[f]| ordinarily holds the value |f|, but can point to a different set of metrics if such multiple loading has been avoided. Virtual fonts will almost certainly introduce different physical fonts; such fonts are given a relative reference number internally, but we will want to supply a value for the |font_num| array that's unique to each such font. Therefore we keep track of the highest font number allocated by \TeX\ and then use this as a base value when reading virtual fonts. @d invalid_width==@'17777777777 @= @!nf:0..max_fonts; {the number of known fonts} @!font_num:array [0..max_fonts] of integer; {external font numbers} @!font_name:array [0..max_fonts] of 0..name_size; {starting positions of external font names} @!names:array [0..name_size] of ASCII_code; {characters of names} @!font_check_sum:array [0..max_fonts] of integer; {check sums} @!font_scaled_size:array [0..max_fonts] of integer; {scale factors} @!font_design_size:array [0..max_fonts] of integer; {design sizes} @!font_space:array [0..max_fonts] of integer; {boundary between ``small'' and ``large'' spaces} @!font_bc:array [0..max_fonts] of integer; {beginning characters in fonts} @!font_ec:array [0..max_fonts] of integer; {ending characters in fonts} @!font_map:array [0..max_fonts] of 0..max_fonts; {internal cross-reference} @!highest_font:0..max_fonts; @ An array |glyphs| has one element for each of the potential characters in each font. Each element is a record of type |glyph_info| (defined below). When a font definition is first read, during the gathering of font usage statistics in the first pass, the row of this array corresponding to the font being defined is set to indicate that every character is |unused|. As any character in this font is found in a wanted page during that first pass, the usage is noted. Later revisions take place when mapping characters from \TeX's fonts to those dowloaded to the printer. During the second pass through the wanted pages, the character codes in |glyphs| are used to generate the \.{LN3} file. |glyph_map(f)(c)=glyphs[glyph_base[f]+c]| permits us access to information relating to an individual glyph; fields of this record, named |width|, |height| and |depth| are accessible through macro |char_width(f)(c)=glyphs[glyph_base[f]+c].width| and its analogues; this works because |glyph_base[f]| contains the index into |glyphs| at which character~0 appears (or would appear, if |font_bc[f]>0|). |glyph_ptr| is the first unused position of the |glyphs| array. @d char_glyph_end(#)==#] @d glyph_map(#)==glyphs[glyph_base[#]+char_glyph_end @d char_width_end(#)==#].width @d char_height_end(#)==#].height @d char_depth_end(#)==#].depth @d char_width(#)==glyphs[glyph_base[#]+char_width_end @d char_height(#)==glyphs[glyph_base[#]+char_height_end @d char_depth(#)==glyphs[glyph_base[#]+char_depth_end @= @!glyphs: array [0..max_glyphs] of glyph_info; @!glyph_base : array [0..max_fonts] of integer; {index into |glyphs|} @!glyph_ptr : 0..max_glyphs; {index into |glyphs|, |width|, |height| and |depth|} @ The array |glyphs| contains elements whose fields are used as follows: \yskip\centerline{\vbox{\offinterlineskip\hrule \halign{\vrule#&\strut\quad#\hfil\quad&\vrule#&\quad#\hfil\quad&\vrule#\cr height2pt&\omit&&\omit&\cr &\hfil Field Name\quad&&Used for&\cr height2pt&\omit&&\omit&\cr \noalign{\hrule} height2pt&\omit&&\omit&\cr &|width|&&Character width in \.{DVI} units&\cr &|height|&&Character height in \.{DVI} units&\cr &|depth|&&Character depth in \.{DVI} units&\cr &|font_code|&&Number of downloaded LN03 font containing glyph&\cr &|char_code|&&LN03 character code to access glyph&\cr &|loaded|&&Indicates status of glyph, as below&\cr height2pt&\omit&&\omit&\cr }\hrule } } The values that may be assumed by the |loaded| field are as follows: \item{$\bullet$} when a font is first met, all characters are set with the value |unused|; \item{$\bullet$} as the document is scanned, whenever a particular glyph is used this field is set to |wanted|; \item{$\bullet$} other values are set once the wanted pages have been scanned, and the character font downloads created. \noindent Further values for the |loaded| field, and other fields, will be defined for accessing virtual fonts. @== @!download_status = (@!unused,@!wanted@|@@+);@/ @!glyph_info = packed record @!width:integer; {character width, in \.{DVI} units} @!height:integer; {character height, in \.{DVI} units} @!depth:integer; {character depth, in \.{DVI} units} @!char_code : eight_bits; @!loaded : download_status; @!font_code : 0..max_fonts; @@; end; @ @= nf:=0; glyph_ptr:=0; font_name[0]:=0; font_space[0]:=0; font_map[0]:=0; highest_font:=0; @ It is, of course, a simple matter to print the name of a given font. @p procedure print_font(@!f:integer); {|f| is an internal font number} var k:0..name_size; {index into |names|} begin if f=nf then print('UNDEFINED!') @.UNDEFINED@> else begin for k:=font_name[f] to font_name[f+1]-1 do print(xchr[names[k]]); end; end; @ Auxiliary arrays |in_width|, |in_height| and |in_depth| are used to hold the relevant sections of the |tfm_file| as they are input. The global variable |tfm_check_sum| is set to the check sum that appears in the current \.{TFM} file. @= @!in_width:array[0..255] of integer; {\.{TFM} width data in \.{DVI} units} @!in_height:array[0..15] of integer; {\.{TFM} height data in \.{DVI} units} @!in_depth:array[0..15] of integer; {\.{TFM} depth data in \.{DVI} units} @!tfm_check_sum:integer; {check sum found in |tfm_file|} @ Here is a procedure that absorbs the necessary information from a \.{TFM} file, assuming that the file has just been successfully reset so that we are ready to read its first byte. (A complete description of \.{TFM} file format appears in the documentation of \.{TFtoPL} and will not be repeated here.) The procedure does not check the \.{TFM} file for validity, nor does it give explicit information about what is wrong with a \.{TFM} file that proves to be invalid; \.{DVI}-reading programs need not do this, since \.{TFM} files are almost always valid, and since the \.{TFtoPL} utility program has been specifically designed to diagnose \.{TFM} errors. The procedure simply returns |false| if it detects anything amiss in the \.{TFM} data. There is a parameter, |z|, which represents the scaling factor being used to compute the font dimensions; it must be in the range $0; @; @; @; glyph_ptr:=gp; in_TFM:=true; goto 9999; 9997: error('---not loaded, TFM file is bad'); @.TFM file is bad@> 9998: in_TFM:=false; 9999: end; @ @= read_tfm_word; lh:=b2*256+b3; read_tfm_word; font_bc[nf]:=b0*256+b1; font_ec[nf]:=b2*256+b3; if font_ec[nf]; {now |gp=glyph_ptr+font_ec[nf]-font_bc[nf]+1|} read_tfm_word; nw:=b0*256+b1; if (nw=0)or(nw>256) then goto 9997; nh:=b2*256+b3; if (nh=0)or(nh>16) then goto 9997; read_tfm_word; nd:=b0*256+b1; if (nd=0)or(nd>16) then goto 9997; for k:=2 to 3+lh do begin if eof(tfm_file) then goto 9997; read_tfm_word; if k=4 then if b0<128 then tfm_check_sum:=((b0*256+b1)*256+b2)*256+b3 else tfm_check_sum:=(((b0-256)*256+b1)*256+b2)*256+b3; end; @ Whenever the program reads a \.{TFM} file, either during the main processing loop, or when such files are read in whilst mapping virtual fonts to physical ones, the |glyph_map| row corresponding to the physical file is preset to indicate that none of the glyphs are used, and also to signify that, in the absence of further information, the characters are taken from the current font. This little segment of code used to appear in-line within procedure |in_TFM|. We also indicate here that no glyphs from the font as a whole are currently used. Once a glyph is referenced (either directly, or from within a character packet in a virtual font), we'll mark the font as |wanted|, thus indicating (so far as we currently know) the font \\{is} physical; this may be changed later, if the font is found to be virtual. This information is stored in yet another array, |font_type|. @= font_type[nf]:=unused; if glyph_ptr+font_ec[nf]-font_bc[nf]+1 > max_glyphs then capacity_exceeded('too many glyphs: ',glyph_ptr+font_ec[nf]-font_bc[nf]+1:1, ' > ',max_glyphs:1); @:capacity exceeded too many glyphs}{\quad\.{too many glyphs}@> glyph_base[nf]:=glyph_ptr-font_bc[nf]; gp:=glyph_ptr+font_ec[nf]-font_bc[nf]+1; for m:= glyph_ptr to gp-1 do with glyphs[m] do begin loaded := unused; font_code := nf end @ At this point, we've arrived at the start of the |char_info| section of the |tfm_file|; from each 32-bit word, we extract the indices into the |width|, |height| and |depth| sections of the file. @= if gp>0 then for k:=glyph_ptr to gp-1 do with glyphs[k] do begin read_tfm_word; if b0>nw then goto 9997; width:=b0; if (b1 div 16)>nh then goto 9997; height:=b1 div 16; if (b1 mod 16)>nd then goto 9997; depth:=b1 mod 16; end; @ The most important part of |in_TFM| is the width computation, which involves multiplying the relative widths in the \.{TFM} file by the scaling factor in the \.{DVI} file. This fixed-point multiplication must be done with precisely the same accuracy by all \.{DVI}-reading programs, in order to validate the assumptions made by \.{DVI}-writing programs like \TeX82. Let us therefore summarize what needs to be done. Each width in a \.{TFM} file appears as a four-byte quantity called a |fix_word|. A |fix_word| whose respective bytes are $(a,b,c,d)$ represents the number $$x=\left\{\vcenter{\halign{$#$,\hfil\qquad&if $#$\hfil\cr b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=0;\cr -16+b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=255.\cr}}\right.$$ (No other choices of $a$ are allowed, since the magnitude of a \.{TFM} dimension must be less than 16.) We want to multiply this quantity by the integer~|z|, which is known to be less than $2^{27}$. Let $\alpha=16z$. If $|z|<2^{23}$, the individual multiplications $b\cdot z$, $c\cdot z$, $d\cdot z$ cannot overflow; otherwise we will divide |z| by 2, 4, 8, or 16, to obtain a multiplier less than $2^{23}$, and we can compensate for this later. If |z| has thereby been replaced by $|z|^\prime=|z|/2^e$, let $\beta=2^{4-e}$; we shall compute $$\lfloor(b+c\cdot2^{-8}+d\cdot2^{-16})\,z^\prime/\beta\rfloor$$ if $a=0$, or the same quantity minus $\alpha$ if $a=255$. This calculation must be done exactly, for the reasons stated above; the following program does the job in a system-independent way, assuming that arithmetic is exact on numbers less than $2^{31}$ in magnitude. @= @; for k:=0 to nw-1 do begin read_tfm_word; in_width[k]:=(((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta; if b0>0 then if b0<255 then goto 9997 else in_width[k]:=in_width[k]-alpha; end; @ @ Similarly, we store away the sets of distinct character heights and depths; we don't need to convert |z| again. @= for k:=0 to nh-1 do begin read_tfm_word; in_height[k]:=(((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta; if b0>0 then if b0<255 then goto 9997 else in_height[k]:=in_height[k]-alpha; end; for k:=0 to nd-1 do begin read_tfm_word; in_depth[k]:=(((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta; if b0>0 then if b0<255 then goto 9997 else in_depth[k]:=in_depth[k]-alpha; end @ @= begin alpha:=16*z; beta:=16; while z>=@'40000000 do begin z:=z div 2; beta:=beta div 2; end; end @ A \.{DVI}-reading program usually works with font files instead of \.{TFM} files, so \.{DVItype} is atypical in that respect. \.{DVItoLN03} actually checks information in the \.{TFM} file, \&{and} also reads an appropriate \.{PXL} file. Font files should, however, contain exactly the same character width data that is found in the corresponding \.{TFM}s; check sums are used to help ensure this. In addition, font files usually also contain the widths of characters in pixels, since the device-independent character widths of \.{TFM} files are generally not perfect multiples of pixels. The |pixel_width| field of the |glyph_map| contains this information; when |glyphs[k].width| is the device-independent width of some character in \.{DVI} units, |glyphs[k].pixel_width| is the corresponding width of that character in an actual font. The macro |char_pixel_width| is set up to be analogous to |char_width|. Similarly, |pixel_height| and |pixel_depth| are available; these are used for replacing the glyph for a character for which the required bitmaps are unavailable by the appropriately-sized rule. @d pixel_width_end(#)==#].pixel_width @d pixel_height_end(#)==#].pixel_height @d pixel_depth_end(#)==#].pixel_depth @d char_pixel_width(#)==glyphs[glyph_base[#]+pixel_width_end @d char_pixel_height(#)==glyphs[glyph_base[#]+pixel_height_end @d char_pixel_depth(#)==glyphs[glyph_base[#]+pixel_depth_end @= @!pixel_width, @!pixel_height, @!pixel_depth : integer; @ The following variables are used to hold the conversion factors for translating device-independent units into device pixels. @= @!conv:real; {converts \.{DVI} units to pixels} @!true_conv:real; {converts unmagnified \.{DVI} units to pixels} @!numerator,@!denominator:integer; {stated conversion ratio} @!mag:integer; {magnification factor times 1000} @ The following code computes pixel widths by simply rounding the \.{TFM} widths to the nearest integer number of pixels, based on the conversion factor |conv| that converts \.{DVI} units to pixels. However, such a simple formula will not be valid for all fonts, and it will often give results that are off by $\pm1$ when a low-resolution font has been carefully hand-fitted. For example, a font designer often wants to make the letter `m' a pixel wider or narrower in order to make the font appear more consistent. \.{DVI}-to-printer programs should therefore input the correct pixel width information from font files whenever there is a chance that it may differ. A warning message may also be desirable in the case that at least one character is found whose pixel width differs from |conv*width| by more than a full pixel. @^system dependencies@> (And similarly for the height and depth values.) @d pixel_round(#)==round(conv*(#)) @= if in_width[0]<>0 then goto 9997; {the first width should be zero} if gp>0 then for k:=glyph_ptr to gp-1 do with glyphs[k] do begin if width=0 then begin width:=invalid_width; pixel_width:=0; end else begin width:=in_width[width]; pixel_width:=pixel_round(width); end; height:=in_height[height]; pixel_height:=pixel_round(height); depth:=in_depth[depth]; pixel_depth:=pixel_round(depth) end @* Output to the terminal. Whilst DVItoLN03 is processing \.{DVI} files, it reports its progress by outputting various messages to the terminal: in particular, when processing each page, it reports the page number, enclosed in `\.{[]}' brackets. Under Vax/VMS, output to the terminal is not normally visible until |writeln| is called, since the operating system buffers output until a line is complete. However, this would mean, for example, that output would only appear as each line was filled. We therefore arrange to |open| the |output| file (synonymous with |term_out|) specifying null carriage control; output may then be made to appear by calling |write_ln|, but this will not give the usual accompanying newline. To start printing on a newline, the character sequence |crlf| must be output explicitly. The |term_out| file is used for terminal output. @^system dependencies@> The number of characters already output to a terminal line is counted in |term_offset| so that a suitable line break may be chosen when the line becomes ``full'. Messages giving page number strings being processed are contained in a variable of type |file_spec|, because some messages may contain VAX/VMS file names. @d term_out==output {the terminal, considered as an output file} @= @!term_offset : 0..line_length; @ When each page is processed, the page number (as generated by \TeX) is written out to the user's terminal, enclosed in `\.{[]}' brackets. The procedure |monitor| ensures that such output does not overlap the right-margin. It does this by counting the characters already output to the line; up to |line_length| characters will be output. We use here the VAX-\PASCAL-specific function |length| which returns the number of characters in a |varying of char| which are actually occupied. @d VAX_length==@= length@> @p procedure monitor(s : file_spec); begin if VAX_length(s)+2>line_length-term_offset then {Line is fullish!} begin write_ln(term_out,crlf); term_offset:=0; end; if (term_offset>0) and (s[1]='[') then {Space before a |'['|} begin write(term_out,' '); incr(term_offset); end; write_ln(term_out,s); term_offset:=term_offset+VAX_length(s) end; @*Displaying error messages. Later we shall make extensive use of the following definitions, particularly when scanning the \.{DVI} file. However, they are defined up here so that warnings may be issued by the code that follows. Normally (when invoked from within |do_page|) the variable |a| will contain the byte number within the \.{DVI} file at which a problem was encountered; however, to give |show| something to work with when these macros are used elsewhere, we also define a constant of that name, which will thus always be within scope. @d show(#)==begin if a<0 then print_ln(#) else print_ln('[',a:1,'] ',#); if term_offset>0 then write_ln(crlf); write_ln(term_out,#,crlf); term_offset:=0 end @d error(#)==begin history := error_given; show('Error: ',#) @.Error:@> end @d warning(#)==begin if history = spotless then history := warning_given; show('Warning: ',#) @.Warning:@> end @== a = -1; @* Optional modes of output. \.{DVItoLN03} will print different quantities of information based on some options that the user must specify: the typeout can be confined to a restricted subset of the pages by specifying the desired starting page and the maximum number of pages. Further options permit the selection of a different position for the origin on the page, which by default is set to the ``standard'' of one inch from the top and left edges of the paper, and selection of ``portrait'' or ``landscape'' orientation for the output on the printed page. All options are selected through the use of VAX/VMS command qualifiers, but procedure |dialog| could be adapted to carry out an interactive dialogue with a user on systems without VMS' excellent facilities for defining commands. The starting page is specified by giving a sequence of 1 to 10 numbers or asterisks separated by dots. For example, the specification `\.{1.*.-5}' can be used to refer to a page output by \TeX\ when $\.{\\count0}=1$ and $\.{\\count2}=-5$. (Recall that |bop| commands in a \.{DVI} file are followed by ten `count' values.) An asterisk matches any number, so the `\.*' in `\.{1.*.-5}' means that \.{\\count1} is ignored when specifying the first page. If several pages match the given specification, \.{DVItoLN03} will begin with the earliest such page in the file. The default specification `\.*' (which matches all pages) therefore denotes the page at the beginning of the file. This version of \.{DVItoLN03} uses the VMS Command Language Interpreter utility routines to determine whether command line qualifiers are present, and to fetch their values, which are then stored in the following variables. @^Command Language Interpreter@> @^system dependencies@> The default options that hold unless over-ridden by command line qualifiers are: \yskip\hang$\bullet$ \.{/starting\_page}=\.{*} \yskip\hang$\bullet$ \.{/number\_of\_pages}=\.{1000000} \yskip\hang$\bullet$ \.{/top\_margin} = \.{300px} \yskip\hang$\bullet$ \.{/left\_margin}=\.{300px} \yskip\hang$\bullet$ \.{/orientation}=\.{portrait} @= @!left_marg,@!top_marg : integer; @!orientation,@!new_orient,@!cur_orient : page_orientation; @!h_fuzz,@!v_fuzz:integer; {controls how pedantic program is} @!page_len,@!page_wid,@!y_min,@!x_min : integer; @!max_pages:integer; {at most this many |bop..eop| pages will be printed} @!resolution:real; {pixels per inch} @!new_mag:integer; {if positive, overrides the postamble's magnification} @ This type allows us to keep track of whether characters are currently being imaged in landscape or portrait orientation. @= @!page_orientation = (@!portrait,@!landscape); @ The starting page specification is recorded in two global arrays called |start_count| and |start_there|. For example, `\.{1.*.-5}' is represented by |start_there[0]=true|, |start_count[0]=1|, |start_there[1]=false|, |start_there[2]=true|, |start_count[2]=-5|. We also set |start_vals=2|, to indicate that count 2 was the last one mentioned. The other values of |start_count| and |start_there| are not important, in this example. @= @!start_count:array[0..9] of integer; {count values to select starting page} @!start_there:array[0..9] of boolean; {is the |start_count| value relevant?} @!start_vals:0..9; {the last count considered significant} @!count:array[0..9] of integer; {the count values on the current page} @ @= max_pages:=1000000; start_vals:=0; start_there[0]:=false; top_marg:=300; left_marg:=300; orientation:=portrait; @ Here is a simple subroutine that tests if the current page might be the starting page. @p function start_match:boolean; {does |count| match the starting spec?} var k:0..9; {loop index} @!match:boolean; {does everything match so far?} begin match:=true; for k:=0 to start_vals do if start_there[k]and(start_count[k]<>count[k]) then match:=false; start_match:=match; end; @ The global variable |buf_ptr| is used while scanning each line of input; it points to the first unread character in |buffer|, into which the command line interpreter places strings by interaction with the CLI interface procedures. @= @!buffer: varying[terminal_line_length] of char; @!buf_ptr:0..terminal_line_length; {the number of characters read} @ The compiler needs to know about the ``foreign'' routines used for access to the Command Line Interpreter. VAX-\PASCAL\ permits the ``declaration'' of external procedures, where the declaration consists of the procedure heading alone, followed by the reserved word |extern|, in a manner analogous to standard \PASCAL's |forward| directive. @^system dependencies@> We also require the following special definitions, types, variables and procedures to be able to get information from the command line interpreter. The macro |VAX_string_or_empty| is used so as not to confuse W{\mc EAVE}'s formatting of these foreign procedure interfaces. @d VAX_volatile==@=[volatile]@> @d VAX_immed==@=%immed @> @d VAX_external==@=[external]@> @d VAX_descr==@=%descr @> @d VAX_ref==@=%ref @> @d VAX_cli_present==@= cli$present@> @d VAX_cli_get_value==@= cli$get_value@> @d VAX_cli_negated==@"000381F8 @d VAX_lib_find_file==@= lib$find_file@> @# @f VAX_volatile==do_nothing @f VAX_descr==var @f VAX_ref==var @f VAX_immed==integer @f VAX_external==var @# @d VAX_string_or_empty(#)==VAX_volatile varying[@!#] of char := VAX_immed 0 @f extern==forward @= VAX_external @+function VAX_cli_present( VAX_descr @!entity:VAX_string_or_empty(s1_len)) : integer; extern;@#@t\2@> VAX_external @+function VAX_cli_get_value( VAX_descr @!entity:VAX_string_or_empty(s1_len); VAX_descr @!returns:VAX_string_or_empty(s2_len); VAX_ref @!retlen:VAX_volatile sixteen_bits := VAX_immed 0):integer; extern;@#@t\2@> VAX_external @+function VAX_lib_find_file( VAX_descr @!part_spec:VAX_volatile file_spec; VAX_descr @!result_spec:VAX_volatile file_spec; VAX_ref @!context:VAX_volatile integer; VAX_descr @!def_spec:VAX_volatile file_spec := VAX_immed 0; VAX_descr @!related_spec:VAX_volatile file_spec := VAX_immed 0; VAX_ref @!stv_addr:VAX_volatile integer := VAX_immed 0; VAX_ref @!user_flags:VAX_volatile integer := VAX_immed 0): integer; extern;@#@t\2@> @ The following function |get_value| inputs the string corresponding to a command-line qualifier and converts it to its numerical value. Calls of the |VAX_cli_get_value(s,a)| return to the buffer parameter |a| the value placed by the user on the command qualifier whose name is given by the string |s|; being a system service type function, it also reports its success (or failure) as its result. The other system service used is |VAX_cli_present(s)| which returns an odd (success) status indication if the qualifier whose name is given by the string |s| \&{is} present in the command line. One VAX-\PASCAL\ extension permits string variables to be used in place of files for reading and writing values. The |VAX_string_read(b,i,...)| procedure reads values into variables |i,...| from the characters stored in the string variable |b|. The |VAX_string_status| function returns a value in a manner entirely analogous to VAX-\PASCAL's external I/O |status| function, but relating to the success of the previous string read. @d VAX_string_read==@= readv@> @d VAX_string_status==@= statusv@> @p function get_value(qualifier: varying [$u1] of char):integer; var @!i,@!stat,@!len : integer; {local workspace} buffer: varying [terminal_line_length] of char; {buffer for qualifier string} begin if odd(VAX_cli_present(qualifier)) then begin stat:=VAX_cli_get_value(qualifier,buffer); buffer:=buffer+' '; {append a space to ensure end can be found} VAX_string_read(buffer,i,VAX_continue); if VAX_string_status > 0 then abort('Bad /',qualifier,' value! (=',buffer,')') @:fatal error Bad qualifier}{\quad \.{Bad /}\meta{qualifier} \.{value}@> else get_value:=i end else get_value:=-1 end; @ Here is a routine that scans a (possibly signed) integer and computes the decimal value. If no decimal integer starts at |buf_ptr|, the value 0 is returned. The integer should be less than $2^{31}$ in absolute value. @p function get_integer:integer; var x:integer; {accumulates the value} @!negative:boolean; {should the value be negated?} begin if buffer[buf_ptr]='-' then begin negative:=true; incr(buf_ptr); end else negative:=false; x:=0; while (buffer[buf_ptr]>='0')and(buffer[buf_ptr]<='9') do begin x:=10*x+xord[buffer[buf_ptr]]-"0"; incr(buf_ptr); end; if negative then get_integer:=-x @+ else get_integer:=x; end; @ One feature of the new DEClaser~2200 (otherwise known as the LN06) is a capability of selecting from which paper tray a sheet shall be fed; the printer has two separate trays, which can be loaded with different sizes or colours of paper: a popular use is to have letterhead paper loaded in one tray, and plain paper for continuation sheets in the other. Both on this printer, and on the DEClaser~2100 (LN05), any of five different types of paper tray can be inserted into (respectively, either, or the only) tray slot. Paper cassettes are available to hold three different American sizes of paper (letter, legal or executive), the ISO standard A4 size used in Europe, or a stack of up to 15 envelopes. The DEClaser~2200 can be fitted with a separate envelope feeder (in addition to the two paper trays), which can hold up to 40~envelopes. This envelope feeder is supported by the program, but we don't (yet!) support one further option of the LN06 which is a large capacity input tray that can hold 1000 sheets. In addition, both the 2100 and 2200 models can await manual feeding of sheets. Selection of paper source is not supported on the LN03, of course. \noindent Here are the values that may be listed with the \.{/feed\_tray} qualifier: \yskip\hang\.{ALL=}\meta{tray type} All sheets will be fed from the specified tray. \yskip\hang\.{FIRST=}\meta{tray type} Feed the first sheet from the specified tray; subsequent sheets will be fed from the \.{DEFAULT\_TRAY}, unless the next qualifier is used: \yskip\hang\.{REST=}\meta{tray type} Feed the second and subsequent sheets from the specified tray; the first sheet will be fed from the \.{DEFAULT\_TRAY}, unless the \.{FIRST} tray has also been specified. \noindent\meta{Tray types} can take the following values: \yskip\hang\.{DEFAULT\_TRAY} Use whichever tray is set as the default by the printer's initialization sequence. \yskip\hang\.{TOP\_TRAY} (This is the only tray on the DEClaser~2100.) \yskip\hang\.{BOTTOM\_TRAY} \yskip\hang\.{ENVELOPE\_TRAY} Feed envelopes, from the large-capacity envelope tray option on the DEClaser~2200. \yskip\hang\.{MANUAL\_FEED} Wait for the operator to insert each sheet manually. \noindent The following values look as though they could be defined as a \PASCAL\ enumeration type, but we need to ensure a particular mapping to ordinal values, so we engage in a Knuthian trick here. @^Knuth, D.~E.@> @:TeX the Program}{{\sl\TeX\ the Program}@> The values correspond to the $P_s$ parameter of the |DECASTC| (Automatic Sheet-feeder Tray Control) control sequence. @d default_tray=0 @d top_tray=1 @d bottom_tray=2 @d envelope_feeder=3 @d manual_feed=99 @= @!tray_type=default_tray..manual_feed; @ Here is a procedure which converts the \.{/feed\_tray} qualifier to the appropriate numeric value. @p procedure @!read_tray(option:file_spec; var result:tray_type); begin result:=default_tray; if odd(VAX_cli_present(option+'.TOP_TRAY')) then result:=top_tray else if odd(VAX_cli_present(option+'.BOTTOM_TRAY')) then result:=bottom_tray else if odd(VAX_cli_present(option+'.ENVELOPE_TRAY')) then result:=envelope_feeder else if odd(VAX_cli_present(option+'.MANUAL_FEED')) then result:=manual_feed; if device_type=ln03 then result:=default_tray end; @ The selected options are put into global variables by the |dialog| procedure, which is called just as \.{DVItoLN03} begins. As already explained, the |term_out| file is opened during the initialization phase, with \.{carriage\_control}=\.{none}. Having announced the program's name, the command line interpreter library procedures are invoked to retrieve the qualifiers and file name from the command line. @p procedure dialog; var k:integer; {loop variable} begin rewrite(term_out); {prepare the terminal for output} write_ln(term_out,banner,crlf);@/ @; @; @; @; @; @; @; @; @; @; @; end; @ @= start_vals:=0; start_there[0]:=false; if odd(VAX_cli_present('STARTING_PAGE')) then begin k:=VAX_cli_get_value('STARTING_PAGE',buffer); buffer:=buffer+' '; if (buffer[1]<>' ') then begin buf_ptr:= 1; k:=0; if buffer[1]<>' ' then repeat if buffer[buf_ptr]='*' then begin start_there[k]:=false; incr(buf_ptr); end else begin start_there[k]:=true; start_count[k]:=get_integer; end; if (k<9)and(buffer[buf_ptr]='.') then begin incr(k); incr(buf_ptr); end else start_vals:=k { anything ill-formed we just stop } until start_vals=k end end @ @= max_pages:=1000000; max_pages:=get_value('NUMBER_OF_PAGES'); if max_pages<=0 then max_pages:=1000000; @ @= resolution:=300.0; { this is the LN03's resolution } @ @= new_mag:=0; { no provision for magnified LN03 output yet } @ The value associated with the \.{/left\UL margin} and \.{/top\UL margin} qualifiers consists of a number and a specifier for a \TeX\ \meta{physical unit}. The values provided are translated into pixels; this uses the same mechanism as is used for interpreting similar dimensions in \.{\BS special} commands, so the qualifier is copied into the |spec_par| buffer used for that interpretation. The characters have to be converted from external representation into |ASCII_code| for this. @= if odd(VAX_cli_present('LEFT_MARGIN')) then begin k:=VAX_cli_get_value('LEFT_MARGIN',buffer); buffer:=buffer+' '; @; if rd_dimension(left_marg) then begin print_ln(' --- assuming pixel units'); write_ln(' --- assuming pixel units',crlf); term_offset:=0 end @.assuming pixel units@> end else left_marg := -1; if left_marg<0 then left_marg:=300; if odd(VAX_cli_present('TOP_MARGIN')) then begin k:=VAX_cli_get_value('TOP_MARGIN',buffer); buffer:=buffer+' '; @; if rd_dimension(top_marg) then begin print_ln(' --- assuming pixel units'); write_ln(' --- assuming pixel units',crlf); term_offset:=0 end @.assuming pixel units@> end else top_marg := -1; if top_marg<0 then top_marg:=300; @ Here's how we copy the |char| values from the |buffer| (whence they have been read from the command-line qualifier) into the |spec_par| buffer, converting to |ASCII_code| in the process. @= buf_ptr:=1; spec_len:=1; while buf_ptr<=buffer.length do begin spec_par[spec_len]:=xord[buffer[buf_ptr]]; incr(spec_len); incr(buf_ptr) end; spec_ptr:=1 @ When the paper orientation is selected, we may define the |page_len| and |page_wid| in terms of the physical limits of the paper; these sizes have been defined at the start of this \.{web}. @= if odd(VAX_cli_present('ORIENTATION')) then begin if odd(VAX_cli_present('PORTRAIT')) then orientation:=portrait else if odd(VAX_cli_present('LANDSCAPE')) then orientation:=landscape else warning('/ORIENTATION must be "PORTRAIT" or "LANDSCAPE"; assuming "PORTRAIT"') @:Warning: orientation must be}{\quad \.{/ORIENTATION must be ...}@> end; if orientation=landscape then begin page_len:=paper_wid+page_x_min; page_wid:=paper_ht+y_land_min; y_min:=page_x_min; x_min:=y_land_min end else begin page_len:=paper_ht+y_port_min; page_wid:=paper_wid+page_x_min; y_min:=y_port_min; x_min:=page_x_min end; @ The value associated with the \.{/hfuzz} and \.{/vfuzz} qualifiers consists of a number and a specifier for a \TeX\ \meta{physical unit}. The values provided are translated into scaled points, entirely analogously with the translation of the value on the \.{/top\_margin} qualifier into pixels. These ``fuzz'' values are used to control how pedantic the program is when it discovers characters which extend beyond the right-hand margin as reported (in the \.{DVI} file) by \TeX. The default values for these qualifiers is \.{100sp}, which implies that ``errors'' smaller than this (it's a very small quantity, approximately equal to the wavelength of visible light) will be ignored. Normally \TeX\ wouldn't dream of exceeding the margin (well, not without issuing an \.{overfull \\hbox} warning), but with some macro packages it isn't so fussy. For example, the output of W{\sc EAVE}, when formatting the \PASCAL\ part of a program, can extend beyond the margin by up to \.{10pt}. By selecting a larger value of ``fuzz'', spurious warning messages from DVItoLN03 can be suppressed. @= if odd(VAX_cli_present('HFUZZ')) then begin k:=VAX_cli_get_value('HFUZZ',buffer); buffer:=buffer+' '; @; if rd_scaled_pt(h_fuzz) then begin print_ln(' --- assuming scaled points'); @.assuming scaled points@> write_ln(' --- assuming scaled points',crlf); term_offset:=0 end end else h_fuzz := 0; decr(h_fuzz); {when used, we watch for |h-max_h| exceeding this value} if h_fuzz<0 then h_fuzz:=99; {corresponds to default of \.{100sp}} if odd(VAX_cli_present('VFUZZ')) then begin k:=VAX_cli_get_value('VFUZZ',buffer); buffer:=buffer+' '; @; if rd_scaled_pt(v_fuzz) then begin print_ln(' --- assuming scaled points'); @.assuming scaled points@> write_ln(' --- assuming scaled points',crlf); term_offset:=0 end end else v_fuzz := 0; decr(v_fuzz); if v_fuzz<0 then v_fuzz:=99 @ The program can support the traditional LN03 (and LN03-plus) printers (but not the LN03R ScriptPrinter, for which a DVI to PostScript program @^PostScript@> is required), the LN05 (DEClaser~2100) and the LN06 (DEClaser~2200). This variable reminds of which mode is in use: @= @!device_type:(@!ln03,@!ln05,@!ln06); @ This section determines which value was used with the \.{/DEVICE\_TYPE} qualifier. It is an error (something's gone wrong with the command definition file) if this qualifier doesn't have a value. @= device_type:=ln03; if not odd(VAX_cli_present('DEVICE_TYPE')) then abort('No device type specified') @:fatal error No device type specified}{\quad\.{No device type specified}@> else if odd(VAX_cli_present('DEVICE_TYPE.LN03')) then device_type:=ln03 else if odd(VAX_cli_present('DEVICE_TYPE.LN05')) then device_type:=ln05 else if odd(VAX_cli_present('DEVICE_TYPE.LN06')) then device_type:=ln06 @ The new DEClaser printers (models 2100 and 2200, otherwise known as the LN05 and LN06) are capable of a number of extra ``tricks'', compared with the original LN03; in particular, the DEClaser~2200 can print on both sides of the paper (``duplex'' printing). The \.{/print\_mode} qualifier of the command line interface allows us to select a number of different modes of operation. \noindent The possible modes are as follows: \yskip\hang\.{SIMPLEX} Print on one side of paper only; this is the default, and the only method compatible with the DEClaser~2100 (LN05) and the original LN03 (but see below). \yskip\hang\.{DUPLEX} Print on both sides of the paper; which page gets printed on the reverse of such a sheet is further controlled by the \.{/duplex\_by\_page\_numbers} qualifier. \yskip\hang\.{MASTER} Can be used in conjunction with the \.{DUPLEX} value to produce a master set of sheets for photocopying or other production process. The program behaves as if the printer were operating in duplex mode, but actually prints on only one side of the paper, and ``prints'' a blank page if it would have left the \\{verso} page blank. This option can be used with the DEClaser~2100 (LN05), and with the traditional LN03, even though those printers don't support genuine duplexing. \yskip\hang\.{TUMBLED} Can be used in conjunction with genuine duplex printing only (and thus is restricted to the DEClaser~2200). Causes \\{verso} (left-hand) pages to be printed upside-down, therefore making the final document suitable for binding along the short edge (long edge in landscape mode) to make a flip-chart style document. \yskip\hang\.{NORMAL} The opposite of \.{TUMBLED}. This is the default. \noindent Once again, we define an `enumeration' type with known values: @d default_print_mode=0 @d true_simplex_normal=1 @d true_simplex_tumbled=2 @d true_duplex_normal=3 @d true_duplex_tumbled=4 @d duplex_master_normal=5 @d duplex_master_tumbled=6 @d ln03_master=7 {we have to handle this by software, since no printer support} @= @!print_mode:default_print_mode..ln03_master; @ Here's where we analyse the \.{/print\_mode} qualifier, and set |print_mode| accordingly: @= @; print_mode:=default_print_mode; if odd(VAX_cli_present('PRINT_MODE')) then if odd(VAX_cli_present('PRINT_MODE.DUPLEX')) then if odd(VAX_cli_present('PRINT_MODE.MASTER')) then if odd(VAX_cli_present('PRINT_MODE.TUMBLED')) then print_mode:=duplex_master_tumbled else print_mode:=duplex_master_normal else if odd(VAX_cli_present('PRINT_MODE.TUMBLED')) then print_mode:=true_duplex_tumbled else print_mode:=true_duplex_normal else if odd(VAX_cli_present('PRINT_MODE.TUMBLED')) then print_mode:=true_simplex_tumbled else print_mode:=true_simplex_normal; if device_type=ln03 then if print_mode>=duplex_master_normal then print_mode:=ln03_master else print_mode:=default_print_mode @ Now we decide which tray shall be used for the first and subsequent sheets @= if odd(VAX_cli_present('FEED_TRAY')) then if odd(VAX_cli_present('FEED_TRAY.ALL')) then begin read_tray('FEED_TRAY.ALL',first_tray); following_tray:=first_tray end else begin read_tray('FEED_TRAY.FIRST',first_tray); read_tray('FEED_TRAY.REST',following_tray) end @ Here are the variables needed to save the print tray(s) selection; also one which determines in which manner duplexing shall take place @= @!first_tray,@!following_tray:tray_type; @!duplex_by_page_numbers:boolean; @ When we are printing on both sides of the paper, or simulating that operation with the \.{MASTER} option on the \.{/PRINT\_MODE} qualifier, we will usually elect to print \\{verso} pages when the page number (held in |count[0]|) is even, and \\{recto} pages when it is odd (or zero). If two odd or two even pages appear in succession, the other side of the sheet will be left blank (or a blank page ejected in \.{MASTER} mode). This is the default mode of operation, when the \.{/DUPLEX\_BY\_PAGE\_NUMBERS} qualifier is present; if this qualifier is negated, then the \\{verso} side of the sheet will be covered with whatever page follows that printed on the \\{recto} side, regardless of the page number; no blank pages appear, unless explicitly generated by \TeX. @= duplex_by_page_numbers:=odd(VAX_cli_present('DUPLEX_BY_PAGE_NUMBERS')) @ After the dialogue is over, we print the options so that the user can see what \.{DVItoLN03} thought was specified. These macros allow us to show which |print_mode| and feed tray have been selected: @d show_print_mode==begin print('Print mode is '); case print_mode of true_simplex_normal,true_duplex_normal,duplex_master_normal: print('normal '); true_simplex_tumbled,true_duplex_tumbled,duplex_master_tumbled: print('tumbled ') endcases; if print_mode=ln03_master then print('ln03 '); if print_mode>=duplex_master_normal then print('master '); if print_mode>=true_duplex_normal then print_ln('duplex') else print_ln('simplex') end @# @d show_tray(#)==case # of default_tray: print_ln('fed from the default tray'); top_tray: print_ln('fed from the top tray'); bottom_tray: print_ln('fed from the bottom tray'); envelope_feeder: print_ln('taken from the envelope feeder'); manual_feed: print_ln('manually fed') endcases @= print_ln('Options selected:'); @.Options selected@> print(' Starting page = '); for k:=0 to start_vals do begin if start_there[k] then print(start_count[k]:1) else print('*'); if k0 then print_ln(' New magnification factor = ',new_mag/1000:8:3); if h_fuzz>99 then print_ln(' Horizontal overruns less than ', (h_fuzz+1)/65536.0:1:2,'pt will be ignored'); if v_fuzz>99 then print_ln(' Depth overruns less than ', (v_fuzz+1)/65536.0:1:2,'pt will be ignored'); if (first_tray<>default_tray) or (following_tray<>default_tray) then if first_tray<>following_tray then begin print('First sheet is '); show_tray(first_tray); print('Following sheets are '); show_tray(following_tray) end else begin print('Paper is '); show_tray(first_tray) end; if print_mode<>default_print_mode then show_print_mode; if print_mode>=true_duplex_normal then begin print('Duplexing is '); if not duplex_by_page_numbers then print('not '); print_ln('controlled by page numbers') end @* Defining fonts. \.{DVItoLN03} reads the postamble first and loads all of the fonts defined there; then it processes the pages. Therefore a \\{fnt\_def} command should match a previous definition if and only if the \\{fnt\_def} being processed is not in the postamble. Since the ``wanted'' pages are read twice, {\it viz.} whilst gathering the font usage statistics and to perform the actual type setting, font definitions in the \.{DVI} file will actually be met {\bf three} times. The original logic of \.{DVItype} using the global variable |in_postamble| covers the first two occasions during the initial scan and the reading of the postamble. The variable |substitution_pass| prevents us from getting a ``font already defined'' message when each definition is met for the third time. @= @!substitution_pass:boolean; {are we performing the actual type setting?} @!in_postamble:boolean; {are we reading the postamble?} @ @= substitution_pass:=false; in_postamble:=false; @ Under the VAX/VMS operating system, no distinction is made between fonts names \.{cmr10} and \.{CMR10}, for example. However, if \TeX\ encounters a font name not written in the same case as those of the preloaded format in use, it will think it is a distinct font (and under some other operating systems, of course, the case of the name could be significant). The following function allows us to fold upper-case letters in a font name to lower-case, and thus avoid such infelicitous duplication. @p function lower(@!ch:ASCII_code): ASCII_code; begin if (ch>="A") and (ch<="Z") then lower:=ch+"a"-"A" else lower:=ch end; @ The following subroutine does the necessary things when a \\{fnt\_def} command is being processed. @p procedure define_font(@!e:integer); {|e| is an external font number} var @!f:0..max_fonts; {local index into font data structures} @!p:integer; {length of the area/directory spec} @!newly_defined: boolean; {first time font has been defined} @!n:integer; {length of the font name proper} @!c,@!q,@!d:integer; {check sum, scaled size, and design size} @!r:0..name_length; {index into |cur_name|} @!j,@!k:0..name_size; {indices into |names|} @!mismatch:boolean; {do names disagree?} begin if nf=max_fonts then capacity_exceeded('too many fonts (max fonts=', max_fonts:1,')'); @:capacity exceeded too many fonts}{\quad \.{too many fonts}@> if e > highest_font then highest_font := e; font_num[nf]:=e; f:=0; font_map[nf]:=nf; while font_num[f]<>e do incr(f); @; if in_postamble then begin if (f end else begin if f=nf then warning('---this font wasn''t loaded before!'); @:Warning: this font wasn't loaded before}{\quad \.{this font wasn't loaded before}@> end; newly_defined:=f=nf;@/ @; if f=nf then @ else begin @; if newly_defined and (font_map[nf] incr(nf) {the ``new'' font is officially present, but remapped} end end; end; @ When we reach the point at which it might be found necessary to read the \TeX\ font metrics from the \.{TFM} file, we can save a little effort if we've already loaded a font with identical name and at the same scaled size. In fact, this is essential with such virtual fonts as \.{recurse}, because otherwise we might go on loading the same \.{TFM} file \\{ad infinitum}! Variable |mismatch| is misused in the |while| loop such that the loop will exit if the name of font |f| is identical to that of font |nf|. If the names differ, the loop continues until |f=nf|. @= f:=font_map[f]; {it might already map elsewhere!} if f=nf then {it's got to \&{be} a new font, of course!} begin f:=0; mismatch:=true; while (f; if mismatch then incr(f) end; {|while f|} if f= begin if font_check_sum[f]<>c then warning('---checksum doesn''t match previous definition!'); @:Warning: checksum doesn't match}{\quad\.{checksum doesn't match}@> if font_scaled_size[f]<>q then warning('---scaled size doesn''t match previous definition!'); @:Warning: scaled size doesn't match}{\quad\.{scaled size doesn't match}@> if font_design_size[f]<>d then warning('---design size doesn''t match previous definition!'); @:Warning: design size doesn't match}{\quad\.{design size doesn't match}@> @; if mismatch then warning('---font name doesn''t match previous definition!'); @:Warning: font name doesn't match}{\quad\.{font name doesn't match}@> end @ This little chunk of code compares the font names stored in |names[font_name[f]..font_name[f+1]-1]| and |names[font_name[nf]..font_name[nf+1]-1]|; if any character differs, or the lengths of the names differ, then |mismatch| will be set. @= begin j:=font_name[f]; k:=font_name[nf]; mismatch:=false; while jnames[k] then mismatch:=true; incr(j); incr(k); end; {|while j|} if k<>font_name[nf+1] then mismatch:=true end @ @= c:=signed_quad; font_check_sum[nf]:=c;@/ q:=signed_quad; font_scaled_size[nf]:=q;@/ d:=signed_quad; font_design_size[nf]:=d;@/ p:=get_byte; n:=get_byte; if font_name[nf]+n+p>name_size then capacity_exceeded('too many names (name size=',name_size:1,')'); @:capacity exceeded too many names}{\quad\.{too many names}@> font_name[nf+1]:=font_name[nf]+n+p; if not substitution_pass then print('Font ',e:1,': '); if n+p=0 then print('null font name!') @.null font name@> else for k:=font_name[nf] to font_name[nf+1]-1 do names[k]:=lower(get_byte); incr(nf); if not substitution_pass then print_font(nf-1); decr(nf) @ @= begin @; open_tfm_file; if (q<=0)or(q>=@'1000000000) then abort('Font not loaded, bad scale (',q:1,')!') @:fatal error Font not loaded bad scale}{\qquad\.{bad scale}@> else if (d<=0)or(d>=@'1000000000) then abort('Font not loaded, bad design size (',d:1,')!') @:fatal error Font not loaded bad design size}{\qquad\.{bad design size}@> else if in_TFM(q) then @; end @ Strictly speaking, we should derive the width of a `thin space' from |param[6]| in the |tfm_file|, but this program (in common with such paragons of virtue as \.{DVItype}!) doesn't bother to read that far in the file; so we assume that \.{1em} in the font is equivalent to the design size, and knowing that an \.{em} in the Computer Modern fonts is \.{18u\#} wide, take one-sixth of this to represent the `thin space'. @= begin font_space[nf]:=q div 6; {this is a 3-unit ``thin space''} if (c<>0)and(tfm_check_sum<>0)and(c<>tfm_check_sum) then warning('checksum doesn''t match; DVI=',c:1,' vs. TFM=',tfm_check_sum:1); @:Warning: checksum doesn\'t match}{\quad\.{checksum doesn't match}@> print('---loaded at size ',q:1,' DVI units'); d:=round((100.0*conv*q)/(true_conv*d)); if d<>100 then print(' (this font is magnified ',d:1,'%)'); @.this font is magnified@> incr(nf); {now the new font is officially present} font_space[nf]:=0; {for |out_space| and |out_vmove|} end @ The string |cur_name| is supposed to be set to the external name of the \.{TFM} file for the current font. This usually means that we need to prepend the name of the default directory, and to append the suffix `\.{.TFM}'. Furthermore, we change lower case letters to upper case, since |cur_name| is a \PASCAL\ string. If |p=0|, i.e., if no font directory has been specified, \.{DVItoLN03} is supposed to use the default font directory, which is a system-dependent place where the standard fonts are kept; in the current implementation, this will have been read into |tfm_directory| from the command-line qualifier \.{/font\UL directory}. @^system dependencies@> @= for k:=1 to name_length do cur_name[k]:=' '; if p=0 then begin for k:=1 to tfm_directory.length do cur_name[k]:=tfm_directory.body[k]; r:=tfm_directory.length; end else r:=0; for k:=font_name[nf] to font_name[nf+1]-1 do begin incr(r); if r+4>name_length then capacity_exceeded('font file name too long (max length=',name_length:1,')'); @:capacity exceeded font file name}{\quad\.{font file name too long}@> if (names[k]>="a")and(names[k]<="z") then cur_name[r]:=xchr[names[k]-@'40] else cur_name[r]:=xchr[names[k]]; end; cur_name[r+1]:='.'; cur_name[r+2]:='T'; cur_name[r+3]:='F'; cur_name[r+4]:='M' @* Accumulation of page. We save a page before outputting it, to minimize the complexity of the page for the LN03. The page is stored as a series of linked lists of ``segments'', with (potentially) one such list being stored for each pixel position in the vertical direction. The individual segments are a structure, containing the $x$-coordinates of the start and finish of the text. We also record the current font in which the text is to be set, and a pointer to the next segment in the list. We therefore declare here the structure to hold the segment, and an array of pointers, one for each vertical pixel position. We also provide a further two segment pointers, one for the ``current'' segment, and another pointing to a list of allocated, but otherwise unused, segments. Thus it is only necessary to call for storage allocation when the free list is exhausted. If a glyph proved to have too large a bitmap for it to have been downloaded as a character into the LN03, a segment was appended which references that glyph alone; this is necessary to ensure that the bitmaps are only output once the |eop| has been met, to accommodate changes of orientation brought about by \.{\\special} commands. @d max_text=max_print {Largest text segment held in a record} @= @!string = varying [10] of char; {to hold character representations of integers} @!segment = packed record @!next : ^segment; {pointer to next segment, or |nil|} @!xs,@!xe : integer; {$x$-coords of start and finish of text} @!font : integer; {LN03 downloaded font \#; external font for bitmaps} @!ch : eight_bits; {character from LN03 internal font} @!seg_type : (@!glyph_string,@!bitmap,@!sixel_dump); @!direction : page_orientation; {|landscape| or |portrait|} @!text : varying [max_text] of char; {sequence for output to printer} end; @ There is one |seg_list| pointer for each possible horizontal row of pixels on the paper; |cur_seg| points to the current segment, whilst |free_list_seg| maintains a list of free segments. Any characters which would appear outside the bounds of the paper are accumulated in segments of the |lost_seg| list. @= @!seg_list : array [y_land_min..paper_ht+y_port_min] of ^segment; @!cur_seg, @!free_seg_list : ^segment; @!lost_seg : ^segment; {For segments set off the paper} @!seg_max : integer; {could be used for statistics} @ @= free_seg_list:=nil; cur_seg:=nil; lost_seg:=nil; for k:=y_land_min to paper_ht+y_port_min do seg_list[k]:=nil; seg_max:=0; top_of_page:=true; @ The procedure |new_segment| allocates storage for a new segment. The program maintains a |free_seg_list| to which processed segments are returned; however, if no free segments remain on this list (as will obtain on first use of the program), the \PASCAL\ |new| function is used to allocate an appropriate area. After the segment has been allocated, as many fields as possible are completed: (1) the |next| field points to the existing |seg_list| to which this new segment now belongs; (2) the |font| field records the font currently selected; (3) the |seg_list| is made to point to this new segment. If the text is known to be invalid (for the case in which it has been discovered that the text will not fit on the physical paper), then the new segment is added instead to the |lost_seg| list, and the |font| field is used to record the $y$-position at which \TeX\ had tried to locate the text. @p procedure new_segment(@!valid,@!offset:boolean; @!font_code,@!ch_code:integer); begin @ ; @ end; {|new_segment|} @ If the |free_seg_list| is empty, either we've never allocated any segments before, or we've used them all. We therefore use the standard \PASCAL\ allocator function, |new|. Alternatively, we just detach the first segment from the |free_seg_list|. @= if free_seg_list = nil then begin new(cur_seg); incr(seg_max) end else begin cur_seg:=free_seg_list; free_seg_list:=cur_seg^.next end @ If the character being set is correctly within the imaging area of the paper, we add the new segment to the segment list associated with its vertical position on the page, and record the |ln_font| in which the character shall be output. Alternatively, we add the segment to the list of |lost_seg|s, and use the |font| field to record the vertical |y_pos| at which \TeX\ \\{thought} it was imaging the text. References to actual paper positions are always offset by the setting of |left_marg| and |top_marg|. @d x_pos==hh+left_marg {Actual paper coordinates} @d y_pos==vv+top_marg @= with cur_seg^ do if valid then begin ch:=ch_code; direction:=new_orient; if offset then {oversized glyphs and sixel graphics inclusions} begin next:=seg_list[y_pos+bitmap_offset]; {Goes on `lower' list} seg_type:=bitmap; {this is corrected externally if necessary} font:=cur_font; {record index into fonts for later file opening} seg_list[y_pos+bitmap_offset]:=cur_seg {Put at front of the list} end else begin next:=seg_list[y_pos]; seg_type:=glyph_string; font:=font_code; {record number of a \\{downloaded} font} seg_list[y_pos]:=cur_seg {Put at front of the list} end end else begin next:=lost_seg; {Don't put it on paper} font:=y_pos; {Remember where \TeX\ wanted to put it} lost_seg:=cur_seg end @ Procedure |finish_seg| is used to discard segments which record text which was set off the paper, after making appropriate error indications in the log (\.{TYP}) file. Discarded segments are returned to the |free_seg_list|. The procedure is also called when a glyph is met that cannot become a member of that held in |cur_seg|; this just results in |cur_seg:=nil|. The segment is already attached to some part of |seg_list| array, so nothing further is required, and nothing gets lost! @p procedure finish_seg; begin while (cur_seg<>nil) and (cur_seg=lost_seg) do begin {We've got something set off the paper} with cur_seg^ do begin warning('Characters ''',text,''' set off paper '); @:Warning: Characters set off paper}{\quad\.{Characters ... set off paper}@> write_ln(term_out,'(see log file)',crlf); print_ln('( x = ',xs:1,'..',xe:1,', y = ',font:1,' ) - ignored'); lost_seg:=next; next:=free_seg_list; free_seg_list:=cur_seg; cur_seg:=lost_seg end; end; cur_seg:=nil {Forget that we've got a current segment} end; @ The function |str_int| yields a minimum length ASCII representation for its integer parameter |num|. It does this by invoking the VAX-\PASCAL\ specific function |dec(n,f,d)|, which yields an |f| character-long string containing the decimal representation of the integer |n| occupying |d| digits at the right-hand end of the string; if necessary leading `\.0' characters are inserted to ensure that |d| digits are printed. (The leading |f-d| characters of the string would be filled with the space character `\.\SP', but we ensure that |f| and |d| are both just wide enough to avoid such a problem!) @d VAX_decimal==@=dec@> @p function str_int(num : integer) : string; var field,digits : integer; begin if num = 0 then digits:=1 else digits:=trunc(ln(abs(num))/ln(10))+1; {$\bigl\lceil\log_{10}\vert\hbox{\\{num}}\vert\bigr\rceil$ is number of digits required} field:=digits; if num < 0 then incr(field); str_int:=VAX_decimal(num,field,digits) end; @ We define here some non-printing control characters, which will later be required for control and escape sequences. @d LF==chr(@"0A) @d FF==chr(@"0C) @d esc==chr(@"1B) {The ASCII ESCape character} @d csi==chr(@"9B) {Control Sequence Introducer} @d dcs==chr(@"90) {Device Control String} @d st==chr(@"9C) {String Terminator} @ The following |move_forward| procedure includes within the text of a segment the appropriate LN03 |HPR| (Horizontal Position Relative) control sequence to instruct the printer to move the printing position to the right. This sequence consists of the control sequence introducer, |csi|, followed by the motion expressed in pixels, followed by the sequence terminator `\.a'. @p procedure move_forward(dist:integer); begin cur_seg^.text:=cur_seg^.text + csi+str_int(dist)+'a'; end; @ Because of the restrictions of \PASCAL, we have to make a |forward| declaration of procedure |do_page|. The procedure is called (recursively) to interpret the |dvi| bytes stored from a character packet in a virtual font. It also implements the normal operations for interpreting the contents of a page read from the \.{DVI} file. @p function do_page(@!vf_start:integer):boolean; forward; @ Procedure |ord_text| adds a single character, |ch|, taken from the |cur_font|, to a segment preparatory to its being ``set'' on the paper. The procedure cannot be declared at this point in the \.{WEB} source because it references a number of procedures that haven't yet been declared. However, we'll arrange for it to appear in the correct part of the \PASCAL\ later on. If the character is taken from a virtual font, and requires more than the simple operation of setting a single character from a single font, then this procedure calls |do_page| recursively to interpret the sequence of |dvi| bytes that have been stored into |vf| from the \.{VF} file. This macro is equivalent to |glyph_map(cur_font)(#)|, assuming that |cur_base=glyph_base[cur_font]|. @d cur_font_glyph(#)==glyphs[cur_base+#] @= procedure ord_text(ch:integer); var @!good : boolean; begin with cur_font_glyph(ch) do begin @; @; if loaded=virtual then begin finish_seg; {Whatever precedes this character to be treated separately} if not do_page(seq_off) then abort('VF packet failed [',cur_font:1,',',ch:1,']!'); @:fatal error VF packet failed}{\quad\.{VF packet failed}@> finish_seg; {Don't run into next character or record this one's width twice} end else begin @; @ end end end {|procedure ord_text|} @ If the character would be set off the physical paper addressable by the LN03 printer, the segment belongs to the |lost_seg| list. @= if (x_pospage_wid) or (y_pospage_len) then begin good := false; if (cur_seg<>nil) then begin if (cur_seg<>lost_seg) then finish_seg {earlier glyphs OK, even though this is off paper} else if cur_seg^.font<>y_pos then finish_seg {these were off paper, but on a different row} end; end else@/ begin {The character fits on the paper} good := true; if cur_seg<>nil then begin if cur_seg=lost_seg then finish_seg {report earlier off-paper chars} else if cur_seg^.seg_type=bitmap then finish_seg; {previous segment contains single oversized glyph} if loaded=no then finish_seg {this segment will be ditto} end end @ If there is no |cur_seg| to hold the text, one is allocated, and the start and finish coordinates of the text therein recorded. The |text| field is set to be the null string. @= if cur_seg = nil then begin new_segment(good,loaded=no,font_code,ch); cur_seg^.xs:=x_pos; cur_seg^.xe:=x_pos; cur_seg^.text:='' end @ Checks are made that this segment (1) would not produce too large a contiguous sequence of characters (which might therefore lead to an overlong output record); (2) that the character does not need to be positioned to the \&{left} of those already in the segment; (3) that the LN03 font used for this character is the same as that for the other characters of the segment. If any of these checks fail, we commence a new segment; these tests are bypassed if we're setting a single oversized glyph. If any additional rightward movement is needed, the appropriate escape sequence is added to the |text| in the |cur_seg|. @= if cur_seg^.seg_type=glyph_string then begin if (VAX_length(cur_seg^.text)>=max_text-8) or (x_poscur_seg^.font) then begin finish_seg; @ {tests enumerated above failed} end else if x_pos>cur_seg^.xe then {We need to move rightward} move_forward(x_pos-cur_seg^.xe) {Add character to segment} end @ Characters whose glyphs proved too large to download in an LN03 font are saved up to be printed as a pixel dump; other glyphs are recorded in the current segment for imaging through the downloaded fonts. If the character being stored is only being recorded to support generation of an error message, it is translated back from ASCII to the external representation for printing of the message in the |type_file|. @= if good then with cur_seg^ do case loaded of yes: text:=text+chr(char_code); no, missing: finish_seg; {needn't consider further} othercases do_nothing; endcases else cur_seg^.text:=cur_seg^.text+xchr[ch] @* Packed file format. This format was designed by Tomas Rokicki in August, 1985. @^Rokicki, Tomas@> It constitutes a compact representation of the data contained in a \.{GF} file. The information content is the same, but packed (\.{PK}) files are almost always less than half the size of their \.{GF} counterparts. They are also easier to convert into a raster representation because they do not have a profusion of \\{paint}, \\{skip}, and \\{new\_row} commands to be separately interpreted. In addition, the \.{PK} format expressedly forbids \&{special} commands within a character. The minimum bounding box for each character is explicit in the format, and does not need to be scanned for as in the \.{GF} format. Finally, the width and escapement values are combined with the raster information into character ``packets'', making it simpler in many cases to process a character. A \.{PK} file is organized as a stream of 8-bit bytes. At times, these bytes might be split into 4-bit nybbles or single bits, or combined into multiple byte parameters. When bytes are split into smaller pieces, the `first' piece is always the most significant of the byte. For instance, the first bit of a byte is the bit with value 128; the first nybble can be found by dividing a byte by 16. Similarly, when bytes are combined into multiple byte parameters, the first byte is the most significant of the parameter. If the parameter is signed, it is represented by two's-complement notation. The set of possible eight-bit values are separated into two sets, those that introduce a character definition, and those that do not. The values that introduce a character definition comprise the range from 0 to 239; byte values above 239 are interpreted commands. Bytes which introduce character definitions are called flag bytes, and various fields within the byte indicate various things about how the character definition is encoded. Command bytes have zero or more parameters, and can never appear within a character definition or between parameters of another command, where they would be interpeted as data. A \.{PK} file consists of a preamble, followed by a sequence of one or more character definitions, followed by a postamble. The preamble command must be the first byte in the file, followed immediately by its parameters. Any number of character definitions may follow, and any command but the preamble command and the postamble command may occur between character definitions. The very last command in the file must be the postamble. @ The packed file format is intended to be easy to read and interpret by device drivers. The small size of the file reduces the input/output overhead each time a font is defined. For those drivers that load and save each font file into memory, the small size also helps reduce the memory requirements. The length of each character packet is specified, allowing the character raster data to be loaded into memory by simply counting bytes, rather than interpreting each command; then, each character can be interpreted on a demand basis. This also makes it possible for a driver to skip a particular character quickly if it knows that the character is unused. @ First, the command bytes shall be presented; then the format of the Character definitions will be defined. Eight of the possible sixteen commands (values 240 through 255) are currently defined; the others are reserved for future extensions. The commands are listed below. Each command is specified by its symbolic name (e.g., \\{pk\_no\_op}), its opcode byte, and any parameters. The parameters are followed by a bracketed number telling how many bytes they occupy, with the number preceded by a plus sign if it is a signed quantity. (Four byte quantities are always signed, however.) \yskip\hang|pk_xxx1| 240 |k[1]| |x[k]|. This command is undefined in general; it functions as a $(k+2)$-byte \\{no\_op} unless special \.{PK}-reading programs are being used. \MF\ generates \\{xxx} commands when encountering a \&{special} string. It is recommended that |x| be a string having the form of a keyword followed by possible parameters relevant to that keyword. \yskip\hang\\{pk\_xxx2} 241 |k[2]| |x[k]|. Like |pk_xxx1|, but |0<=k<65536|. \yskip\hang\\{pk\_xxx3} 242 |k[3]| |x[k]|. Like |pk_xxx1|, but |0<=k<@t$2^{24}$@>|. \MF\ uses this when sending a \&{special} string whose length exceeds~255. \yskip\hang\\{pk\_xxx4} 243 |k[4]| |x[k]|. Like |pk_xxx1|, but |k| can be ridiculously large; |k| musn't be negative. \yskip\hang|pk_yyy| 244 |y[4]|. This command is undefined in general; it functions as a five-byte \\{no\_op} unless special \.{PK} reading programs are being used. \MF\ puts |scaled| numbers into |yyy|'s, as a result of \&{numspecial} commands; the intent is to provide numeric parameters to \\{xxx} commands that immediately precede. \yskip\hang|pk_post| 245. Beginning of the postamble. This command is followed by enough |pk_no_op| commands to make the file a multiple of four bytes long. Zero through three bytes are usual, but any number is allowed. This should make the file easy to read on machines which pack four bytes to a word. \yskip\hang|pk_no_op| 246. No operation, do nothing. Any number of |pk_no_op|'s may appear between \.{PK} commands, but a |pk_no_op| cannot be inserted between a command and its parameters, between two parameters, or inside a character definition. \yskip\hang|pk_pre| 247 |i[1]| |k[1]| |x[k]| |ds[4]| |cs[4]| |hppp[4]| |vppp[4]|. Preamble command. Here, |i| is the identification byte of the file, currently equal to 89. The string |x| is merely a comment, usually indicating the source of the \.{PK} file. The parameters |ds| and |cs| are the design size of the file in $1/2^{20}$ points, and the checksum of the file, respectively. The checksum should match the \.{TFM} file and the \.{GF} files for this font. Parameters |hppp| and |vppp| are the ratios of pixels per point, horizontally and vertically, multiplied by $2^{16}$; they can be used to correlate the font with specific device resolutions, magnifications, and ``at sizes''. Usually, the name of the \.{PK} file is formed by concatenating the font name (e.g., amr10) with the resolution at which the font is prepared in pixels per inch multiplied by the magnification factor, and the letters \.{PK}. For instance, amr10 at 300 dots per inch should be named AMR10.300PK; at one thousand dots per inch and magstephalf, it should be named AMR10.1095PK. @ We put a few of the above opcodes into definitions for symbolic use by this program. @d pk_id = 89 {the version of \.{PK} file described} @d pk_xxx1 = 240 {\&{special} commands} @d pk_yyy = 244 {\&{numspecial} commands} @d pk_post = 245 {postamble} @d pk_no_op = 246 {no operation} @d pk_pre = 247 {preamble} @ The \.{PK} format has two conflicting goals; to pack character raster and size information as compactly as possible, while retaining ease of translation into raster and other forms. A suitable compromise was found in the use of run-encoding of the raster information. Instead of packing the individual bits of the character, we instead count the number of consecutive `black' or `white' pixels in a horizontal raster row, and then encode this number. Run counts are found for each row, from the top of the character to the bottom. This is essentially the way the \.{GF} format works. Instead of presenting each row individually, however, let us concatenate all of the horizontal raster rows into one long string of pixels, and encode this row. With knowledge of the width of the bit-map, the original character glyph can be easily reconstructed. In addition, we do not need special commands to mark the end of one row and the beginning of the next. Next, let us put the burden of finding the minimum bounding box on the part of the font generator, since the characters will usually be used much more often than they are generated. The minimum bounding box is the smallest rectangle which encloses all `black' pixels of a character. Let us also eliminate the need for a special end of character marker, by supplying exactly as many bits as are required to fill the minimum bounding box, from which the end of the character is implicit. Let us next consider the distribution of the run counts. Analysis of several dozen pixel files at 300 dots per inch yields a distribution peaking at four, falling off slowly until ten, then a bit more steeply until twenty, and then asymptotically approaching the horizontal. Thus, the great majority of our run counts will fit in a four-bit nybble. The eight-bit byte is attractive for our run-counts, as it is the standard on many systems; however, the wasted four bits in the majority of cases seems a high price to pay. Another possibility is to use a Huffman-type encoding scheme with a variable number of bits for each run-count; this was rejected because of the overhead in fetching and examining individual bits in the file. Thus, the character raster definitions in the \.{PK} file format are based on the four-bit nybble. @ The analysis of the pixel files yielded another interesting statistic: fully 37\char`\%\ of the raster rows were duplicates of the previous row. Thus, the \.{PK} format allows the specification of repeat counts, which indicate how many times a horizontal raster row is to be repeated. These repeated rows are taken out of the character glyph before individual rows are concatenated into the long string of pixels. For elegance, we disallow a run count of zero. The case of a null raster description should be gleaned from the character width and height being equal to zero, and no raster data should be read. No other zero counts are ever necessary. Also, in the absence of repeat counts, the repeat value is set to be zero (only the original row is sent.) If a repeat count is seen, it takes effect on the current row. The current row is defined as the row on which the first pixel of the next run count will lie. The repeat count is set back to zero when the last pixel in the current row is seen, and the row is sent out. This poses a problem for entirely black and entirely white rows, however. Let us say that the current row ends with four white pixels, and then we have five entirely empty rows, followed by a black pixel at the beginning of the next row, and the character width is ten pixels. We would like to use a repeat count, but there is no legal place to put it. If we put it before the white run count, it will apply to the current row. If we put it after, it applies to the row with the black pixel at the beginning. Thus, entirely white or entirely black repeated rows are always packed as large run counts (in this case, a white run count of 54) rather than repeat counts. @ Now let us turn our attention to the actual packing of the run counts and repeat counts into nybbles. There are only sixteen possible nybble values. We need to indicate run counts and repeat counts. Since the run counts are much more common, we will devote the majority of the nybble values to them. We therefore indicate a repeat count by a nybble of 14 followed by a packed number, where a packed number will be explained later. Since the repeat count value of one is so common, we indicate a repeat one command by a single nybble of 15. A 14 followed by the packed number 1 is still legal for a repeat one count, however. The run counts are coded directly as packed numbers. For packed numbers, therefore, we have the nybble values 0 through 13. We need to represent the positive integers up to, say, $2^{31}-1$. We would like the more common smaller numbers to take only one or two nybbles, and the infrequent large numbers to take three or more. We could therefore allocate one nybble value to indicate a large run count taking three or more nybbles. We do this with the value 0. @ We are left with the values 1 through 13. We can allocate some of these, say |dyn_f|, to be one-nybble run counts. These will work for the run counts |1..dyn_f|. For subsequent run counts, we will use a nybble greater than |dyn_f|, followed by a second nybble, whose value can run from 0 through 15. Thus, the two-byte nybble values will run from |dyn_f+1..(13-dyn_f)*16+dyn_f|. We have our definition of large run count values now, being all counts greater than |(13-dyn_f)*16+dyn_f|. We can analyze our several dozen pixel files and determine an optimal value of |dyn_f|, and use this value for all of the characters. Unfortunately, values of |dyn_f| that pack small characters well tend to pack the large characters poorly, and values that pack large characters well are not efficient for the smaller characters. Thus, we choose the optimal |dyn_f| on a character basis, picking the value which will pack each individual character in the smallest number of nybbles. Legal values of |dyn_f| run from 0 (with no one-byte run counts) to 13 (with no two-byte run counts). @ Our only remaining task in the coding of packed numbers is the large run counts. We use a scheme suggested by D.~E.~Knuth @^Knuth, D.~E.@> which will simply and elegantly represent arbitrarily large values. The general scheme to represent an integer |i| is to write its hexadecimal representation, with leading zeros removed. Then we count the number of digits, and prepend one less than that many zeros before the hexadecimal representation. Thus, the values from one to fifteen occupy one nybble; the values sixteen through 255 occupy three, the values 256 through 4095 require five, etc. For our purposes, however, we have already represented the numbers one through |(13-dyn_f)*16+dyn_f|. In addition, the one-nybble values have already been taken by our other commands, which means that only the values from sixteen up are available to us for long run counts. Thus, we simply normalize our long run counts, by subtracting |(13-dyn_f)*16+dyn_f+1| and adding 16, and then representing the result according to the scheme above. @ The final algorithm for decoding the run counts based on the above scheme might look like this, assuming a procedure called \\{pk\_nyb} is available to get the next nybble from the file, and assuming that the global |repeat_count| indicates whether a row needs to be repeated. Note that this routine is recursive, but since a repeat count can never directly follow another repeat count, it can only be recursive to one level. @= function pk_packed_num : integer ; var i, j, k : integer ; begin i := get_nyb ; if i = 0 then begin repeat j := get_nyb ; incr(i) ; until j <> 0 ; while i > 0 do begin j := j * 16 + get_nyb ; decr(i) ; end ; pk_packed_num := j - 15 + (13-dyn_f)*16 + dyn_f ; end else if i <= dyn_f then pk_packed_num := i else if i < 14 then pk_packed_num := (i-dyn_f-1)*16+get_nyb+dyn_f+1 else begin if i = 14 then repeat_count := pk_packed_num else repeat_count := 1 ; pk_packed_num := pk_packed_num ; end ; end ; @ For low resolution fonts, or characters with `gray' areas, run encoding can often make the character many times larger. Therefore, for those characters that cannot be encoded efficiently with run counts, the \.{PK} format allows bit-mapping of the characters. This is indicated by a |dyn_f| value of 14. The bits are packed tightly, by concatenating all of the horizontal raster rows into one long string, and then packing this string eight bits to a byte. The number of bytes required can be calculated by |(width*height+7) div 8|. This format should only be used when packing the character by run counts takes more bytes than this, although, of course, it is legal for any character. Any extra bits in the last byte should be set to zero. @ At this point, we are ready to introduce the format for a character descripter. It consists of three parts: a flag byte, a character preamble, and the raster data. The most significant four nybbles of the flag byte yield the |dyn_f| value for that character. (Notice that only values of 0 through 14 are legal for |dyn_f|, with 14 indicating a bit mapped character; thus, the flag bytes do not conflict with the command bytes, whose upper nybble is always 15.) The next bit (with weight 16) indicates whether the first run count is a black count or a white count, with a one indicating a black count. For bit-mapped characters, this bit should be set to a zero. The next bit (with weight 8) indicates whether certain later parameters (referred to as size parameters) are given in one-byte or two-byte quantities, with a one indicating that they are in two-byte quantities. The last two bits are concatenated on to the beginning of the length parameter in the character preamble, which will be explained below. However, if the last three bits of the flag byte are all set (normally indicating that the size parameters are two-byte values and that a 3 should be prepended to the length parameter), then a long format of the character preamble should be used instead of one of the short forms. Therefore, there are three formats for the character preamble, and which one is used depends on the least significant three bits of the flag byte. If the least significant three bits are in the range zero through three, the short format is used. If they are in the range four through six, the extended short format is used. Otherwise, if the least significant bits are all set, then the long form of the character preamble is used. The preamble formats are explained below. \yskip\hang Short form: |flag[1]| |pl[1]| |cc[1]| |tfm[3]| |dm[1]| |w[1]| |h[1]| |hoff[+1]| |voff[+1]|. If this format of the character preamble is used, the above parameters must all fit in the indicated number of bytes, signed or unsigned as indicated. Almost all of the standard \TeX\ font characters fit; the few exceptions are fonts such as \.{aminch}. \yskip\hang Extended short form: |flag[1]| |pl[2]| |cc[1]| |tfm[3]| |dm[2]| |w[2]| |h[2]| |hoff[+2]| |voff[+2]|. Larger characters use this extended format. \yskip\hang Long form: |flag[1]| |pl[4]| |cc[4]| |tfm[4]| |dx[4]| |dy[4]| |w[4]| |h[4]| |hoff[4]| |voff[4]|. This is the general format which allows all of the parameters of the \.{GF} file format, including vertical escapement. \vskip\baselineskip The |flag| parameter is the flag byte. The parameter |pl| (packet length) contains the offset of the byte following this character descripter, with respect to the beginning of the |tfm| width parameter. This is given so a \.{PK} reading program can, once it has read the flag byte, packet length, and character code (|cc|), skip over the character by simply reading this many more bytes. For the two short forms of the character preamble, the last two bits of the flag byte should be considered the two most-significant bits of the packet length. For the short format, the true packet length might be calculated as |(flag mod 4)*256+pl|; for the extended format, it might be calculated as |(flag mod 4)*65536+pl|. The |w| parameter is the width and the |h| parameter is the height in pixels of the minimum bounding box. The |dx| and |dy| parameters are the horizontal and vertical escapements, respectively. In the short formats, |dy| is assumed to be zero and |dm| is |dy| but in pixels; in the long format, |dx| and |dy| are both in pixels multiplied by $2^{16}$. The |hoff| is the horizontal offset from the upper left pixel to the reference pixel; the |voff| is the vertical offset. They are both given in pixels, with right and down being positive. The reference pixel is the pixel which occupies the unit square in \MF; the \MF\ reference point is the lower left hand corner of this pixel. (See the example below.) @ \TeX\ requires that all characters which have the same character codes modulo 256 also have the same |tfm| widths, and escapement values. The \.{PK} format does not itself make this a requirement, but in order for the font to work correctly with the \TeX\ software, this constraint should be observed. The current version of \TeX\ (1.5) cannot output character codes greater than 255 anyway. Following the character preamble is the raster information for the character, packed by run counts or by bits, as indicated by the flag byte. If the character is packed by run counts and the required number of nybbles is odd, then the last byte of the raster description should have a zero for its least significant nybble. @ As an illustration of the \.{PK} format, the character \char4\ from the font amr10 at 300 dots per inch will be encoded. This character was chosen because it illustrates some of the borderline cases. The raster for the character looks like this (the row numbers are chosen for convenience, and are not \MF's row numbers.) \vskip\baselineskip \centerline{\vbox{\baselineskip=10pt \halign{\hfil#\quad&&\hfil#\hfil\cr 0& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr 1& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr 2& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr 3& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr 4& & &M&M& & & & & & & & & & & & & & & & &M&M\cr 5& & &M&M& & & & & & & & & & & & & & & & &M&M\cr 6& & &M&M& & & & & & & & & & & & & & & & &M&M\cr 7\cr 8\cr 9& & & & &M&M& & & & & & & & & & & & &M&M& & \cr 10& & & & &M&M& & & & & & & & & & & & &M&M& & \cr 11& & & & &M&M& & & & & & & & & & & & &M&M& & \cr 12& & & & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M& & \cr 13& & & & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M& & \cr 14& & & & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M& & \cr 15& & & & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M& & \cr 16& & & & &M&M& & & & & & & & & & & & &M&M& & \cr 17& & & & &M&M& & & & & & & & & & & & &M&M& & \cr 18& & & & &M&M& & & & & & & & & & & & &M&M& & \cr 19\cr 20\cr 21\cr 22& & &M&M& & & & & & & & & & & & & & & & &M&M\cr 23& & &M&M& & & & & & & & & & & & & & & & &M&M\cr 24& & &M&M& & & & & & & & & & & & & & & & &M&M\cr 25& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr 26& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr 27& & &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr 28&*& &M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M&M\cr &\hphantom{M}&\hphantom{M}\cr }}} The width of the minimum bounding box for this character is 20; its height is 29. The `*' represents the reference pixel; notice how it lies outside the minimum bounding box. The |hoff| value is $-2$, and the |voff| is~28. The first task is to calculate the run counts and repeat counts. The repeat counts are placed at the first transition (black to white or white to black) in a row, and are enclosed in brackets. White counts are enclosed in parentheses. It is relatively easy to generate the counts list: \vskip\baselineskip \centerline{82 [2] (16) 2 (42) [2] 2 (12) 2 (4) [3]} \centerline{16 (4) [2] 2 (12) 2 (62) [2] 2 (16) 82} \vskip\baselineskip Note that any duplicated rows that are not all white or all black are removed before the repeat counts are calculated. The rows thus removed are rows 5, 6, 10, 11, 13, 14, 15, 17, 18, 23, and 24. @ The next step in the encoding of this character is to calculate the optimal value of |dyn_f|. The details of how this calculation is done are not important here; suffice it to say that there is a simple algorithm which in one pass over the count list can determine the best value of |dyn_f|. For this character, the optimal value turns out to be 8 (atypically low). Thus, all count values less than or equal to 8 are packed in one nybble; those from nine to $(13-8)*16+8$ or 88 are packed in two nybbles. The run encoded values now become (in hex, separated according to the above list): \vskip\baselineskip \centerline{\tt D9 E2 97 2 B1 E2 2 93 2 4 E3} \centerline{\tt 97 4 E2 2 93 2 C5 E2 2 97 D9} \vskip\baselineskip\noindent which comes to 36 nybbles, or 18 bytes. This is shorter than the 73 bytes required for the bit map, so we use the run count packing. @ The short form of the character preamble is used because all of the parameters fit in their respective lengths. The packet length is therefore 18 bytes for the raster, plus eight bytes for the character preamble parameters following the character code, or 26. The |tfm| width for this character is 640796, or {\tt 9C71C} in hexadecimal. The horizontal escapement is 25 pixels. The flag byte is 88 hex, indicating the short preamble, the black first count, and the |dyn_f| value of 8. The final total character packet, in hexadecimal, is: \vskip\baselineskip $$\vbox{\halign{\hfil #\quad&&{\tt #\ }\cr Flag byte&88\cr Packet length&1A\cr Character code&04\cr |tfm| width&09&C7&1C\cr Horizontal escapement (pixels)&19\cr Width of bit map&14\cr Height of bit map&1D\cr Horizontal offset (signed)&FE\cr Vertical offset&1C\cr Raster data&D9&E2&97\cr &2B&1E&22\cr &93&24&E3\cr &97&4E&22\cr &93&2C&5E\cr &22&97&D9\cr}}$$ @* Routines to access the packed pixel files. We shall require routines to access the packed file; sometimes this may even require access to individual \&{bits}, but mostly we shall be accessing bytes or nybbles. We also need a function that will get a single byte from the \.{PK} file. Again, buffering may be done in this procedure. @p function pk_byte : eight_bits ; var temp : eight_bits ; begin temp := pxl_file^[pk_loc mod VAX_block_length] ; pk_loc := pk_loc + 1 ; if (pk_loc mod VAX_block_length)=0 then get(pxl_file) ; pk_byte := temp ; end ; @ As we are reading the packed file, we often need to fetch (signed or unsigned) 16 and 32 bit quantities. Here we have three procedures to do this. @p function get_16 : integer ; var a : integer ; begin a := pk_byte ; get_16 := a * 256 + pk_byte ; end ; @# function get_16_signed : integer ; var a : integer ; begin a := get_16; if a>32767 then a := a-65536; get_16_signed := a ; end ; @# function get_32 : integer ; var a : integer ; begin a := get_16 ; if a > 32767 then a := a - 65536 ; get_32 := a * 65536 + get_16 ; end ; @ Now we read and check the preamble of the pk file. In the preamble, we find the |hppp|, |dsize|, |checksum|. After we've read the preamble, no character locators will be available. @d two_to_the_20th==@"100000 @= pk_loc:=0; char_num:=-1; {Ensure that |char_num| is ``undefined''} if pk_byte <> pk_pre then abort('Bad pk file, pre command missing') ; @:fatal error Bad pk file ...}{\quad\.{Bad pk file}@> @:fatal error Bad pk file pre command missing}{\qquad\\{pre }\.{command missing}@> if pk_byte <> pk_id then abort('Bad pk file, wrong version') ; @:fatal error Bad pk file wrong version}{\qquad\.{wrong version}@> pk_j := pk_byte ; for pk_i := 1 to pk_j do dummy:=pk_byte ; { Skip over PK file comment } dsize := get_32 ; checksum := get_32 ; hppp := get_32 ; vppp := get_32 ; magnification := round(hppp * 72.27 / 65536) ; {|* 5| in PKtoPX} if hppp <> vppp then warning('PK font pixels non-square'); @:Warning: PK font pixels non-square}{\quad\.{PK font pixels non-square}@> dsize:=(dsize/two_to_the_20th)*(magnification/resolution); for pk_i:=0 to 255 do ch_loc[pk_i]:=0; @ Of course, we need to define the above variables. @= @!checksum : integer ; {checksum of pixel file} @!hppp, @!vppp : integer ; {horizontal and vertical points per inch} @!pk_loc : integer ; { byte currently being accessed in |pk_file| } @!dummy : integer ; { used to ``absorb'' unwanted bytes from |pk_file| } @* Character unpacking. This is the procedure where we read the character packet and decode it into standard pixel raster format; if a glyph is encountered that is not used in the present document, its packet is skipped. We create one row at a time, checking for repeat commands, and then repeat the row as many times as is necessary. The information gleaned from the beginning of the character packet is to be placed in various local variables. We also check that the length of the character packet is correct. If the character's raster is null (as is the case with the invisible fonts used by Sli\TeX), the |num_cols| and |num_rows| are set to suitable dummy values (see the section $\langle$Complete remainder of Character Definition parameters$\rangle$ for further information). @= dyn_f := flag_byte div 16 ; flag_byte := flag_byte mod 16 ; turn_on := flag_byte >= 8 ; if turn_on then flag_byte := flag_byte - 8 ; if flag_byte = 7 then @ else if flag_byte > 3 then @ else @ ;@/ byte_width:=(num_cols+7) div 8; {Bytes required per row in |lnf_file|} ch_loc[char_num]:=end_of_packet-packet_length; with glyph_map(TeX_font)(char_num) do if loaded=wanted then begin base:=ras_beg+ras_len;@/ @; @; @; @; @; @ end else @ ; if end_of_packet <> pk_loc then abort('Bad pk file! Bad packet length.') @.Bad pk file@> @ A simplified version of this code is also used when printing the bitmap of an oversized glyph in sixels. @= dyn_f := flag_byte div 16 ; flag_byte := flag_byte mod 16 ; turn_on := flag_byte >= 8 ; if turn_on then flag_byte := flag_byte - 8 ; if flag_byte = 7 then @ else if flag_byte > 3 then @ else @ ;@/ byte_width:=(num_cols+7) div 8; {Bytes required per row in |lnf_file|} ch_loc[char_num]:=end_of_packet-packet_length; if char_num=ch then begin base:=ras_beg+ras_len;@/ @; end else @ ; if end_of_packet <> pk_loc then abort('Bad pk file! Bad packet length.') @.Bad pk file@> @ We need a whole lot of globals used but not defined up there. Some of these have the same names as certain fields of |pxl_dir_type|, or of local variables of procedures |add_txf_to_lnf| and |copy_char|. This is in order that the segments of \.{WEB} code invoked above reference these globals therein, but the appropriate local variables when within those procedures (when being used for \.{PXL} files). @= @!pk_i, @!pk_j : integer ; {index pointers} @!flag_byte : integer ; {the byte that introduces the character definition} @!end_of_packet : integer ; {where we expect the end of the packet to be}@/ {Following are also names of fields in a record of type |pxl_dir_type|}@/ @!num_cols, @!num_rows : sixteen_bits; {width and height of character} @!x_off, @!y_off : signed_word ; {$x$ and $y$ offsets of character} @!tfm_width : integer ; {character tfm width} @# @!tfms : array [0..255] of integer ; {character tfm widths} @!dx, @!dy : integer ; {escapement values} @!dxs, @!dys : array [0..255] of integer ; {escapement values} @!ch_loc : array[0..255] of integer; {has the character been seen (and where)?} @!dyn_f : integer ; {dynamic packing variable} @!char_num : integer ; {the character we are reading} @!packet_length : integer ; {the length of the character packet} @!base : integer ; {index into |lnf_bytes|} @!to_where : integer ; {\&{cf.} |to_where| in |copy_char|} @!len : integer ; {\&{cf.} |len| in |copy_char|} @!byte_width : integer ; {Bytes per row in the |lnf_file| bitmap} @ To skip over the raster of an unwanted character, we can simply jump forward in the PK file to |end_of_packet|. @= while pk_loc= begin packet_length := get_32 ; char_num := get_32 ; end_of_packet := packet_length + pk_loc ; packet_length := packet_length + 9 ; tfm_width := get_32 ; dx := get_32 ; dy := get_32 ; num_cols := get_32 ; num_rows := get_32 ; x_off := get_32 ; y_off := get_32 ; end @ This module reads the character preamble with double byte parameters. @= begin packet_length := (flag_byte - 4) * 65536 + get_16 ; char_num := pk_byte ; end_of_packet := packet_length + pk_loc ; packet_length := packet_length + 4 ; pk_i := pk_byte ; tfm_width := pk_i * 65536 + get_16 ; dx := get_16 * 65536 ; dy := 0 ; num_cols := get_16 ; num_rows := get_16 ; x_off := get_16_signed ; y_off := get_16_signed ; end @ Here we read the most common character preamble, that with single byte parameters. @= begin packet_length := flag_byte * 256 + pk_byte ; char_num := pk_byte ; end_of_packet := packet_length + pk_loc ; packet_length := packet_length + 3 ; pk_i := pk_byte ; tfm_width := pk_i * 65536 + get_16 ; dx := pk_byte * 65536 ; dy := 0 ; num_cols := pk_byte ; num_rows := pk_byte ; x_off := pk_byte ; y_off := pk_byte ; if x_off > 127 then x_off := x_off - 256 ; if y_off > 127 then y_off := y_off - 256 ; end @ Now we have the most important part of unravelling packed pixel files, where we actually interpret the commands in the raster description. First of all, we need a procedure to get a single nybble from the file, as well as one to get a single bit. We also are now able to ``declare'' |pk_packed_num|. @p function get_nyb : integer ; var temp : eight_bits ; begin if bit_weight = 0 then begin input_byte := pk_byte ; bit_weight := 16 ; end ; temp := input_byte div bit_weight ; input_byte := input_byte - temp * bit_weight ; bit_weight := bit_weight div 16 ; get_nyb := temp ; end ; @# function get_bit : boolean ; var temp : boolean ; begin bit_weight := bit_weight div 2 ; if bit_weight = 0 then begin input_byte := pk_byte ; bit_weight := 128 ; end ; temp := input_byte >= bit_weight ; if temp then input_byte := input_byte - bit_weight ; get_bit := temp ; end ; @# @t\4@>@ @ Now, the globals to help communication between these procedures. @= @!input_byte : eight_bits ; {the byte we are currently decimating} @!bit_weight : eight_bits ; {weight of the current bit} @!nybble : eight_bits ; {the current nybble} @!row_addr : integer ; {whereabouts in |lnf_file| of start of each pixel row} @!bitmap_start : integer ; {and of start of \&{first} pixel row} @ And the main procedure. @= bit_weight := 0 ; row_addr:=base; {Note where row of pixels starts} bitmap_start:=base; if dyn_f = 14 then @ else @ @ If |dyn_f=14|, then we need to get the raster representation one bit at a time. @= begin for pk_i := 1 to num_rows do begin bite:=0; bite_weight:=7; for pk_j := 1 to num_cols do begin if get_bit then bite:=bite+power[bite_weight]; decr(bite_weight); if bite_weight<0 then begin bite_weight:=7; put_lnf(bite); bite:=0 end end; if bite_weight<7 then put_lnf(bite) end; end @ We need the |power| array, which contains powers of two, as well as the |bite| variable (which accumulates eight pixels) and |bite_weight|, which directs which pixel is to be ``blackened''. The array |gpower| contains 0..8 black pixels, staring at the left-hand end. @= @!bite : integer; {accumulates 8 pixels to go to |lnf_file|} @!bite_weight : integer; {next bit of |bite| to be filled (leftmost=0)} @!power:array [0..7] of integer; {|power[i]| contains $2^{7-i}$} @!gpower:array [0..8] of integer; {|gpower[i]| contains ones in leftmost |i| bits} @ @= power[0]:=128; for i:=1 to 7 do power[i]:=power[i-1] div 2; gpower[0]:=0; for i:=1 to 8 do gpower[i]:=gpower[i-1]+power[i-1]; @ Otherwise, we translate the bit counts into the raster rows. |run_ct| contains the number of bits of the current color, and |turn_on| indicates whether or not they should be black. |rows_left| contains the number of rows to be sent. @= begin rows_left := num_rows ; h_bit := num_cols ; repeat_count := 0 ; bite:=0; bite_weight:=8; while rows_left > 0 do begin run_ct := pk_packed_num ; while run_ct>0 do begin if (run_ct=h_bit) and (h_bit<=bite_weight) then begin if turn_on then bite:=bite+gpower[bite_weight]-gpower[bite_weight-h_bit]; put_lnf(bite); bite:=0; for pk_i:=1 to repeat_count do for pk_j:=1 to byte_width do repeat_row; row_addr:=base; rows_left := rows_left - repeat_count - 1 ; repeat_count := 0 ; bite:=0; bite_weight:=8; run_ct := run_ct - h_bit ; h_bit := num_cols ; end else begin if turn_on then bite:=bite+gpower[bite_weight]; put_lnf(bite); run_ct:=run_ct-bite_weight; h_bit:=h_bit-bite_weight; bite:=0; bite_weight:=8 end end; turn_on := not turn_on ; end ; if (rows_left <> 0) or (h_bit <> num_cols) then abort('Bad pk file---more bits than required!'); @.Bad pk file@> end @ We need to declare the repeat flag, bit counter, and color flag here. @= @!repeat_count : integer ; {how many times to repeat the next row?} @!rows_left : integer ; {how many rows left?} @!turn_on : boolean ; {are we black here?} @!h_bit : integer ; {what is our horizontal position?} @!run_ct : integer ; {how many bits of current color left?} @ Another necessary section of code skips over any \MF\ \.{special}s between characters and before or after the postamble. It is convenient to use the following definitions in creating readable |case| statements; they will also be necessary for interpreting the \.{DVI} file. @d four_cases(#)==#,#+1,#+2,#+3 @d eight_cases(#)==four_cases(#),four_cases(#+4) @d sixteen_cases(#)==eight_cases(#),eight_cases(#+8) @d thirty_two_cases(#)==sixteen_cases(#),sixteen_cases(#+16) @d sixty_four_cases(#)==thirty_two_cases(#),thirty_two_cases(#+32) @p procedure skip_specials ; var i, j, k : integer ; begin repeat flag_byte := pk_byte ; if flag_byte >= pk_xxx1 then case flag_byte of four_cases(pk_xxx1): begin i := 0 ; for j := pk_xxx1 to flag_byte do i := 256 * i + pk_byte ; for j := 1 to i do dummy:=pk_byte ; end ; pk_yyy: dummy:=get_32 ; pk_post: do_nothing; pk_no_op: do_nothing; pk_pre, eight_cases(pk_pre+1): abort('Bad pk file; unexpected byte: ', flag_byte:1,'!') ; @.Bad pk file@> endcases ; until (flag_byte < pk_xxx1) or (flag_byte = pk_post) ; end ; @* Pixel file format. A \.{PXL} file is an expanded raster description of a single font at a particular resolution and contains essentially the same information as that contained in a \.{GF} file. \.{PXL} files are used by many existing device-driver programs for dot matrix devices. By convention, \.{PXL} files are for 200 pixels per inch. However, the LN03 requires pixel files generated at 300 pixels per inch; the font naming conventions for access to these files are specified elsewhere. All words in a \.{PXL} files are in 32-bit format, with the four lower bits zero on 36-bit machines. The raster information is contained in a sequence of binary words which record white pixels as zeros and black pixels as ones. The first word of the \.{PXL} file and the last word contain the |pxl_id| which is currently equal to 1001. This first word is followed by a sequence of raster information words where each line of pixels in the glyphs is represented by one or more words of binary information. The number of words used to represent each row of pixels for any particular glyph is fixed and it is set by the value of |max_m-min_m+1| for that particular glyph. Each white pixel is represented by a zero and each black pixel is represented by a one in the corresponding bit positions (the first 32 only of each word on 36-bit machines). The unused bit positions toward the end of each set of words for each row of pixels are filled with zeros. It should be noted that this representation is more wasteful of space than it needs to be, but it may possibly simplify the subsequent use of the information by a device-driver program. The font directory follows, occupying a fixed position with respect to the end of the file (in words 517 through 6 from this end), and assigns 4 words for each of the potential 128 different glyphs that could be contained in this particular font in the order of their ascending ascii values (not in the order that the glyphs appear in the raster section, which may be entirely arbitrary). This means that the first four words are for the ascii zero glyph. All four words reserved for any missing glyphs are set to zero. A detailed description of these directory entries is given in the next section. The final five words in the \.{PXL} file contain information relative to the entire file. The first of these five words is a checksum which should match the checksum contained in the \.{TFM} file that \TeX\ used in reference to this font, although, if this checksum is zero, no validity checking will be done. The second of these five words is an integer that is 1000 times the magnification factor at which this font was produced. The third word contains the design size of the font measured in \.{FIXes} ($2^{-20}$ unmagnified points). The fourth word contains a pointer to the first word of the font directory. The fifth (and last word of the entire file) contains a duplicate of the |pxl_id| as contained in the first word of the file. @d pxl_id=1001 {current version of \.{PXL} format} @ As mentioned above, the pixel file's glyph directory consists of four longwords for each possible glyph. The use of each of these longwords is as follows: \yskip\hang\&{Word 0.} This is treated as being split into two unsigned sixteen-bit quantities, containing the Pixel Width in the left half-word (the leftmost 16 bits) and the Pixel Height in the right half-word (the next 16 bits). These dimensions are those of the smallest bounding-box, measured in pixels, and they have nothing necessarily to do with the width and height figures that appear in the \.{TFM} file. \yskip\hang\&{Word 1.} Again is split into two sixteen-bit half-words, but this time they are each treated as \\{signed} quantities. Together, these give the offset of the glyph's reference point \&{from} its upper-left-hand corner of the bounding box, measured in pixels, with the $x$-offset ($\delta x$) in the left half-word and the $y$-offset ($\delta y$) in the right half-word. Two's complement representation is used. Remember that although the positive $x$ direction means `rightward' and positive $y$ is `downward' on the page, these offsets specify the distance \&{to} the reference point \&{from} the upper left-hand corner of the raster with +ve $x$ implying that the left-most column of the raster is to the \&{left} of the reference point, and +ve $y$ implying that the top-most row of the raster is \&{above} the baseline. \yskip\hang\&{Word 2.} This contains the number of the word in this \.{PXL} file where the Raster Description for this particular glyph begins, measured from the first word of the file, which is numbered zero. \yskip\hang\&{Word 3.} Contains the \.{TFM} width, measured in \.{FIXes}, where 1 \.{FIX} is $1/(2^{20})$ times the design size. We now define an appropriate structure to hold such directory entries: @= @!signed_word= [word] -32768..32767;@/ @!pxl_dir_type=packed record@/ num_cols,num_rows : sixteen_bits; x_off,y_off : signed_word; addr : [byte(4)] integer; tfm_width : [byte(4)] integer end; @ We need a |file| object to reference the pixel file, and also an array to hold its directory. Furthermore, when a pixel file directory is read in, we need variables |magnification| and |dsize| to hold quantities taken from the last five longwords of the file. @= @!pxl_file: byte_file; @!pxl_dir : packed array [0..127] of pxl_dir_type; @!pxl_block,@!pxl_ptr : integer; @!pxl_size:integer; @!magnification,@!dsize:real; @*Procedures to access the pixel file. The following definitions and declarations provide a means of accessing bytes, words (signed \AM\ unsigned) and longwords in the |pxl_file|. @d pxl_byte(#)==pxl_file^[#] @d pxl_word(#)==(pxl_byte(#+1)+256*pxl_byte(#)) @d pxl_long(#)==(pxl_byte(#+3)+256*(pxl_byte(#+2)+@|256*(pxl_byte(#+1)+@| 256*pxl_byte(#)))) @d must_get(#)== if # = VAX_block_length then begin get(pxl_file,VAX_continue); incr(pxl_block); #:=0 end @p procedure move_to_pxl(n:integer); begin if n div VAX_block_length <> pxl_block then begin pxl_block:=n div VAX_block_length; VAX_find_block(pxl_file,pxl_block+1,VAX_continue) end; pxl_ptr:=n mod VAX_block_length end; @ The |long_pxl| function reads a longword (in Bigendian order) from the |pxl_file| and converts it to correct (signed) VAX longword notation. @p function long_pxl(var index:integer) : integer; begin if pxl_byte(index)<=@"80 then long_pxl:=pxl_long(index) else long_pxl:=pxl_byte(index+3)+256*(pxl_byte(index+2)+256*( pxl_byte(index+1)+256*(pxl_byte(index)-256))); index:=index+4; must_get(index) end; @ The |sign_pxl_word| function reads a signed shortword (two bytes). @p function sign_pxl_word(var index:integer) : integer; begin if pxl_byte(index)>=@"80 then sign_pxl_word:=pxl_word(index)-@"10000 else sign_pxl_word:=pxl_word(index); index:=index+2; must_get(index) end; @ The |word_pxl| function reads an unsigned two-byte word. @p function word_pxl(var index:integer) : integer; begin word_pxl:=pxl_word(index); index:=index+2; must_get(index) end; @*Handle rasters of oversized glyphs. Because of a physical limitation of the LN03, it is not legal to download the glyph of a character which would occupy more than 5,700 bytes. (Details appear under ``\&{Copy one character's rasters}''.) Any such glyph which has not been downloaded will have its |loaded| flag set to |no| in its |glyph_map| entry. When we attempt to add such a character to the output lists, we add instead a separate segment which indicates that it holds a single oversized glyph. When this segment is eventually processed, we output the glyph itself as a bitmap dump, using sixel graphics. To save us having to keep on opening and closing the pixel file containing the glyph, we maintain the ``name'' of the currently open file in a global string, and only open a new file if the glyph required comes from a different \TeX\ font. The global variable |open_file_name| is a textual representation of the full file specification of the currently open pixel file. (Note that this same variable name also appears as a \&{formal parameter} of the |add_txf_to_lnf| function; it is referenced in the $\langle$Open the file containing the glyph bitmaps$\rangle$ W{\mc EB} section, used in the following code, and the aforementioned function.) @= @!open_file_name : file_spec; @ Of course, when we start (and even after we've created the font file(s) for down-loading) there is no pixel file open. Let's indicate that: @= open_file_name:=''; @ Procedure |print_glyph| has to refer to |change_pixel_file|, which is more convenient to declare along with the font downloading procedures. So we'll cheat and put a |forward| declaration here! @p procedure change_pixel_file( TeX_font : integer); forward; @ The procedure |print_glyph| opens the associated pixel file (if necessary) by calling |change_pixel_file|. It firstly outputs absolute positioning commands to establish the correct ``printing'' position (the strange |bitmap_offset| was incorporated when the reference to the glyph was stored in the |segment|), and then outputs suitable escape sequences to locate the printing position at the correct offset relative to that position, as determined by the glyph's first pixel relative to its reference point. If the pixels come from a |pixel_file|, then we can locate the required character glyph through the character directory. In the case of a |packed_file|, it may happen than this character has already been output, and so its position within the file is known; in this case either it was the last character read (has already been dumped as a bitmap), when its glyph will be in the |lnf_file|: otherwise we position |pk_loc| to the correct position in the file. If the character has never been read (|ch_loc=0|) then we read and process characters into the \&{same} part of the |lnf_file| until the desired character has been read. When reading |packed_file|s, we utilize an entry in the |pxl_dir| in preference to the globals |x_off|, etc, so save repetition of many lines of \.{WEB} code. The lines of pixel rasters are then output, six at a time, in sixel graphics format. @p procedure print_glyph(@!glyph_seg : segment; @!y_loc : integer); var @!sixel_line : array [1..6,1..300] of eight_bits; @!i,@!j,@!k,@!l,@!m,@!n,@!len : integer; @!last_sixel,@!matching: integer; @!ch : integer; @ ; begin write_ln(ln3_file); change_pixel_file(glyph_seg.font);@/ write(ln3_file,csi,y_loc:1,'d',csi,glyph_seg.xs:1,'`'); ch:=glyph_seg.ch; with pxl_dir[ch] do begin @ ; @ ; write_ln(ln3_file,dcs,'9;0;1q'); {Prepare to meet a sixel dump!} @; write_ln(ln3_file,st); {Finish sixel dump} end; {with} end; @ We are making use of a record of |pxl_dir| to hold the various quantities regarding the dimensions of the glyph which have been (or are about to be) read from the font file. In the case of an unpacked raster, we need only position the file to the correct longword, but for a packed file, it's now time to start reading the bytes and reconstituting the raster in a free part of the |lnf_file|. @= if pxl_ident=pixel_file then move_to_pxl(addr*4) else begin ras_beg:=2*VAX_block_length; if ch_loc[ch]=0 then @ ; if char_num<>ch then @ ; base:=ras_beg end; @ If the character has not yet been read from the packed font file, we need to read forwards until the character is met. We then are able to access its raster, which will have been created in memory as the character was read. The character cannot be missing or the font download would not have resulted in the |loaded| field having the value |no|. @= repeat ras_len:=0;@/ @; skip_specials until (ch_loc[ch]<>0) or (flag_byte=pk_post); @ If we've previously read the wanted character's glyph, but it has since been superseded by another's, we need to backtrack the packed font file to the correct position and read the raster all over again. @= begin {character not already in |lnf_file|} pk_loc:=ch_loc[ch]; VAX_find_block(pxl_file,(pk_loc div VAX_block_length)+1,VAX_continue); ras_len:=0; flag_byte:=pk_byte; {We know there's no \.{special}s}@/ @; skip_specials end @ Having read the glyph into memory, we now need to position the LN03 to start writing its first sixel at the appropriate position relative to the reference point. We do this by sending Horizontal and Vertical Positioning control sequences, either Relative for forward movement, or Backward when negative movement is necessary. The signs are reversed because the offsets are those of the top left-most pixel of the glyph relative to the reference point. @= if x_off<0 then write(ln3_file,csi,abs(x_off):1,'a') {Horizontal Position Relative} else if x_off>0 then write(ln3_file,csi,x_off:1,'j');{Horizontal Position Backward} if y_off<0 then write(ln3_file,csi,abs(y_off):1,'e') {Vertical Position Relative} else if y_off>0 then write(ln3_file,csi,y_off:1,'k') {Vertical Position Backward} @ This routine outputs the sixel character, |x|, in the optimum sixel encoding making use of repeat counts if the same sixel is repeated more than twice. The current output record is terminated if it's getting too long, and a new one commenced. @= procedure @!put_sixel(x:integer); begin if x = last_sixel then incr(matching) else @ ; if len >=128 then begin write_ln(ln3_file); len:=0 end end @ If we have a new sixel (|x|) which differs from the previous one (|last_sixel|) we have to output the latter the correct number of times. The number of successive calls of |put_sixel| will have been counted in |matching|. After performing the output, we record that |x| has now been ``output'' once. @= begin if matching < 3 then @ else @ ; matching:=1; last_sixel:=x end @ If |last_sixel| has only been output once or twice, we send the explicit copies of it @= begin len:=len+matching; while matching > 0 do begin write(ln3_file,chr(last_sixel)); decr(matching) end end @ On the other hand, it's more efficient to use a sixel repeat count if there are three or more copies of |last_sixel| required. The introducer for such a repeat count is the `\.!' character, which is followed by the ASCII character representation of the count. The actual sixel to be output then follows this count. @= begin write(ln3_file,'!',matching:1,chr(last_sixel)); len:=len+5 end @ To produce the bitmap sixel dump of the glyph, we read the pixels into the array |sixel_line|; this contains 6 rows, each capable of holding 300 |eight_bits|, containing 8 pixels of the character's raster. We index this with |k|, which thus runs |1..6| Variable |i| is used to run through the |num_rows| lines of rasters, whilst |j| indexes the bytes from the pixel file, and into |sixel_line|. @= i:=0; k:=1; while i < num_rows do begin @; incr(k); incr(i); if k > 6 then begin @; k:=1 end end; if k>1 then begin while k <= 6 do begin {Fill in bottom incomplete rows of sixels} for j:=1 to (num_cols+7) div 8 do sixel_line[k,j]:=0; incr(k) end; @ end @ The following section outputs the six rows of pixels in |sixel_line| as $8\times{|num_cols|}$ columns of sixel graphics. The variable |m| runs through the values 128,64,32,..,1, and is used to divide an |eight_bit| to extract the most-significant bit of eight pixels in rows 6..1 (indexed by |n|) of |sixel_line|. Each bit is transferred into the least significant bit of |l|, initially zeroed for each sixel. As each sixel is completed, it is output to the \.{LN3} file. To prevent the output of too many characters to the |ln3_file|, which is of type |text|, the output is broken every 128 bytes. A ``graphics'' end-of-record is inserted after every line of sixels. @= len:=0; last_sixel:=0; matching:=0; for j:=1 to (num_cols+7) div 8 do begin m:=128; while m>0 do begin l:=0; for n:=6 downto 1 do if odd(sixel_line[n,j] div m) then l:=2*l + 1 else l:=2*l; put_sixel(l+63); m:=m div 2 end; end; put_sixel(0); {Flushes the |last_sixel| from the ``buffer''} write_ln(ln3_file,'-') {Sixel `CR-LF'} @ We are now in a position to be able to include the declaration of |ord_text| into the \PASCAL\ generated from the \.{WEB} @p @; @*Output the individual segments forming a page. At the end of each page, we have a number of linked lists containing each of the segments set on any individual ``line'' of the paper. We have to re-order these, such that they are output in the most efficient manner, with appropriate changes of (lnf) font as necessary. When pages are actually being ``set'', |font_in_use[lnf]| will contain either -1, which indicates that the LN03 font required is not currently mapped to a |SGR| (Select Graphic Rendition) code, or a number in the range |0..9|, from which the |SGR| font number to be used to invoke that font as the current one is derived (by adding 10). The array |who_uses| maps these |SGR| indices back to the number of an LN03 font. Finally, the current |SGR| in use is held in |cur_out|, to minimise the number of |SGR| commands that need to be included in the output stream. @= @!font_in_use:array [0..max_lnfonts] of -1..max_SGR; @!who_uses:array [0..max_SGR] of -1..max_lnfonts; @!cur_out:0..max_SGR; @ First of all, we shall require a procedure to output an arbitrary string (most commonly an escape sequence) to the LN03 printer. This will typically be used to activate the |HPA| (Horizontal Position Absolute) control sequence. @p procedure esc_out(str:varying[str_len] of char); {Escape sequence to LN3} begin ln3_cnt:=ln3_cnt+VAX_length(str); write(ln3_file,str) end; @ Then we require a procedure to output as much of a segment as possible to the current line, and delete that section from the segment as stored before returning. One useful extension of VAX-\PASCAL\ that we utilize are the functions |min| and |max|. These each take an arbitrary number of parameters, and return a result, of the same type, that is equal to the smallest or largest (numerically) of the parameters provided. @d VAX_min==@= min@> @d VAX_max==@= max@> @p procedure print_seg; var @!text_len,@!uselen,@!i,@!j,@!ln_font,@!delta_x:integer; @!csi_present:integer; begin with cur_seg^ do begin text_len:=VAX_length(text); uselen:=VAX_min(text_len,max_line-ln3_cnt,max_print-ln3_print); if (uselen>0) or (text_len=0) then {`Use' empty segment} begin {Otherwise, throw segment back if none of it will fit!} if uselen <> text_len then {Text won't all fit!} @; @= if font_in_use[font]<0 then {not currently designated} @= begin font_in_use[who_uses[max_SGR]]:=-1; font_in_use[font]:=max_SGR; who_uses[max_SGR]:=font; esc_out(dcs+'1;'+str_int(10+max_SGR)+'}U0000'+@=dec@>(font,2,2)+ '002SK00GG'+st) end @ We need to output appropriate ISO-standard control sequences to position the segment at the correct position on the current line; if the segment is the first to be output on the current line, this positioning should use the |HPA| (Horizontal Position Absolute) sequence, otherwise the |HPR| (Horizontal Position Relative) sequence is preferred. @= if cur_seg=out_list then {Leftmost segment of line} esc_out(csi+str_int(xs)+'`') { HPA to |xs|} else@/ begin delta_x:=xs-prev_seg^.xe; {Distance from end of next leftward segment} if delta_x>0 then esc_out(csi+str_int(delta_x)+'a') {HPR} else if delta_x<0 then abort('Internal error (-ve delta x)') @:fatal error Internal error ...}{\quad\.{Internal error}@> @:fatal error Internal error negative delta x}{\qquad\.{(-ve delta x)}@> end @ After we have output the first |uselen| characters to the \.{LN3} file, we then check whether that's finished off this segment. If it has, we can discard the segment, but otherwise we perform the split at the predetermined point. @= ln3_cnt:=ln3_cnt+uselen; ln3_print:=ln3_print+uselen; if uselen=text_len then cur_seg:=nil {All of segment used} else @ @ Having previously determined the split point for the segment (including none at all), we now reduce the segment to the part following that point. This is complicated by the fact that we must determine whereabouts we have actually got to on the current line. To do this, we increment the ``current position'' by each character's width, whilst any |HPR| sequences have to be ``read'' in as numbers and added to the count. @= begin i:=1; while i<=uselen do begin if (text[i]<>esc) and (text[i]<>csi) then xs:=xs+ch_widths[font,ord(text[i])] else @ ; incr(i); end; text:=substr(text,uselen+1,text_len-uselen) end @ Whenever we meet a \.{ESC} or \.{CSI} character, we assume that it starts a Horizontal Position Relative command, locate the first digit of its parameter (which will follow a `\.[' after the \.{ESC} or come immediately after the \.{CSI}), and the `\.a' which finishes the control sequence. The characters in between represent a decimal number, which we can ``read'' using a Vax-\PASCAL\ extension. The current placing position is then incremented by this amount. @= begin if text[i]=esc then j:=i+2 else j:=i+1; while text[j]<>'a' do incr(j); if text[i]=esc then VAX_string_read(substr(text,i+2,j-i-2)+' ',delta_x) else VAX_string_read(substr(text,i+1,j-i-1)+' ',delta_x); xs:=xs+delta_x; i:=j; end @ As we shall see later, if the string of characters in a \TeX\ \.{\\special} command commences with the character string |'SX '| (i.e., `old style'), or |'ln03:plotfi[le] '| or |'ln03:sixel '| (`new style'), a segment will be inserted into the page list referencing the appropriate file; when the time comes to process that segment, the following procedure will be invoked. The |text| field of |cur_seg| will be the name of a VAX/VMS file which is assumed to contain an image encoded into DEC's standard ``sixel'' format. This file is then opened, and the records therein copied to the \.{.LN3} file; the |y_position| parameter, and the starting position recorded in field |xs| are used in appropriate escape sequences to position this graphics dump at the desired position on the page before this operation. The name of the file included is reported on the terminal and in the log file. @p procedure Copy_sixel_file(@!y_position:integer); var @!open_stat : integer; {records whether |open| was successful} @!sixel_record, {holds records being copied from included file} @!stat_msg : varying [256] of char; {just holds an error indication} @!sixel_file : text; {The file of graphics details to be copied} begin open(sixel_file,cur_seg^.text,@=readonly@>,@=256@>, @=user_action@>:=file_open,VAX_continue); open_stat := status(sixel_file); if open_stat<>0 then begin case open_stat of 2: stat_msg := 'PAS-E-ERRDUROPE'; 3: stat_msg := 'PAS-E-FILNOTFOU'; 5: stat_msg := 'PAS-E-ACCMETINC'; 6: stat_msg := 'PAS-E-RECLENINC'; 7: stat_msg := 'PAS-E-RECTYPINC'; 8: stat_msg := 'PAS-E-ORDSPEINC'; othercases stat_msg := str_int(open_stat); endcases; error('Couldn''t open \special inclusion: ',cur_seg^.text, ' - ignored (status=',stat_msg,')'); @:Error: Couldn't open special inclusion}{\quad\.{Couldn't open \\special inclusion}@> end else begin monitor(' ('+def_file_name); print_ln('Sixel graphics included from: ',def_file_name); @.Sixel graphics included...@> reset(sixel_file); write(ln3_file,csi,y_position:1,'d',csi,cur_seg^.xs:1,'`'); while not eof(sixel_file) do begin read_ln(sixel_file,sixel_record); write_ln(ln3_file,sixel_record) end; close(sixel_file,@=disposition:=save@>,VAX_continue); monitor(') '); write_ln(ln3_file,SSU_pixel); {Make sure that we still have pixel SSUs} end; end; @ Whilst traversing the lists, we require some additional variables: During this output phase, we split the lines of output such that no line contains more than |max_line| characters altogether, of which no more than |max_print| may be printable ones. The counters |ln3_cnt| and |ln3_print| permit us to exercise this control. The |boolean| variable |top_of_page| controls the inclusion of a |FF| character in the output file to split the pages. If the page contains glyphs in both of the possible orientations, this is recorded in |two_orientations|. Segments corresponding to the orientation not currently being output are shuffled off onto the |deferred_list| whilst this processing takes place. @= @!out_list,@!prev_seg,@!right_seg,@!left_seg:^segment; @!x_left,@!x_max:integer; @!ln3_cnt,@!ln3_print:integer; @!still_fits,top_of_page:boolean; @!two_orientations:boolean; @!deferred_list:^segment; @ The actual traversing of the lists takes place here; since we now support the concept of mixing orientations on the one page, it's sometimes necessary to traverse the lists twice! @= repeat two_orientations := false; {assume that we \&{don't} need two passes!} for k:=y_top to y_bot do {Cycle through each possible list} if seg_list[k] <> nil then begin out_list:=nil; deferred_list:=nil; repeat {until we've emptied current line} @ ; @; @ ; @ ; until seg_list[k]=nil; if deferred_list<>nil then two_orientations:=true; seg_list[k]:=deferred_list end; if two_orientations then {prepare for second (and last) pass} begin if cur_orient=landscape then new_orient:=portrait else new_orient:=landscape; @ end; until not two_orientations; if lost_seg <> nil then begin cur_seg:=lost_seg; finish_seg {Output remaining errors} end; @ If, when outputting stored information in |cur_orient|, we encounter some segment(s) in the opposite orientation, we save them in a subsidiary list, and stuff them back into |seg_list[k]| after processing the current row. @= prev_seg:=nil; cur_seg:=seg_list[k]; repeat right_seg:=cur_seg^.next; {we know that |cur_seg<>nil|} if cur_seg^.direction<>cur_orient then begin if prev_seg<>nil then prev_seg^.next:=right_seg {in middle of list} else seg_list[k]:=right_seg; {at beginning of list} cur_seg^.next:=deferred_list; {add segment to front of deferred list} deferred_list:=cur_seg end else prev_seg:=cur_seg; {latest non-empty node} cur_seg:=right_seg {next node on |seg_list|} until cur_seg=nil @ Now that the program can support landscape and portrait orientation within the same document, we need a method of telling the LN03 to switch between the modes. This code is called before output of the first object to the page, if |new_orient<>orientation|, and again at the end of the page, to reset to the default |orientation| for the next page. @= if new_orient<>cur_orient then begin if new_orient=landscape then begin write_ln(ln3_file,csi,PFS_landscape); page_len:=paper_wid+page_x_min; page_wid:=paper_ht+y_land_min; y_min:=page_x_min; x_min:=y_land_min end else begin write_ln(ln3_file,csi,PFS_portrait); page_len:=paper_ht+y_port_min; page_wid:=paper_wid+page_x_min; y_min:=y_port_min; x_min:=page_x_min end; cur_orient:=new_orient end @ When handling characters from a virtual font, the situation can arise in which a totally empty segment (one that contains no text) has been added to the list. Such segments can be discarded! Other segments can appear to contain no text, but actually reference a single oversized glyph, which must therefore be output as a bitmap. @= prev_seg:=nil; cur_seg:=seg_list[k]; if cur_seg<>nil then repeat right_seg:=cur_seg^.next; {we know that |cur_seg<>nil|} if (VAX_length(cur_seg^.text)=0) or (cur_seg^.seg_type<>glyph_string) then begin @; if prev_seg<>nil then prev_seg^.next:=right_seg {in middle of list} else seg_list[k]:=right_seg; {at beginning of list} cur_seg^.next:=free_seg_list; {add segment to front of free list} free_seg_list:=cur_seg end else prev_seg:=cur_seg; {latest non-empty node} cur_seg:=right_seg {next node on |seg_list|} until cur_seg=nil @ Character glyphs which were too large to be downloaded into the LN03's internal fonts have the |seg_type| field of the segment set to |bitmap|; when we meet such a segment, we output the bitmap immediately. Similarly, sixel dumps referenced in \.{\\special} commands have the value |sixel_dump| for this field, and |Copy_sixel_file| is called to copy the referenced file. @= case cur_seg^.seg_type of bitmap: print_glyph(cur_seg^,k); sixel_dump: Copy_sixel_file(k); othercases do_nothing; endcases @ For as many as possible of all the segments which belong at the current absolute vertical position, we construct a list in which none of the segments overlap each other. We are then able to output these in one sequence which will contain only characters and \&{forward} horizontal movement commands. @= x_left:=1000000000; {A very high value; ensure |right_seg| \AM\ |left_seg| get set} repeat prev_seg:=nil; right_seg:=nil; x_max:=-1; cur_seg:=seg_list[k]; @ ; @ until right_seg = nil; @ We start by locating the segment (of those available) which extends furthest to the right, and also the one which precedes it. These are then pointed to by |right_seg| and |prev_seg|, respectively. @= while cur_seg<>nil do begin if (cur_seg^.xe > x_max) and (cur_seg^.xe <= x_left) then begin x_max:=cur_seg^.xe; right_seg:=cur_seg; {Points to rightmost segment} left_seg:=prev_seg {Points to first of at least 2, or is |nil|} end; prev_seg:=cur_seg; cur_seg:=cur_seg^.next; end @ At this point, we have |right_seg| pointing to the segment which extends furthest to the right, whilst |left_seg| will point to the segment immediately before this (which may, or may not, overlap |right_seg|). We then put it at the front of the |out_list|; further non-overlapping segments will later be placed in front of it. We ensure that the next |right_seg| that we select will not overlap by noting the left-most extent of the segment we've just put on the |out_list|. @= if right_seg <> nil then begin @ ; right_seg^.next:=out_list; {Put rightmost at start of |out_list|} out_list:=right_seg; x_left:=right_seg^.xs {Don't consider anything right of its LHS} end @ If |left_seg| is |nil| then |right_seg| is the first segment associated with the line, and so we remove it from the front of the list. Otherwise, we detach it from the middle of the list. @= if left_seg = nil then seg_list[k]:=seg_list[k]^.next else left_seg^.next:=left_seg^.next^.next @ Once we've constructed the list out of as many segments as possible for the current ``line'', we can output them all and go back for some more. @= while out_list <> nil do begin ln3_cnt:=0; ln3_print:=0; {Nothing as yet on line} esc_out(csi+str_int(k)+'d'); {Move to correct line on paper} cur_seg:=out_list; prev_seg:=nil; still_fits:=true; @ ; @; write_ln(ln3_file) end @ Once we have a single segment ready for output, we call |print_seg| to output as much of it as will fit in the space remaining in the output buffer. If this is the whole segment, we can then repeat the process with the next segment in the |out_list|, noting the address of this segment so that it may be returned to the |free_seg_list|. On the other hand, if |print_seg| decided to split the segment, we break out of the loop. @= while (cur_seg <> nil) and still_fits do begin right_seg:=cur_seg; print_seg; {Will leave |cur_seg=nil| if all text output} if cur_seg = nil then begin cur_seg:=right_seg^.next; {Next segment for output} prev_seg:=right_seg end else still_fits:=false; end; @ Once a segment (or group of segments) have been output, it is safe to discard them. We do this by returning them to the |free_seg_list|, so that new nodes will be allocated therefrom in preference to using |new|. The first such freed segment is that at the begining of the |out_list|, whilst the last is that most recently output, noted in |prev_seg|. @= if prev_seg <> nil then begin prev_seg^.next:=free_seg_list; free_seg_list:=out_list; out_list:=cur_seg end @ When \.{DVItoLN03} first starts, any output produced will be printed on the first page. Subsequently, it is necessary to insert a form-feed character to terminate the preceding page before any output for another page may commence. The |boolean| |top_of_page| is used to keep track of whether the form-feed is required. This code is called whenever something is about to be output to the page: if the orientation is incorrect at that time, the appropriate change is made. It's also the appropriate time to change paper trays, and select the printing mode. @= if top_of_page then begin top_of_page:=false; if pages_printed>0 then write_ln(ln3_file,FF); @; @ end; @ @ With the DEClaser~2200, it is possible (through the \.{FIRST} and \.{REST} selectors of the \.{/FEED\_TRAY} qualifier) to arrange for the second and subsequent sheets to be printed on paper from a different input tray than that used for the first sheet. The code in this module effects this tray change, when the second sheet to be printed is first met: it firstly determines whether we're printing a \\{recto} page, when duplexing (this information is later used for selecting the printing mode for the current page). The ``cascade'' of |if| clauses is to reduce the processing overhead except for the first three pages printed. The macro here is used to change trays; the control sequence ending in |'!v'| is the |DECASTC| (Automatic Sheet-feeder Tray Control) command, to select the desired tray. @d select_tray(#)==case # of default_tray: do_nothing; othercases write(ln3_file,csi,#:1,'!v') endcases @d odd_page==(odd(count[0])or(count[0]=0)) @d recto_page==(duplex_by_page_numbers and odd_page) or (not duplex_by_page_numbers and not odd(pages_printed)) @d starting_second_sheet== (((print_modetrue_duplex_tumbled)) and (pages_printed=1)) {non-duplex; change at second page} or (((print_mode=true_duplex_normal)or(print_mode=true_duplex_tumbled)) and this_page_recto) {duplex; change at second sheet} @= this_page_recto:=recto_page; if pages_printed<=2 then if pages_printed>0 then if starting_second_sheet then if following_tray<>first_tray then select_tray(following_tray) @ When we are printing in duplex mode (even simulated duplex, in response to the \.{MASTER} option of the \.{/PRINT\_MODE} qualifier), we have to arrange to leave a page blank if two \\{recto} or two \\{verso} pages follow each other. This information will already have been put into |this_page_recto|, when the paper tray was selected. For efficiency reasons, when printing in a duplex mode on the DEClaser~2200 (LN06), we switch into simplex mode for a \\{recto} page which is followed by another such: this information was recorded in the array |blank_follows| when the starting page was being located. This speeds up processing, because the printer doesn't need to perform the mechanical reversal of the page only to find that is has nothing to be printed upon it. @= @; if print_mode>=true_duplex_normal then begin if last_page_recto=this_page_recto then begin if (print_mode=ln03_master) or not this_page_recto then write(ln3_file,' ',FF) {Eject an extra page on LN03, or make \\{recto} page blank} end else if (print_mode0) and duplex_by_page_numbers and this_page_recto then if blank_follows[ptr_blanks]=cur_loc_after_bop then begin {This page has blank \\{verso} side} write(ln3_file,csi,true_simplex_normal:1,' x'); duplex_to_be_reset:=true; decr(ptr_blanks) end end; last_page_recto:=this_page_recto; @ If the previous page was printed in simplex mode, because it was a \\{recto} page to be followed by another one then when we meet that next \\{recto} page we have a look to see if the following page is yet another \\{recto} one. If not, we have to reset the required duplexing mode. @= if duplex_to_be_reset then {previous page was temporary excursion into simplex mode} if ptr_blanks>0 then if blank_follows[ptr_blanks]=cur_loc_after_bop then begin decr(ptr_blanks); {and remain in simplex mode} if print_mode>=duplex_master_normal then write(ln3_file,' ',FF) {eject blank page for \\{verso} master} end else begin write(ln3_file,csi,print_mode:1,' x'); duplex_to_be_reset:=false end @ When we come to the end of the document when printing in any duplexing mode, we may just have printed a \\{recto} page; we must either eject the second side as blank (in true duplex modes) or print an extra blank page (in any of the master modes). This is accomplished by printing a form-feed, and following it with a space; the form-feed on its own would just result in the printer symbiont suppressing that which it would have inserted, but the space fools it into sending a second form-feed itself. @= if (print_mode>=true_duplex_normal) and last_page_recto then if (print_mode>=duplex_master_normal) or not duplex_to_be_reset then write(ln3_file,FF,' ') @ Here are the variables required to control \\{recto}/\\{verso} printing @= @!last_page_recto:boolean; {|true| if previous page was \\{recto}} @!this_page_recto:boolean; {used to save multiple evaluation of |recto_page|} @!pages_printed:integer; {counts each page generated for \.{/noduplex\_by\_page\_numbers}} @!duplex_to_be_reset:boolean; {Used to reset any duplex mode after one sheet has been printed in simplex} @!cur_loc_after_bop:integer; {Records |cur_loc| after the current page's |bop| command was processed} @ These variables have to be initialized, of course @== pages_printed:=0; last_page_recto:=false; duplex_to_be_reset:=false; @ When we first start writing the output file, we have to add commands to tell the DEClaser printers from which tray to take the first sheet of paper, and which printing mode to use. The control sequence ending in |' x'| is the |DECSPDM| (Set Duplex Print Mode) command. @ @ If we are starting the download of any except the first \TeX\ font, it may be that the previous \TeX\ font was the last loaded into the current LN03 font, which means we will be starting a new LN03 font here. Therefore we need to separate the two LN03 fonts from each other: the comma (`\.,') character is used by the LN03 for this purpose. @= if (txf_to_lnf[k]<>txf_to_lnf[txf_ord[m-1]]) then write_ln(ln3_file,',') @ The function |add_txf_to_lnf| performs all the work of creating an LN03 font from the rasters in the font file. The result returned by this function gives the total number of bytes created in the LN03 font for the given \TeX\ file. @= total_rasters:=total_rasters+ add_txf_to_lnf(txf_to_lnf[k],k,file_name,pxl_ident) @ We may have both packed and unpacked files available from which to take pixel bitmaps, so we require some indication of what type of file is actually available. @= @!pixel_types = (@!unavailable,@!packed_file,@!pixel_file); @ The user has the option of using either pixel files (\.{.PXL}) and/or packed pixel files (\.{.PK}) as the source of font raster information. There is the further option of the directories in which these files are held being either ``flat'' (with all font files of one class in the same directory) or ``nested'' (where separate sub-directories are provided for each different magnification). File naming conventions used are illustrated by the following examples, which show the names under which \.{cmr10} will be found at magsteps 0--2: \yskip\centerline{\vbox{\offinterlineskip \hrule \halign{&\vrule#&\strut\hfil#\hfil&\quad\tt#\quad&\quad\tt#\quad& \quad\tt#\quad&\quad\tt#\quad&\vrule#\cr height2pt&\multispan5 &height2pt\cr &\omit\strut Magnif-\hfil&\multispan2\hfil Packed Pixels\hfil& \multispan2\hfil Unpacked Pixels\hfil&\cr &\omit\strut\hfil ication&\hfil\rm Flat\hfil&\hfil\rm Nested\hfil& \hfil\rm Flat\hfil&\hfil\rm Nested\hfil&\cr height2pt&\multispan5 &height2pt\cr \noalign{\hrule} height2pt&\multispan5 &height2pt\cr &0&CMR10.300PK&[300]CMR10.PK&CMR10.1500PXL&[1500]CMR10.PXL&\cr &$1\over2$&CMR10.329PK&[329]CMR10.PK&CMR10.1643PXL&[1643]CMR10.PXL&\cr &1&CMR10.360PK&[360]CMR10.PK&CMR10.1800PXL&[1800]CMR10.PXL&\cr &2&CMR10.432PK&[432]CMR10.PK&CMR10.2160PXL&[2160]CMR10.PXL&\cr height2pt&\multispan5 &height2pt\cr} \hrule }} The program determines which class(es) of files are available from the qualifiers \.{/PXL\_FONT\_DIRECTORY} (for the unpacked files) and \.{/PK\_FONT\_DIRECTORY} (for the packed files). At least one of these qualifiers must be present; if the qualifier provided is a VMS logical name, it has been translated repeatedly until a VMS directory specification resulted. Examination of (the first translation of) this specification then determines whether the flat or nested structure is being used: if the directory specification ends with the characters `\.{.]}' it indicates that the raster files will be found in sub-directories, whilst absence of the `\..' indicates a flat structure. The variables |pk_rooted| and |pxl_rooted| will already have been set to reflect this. Whether packed or unpacked pixel files are used, it is necessary to compute a number which relates the magnification to either the sub-directory name or the file extension. Since actual file names may well have been computed using low-accuracy (possibly integer) arithmetic by operating system commands, the possibility exists that the numbers may have been truncated to the ``wrong'' integer compared with those that \.{DVItoLN03} computes; therefore, we always arrange to look for the files using both the computed number and those $\pm1$ from it. The variable |dir_off| is used to compute the offset from the central figure, which is held in |p|. The variables |file_stat| and |find_ctxt| are used in conjunction with Vax/VMS system calls to determine whether the requested file exists; |found_file| takes the values $-1$, indicating that the file wasn't found, or $+1$ if it is found. It also can take the value $0$ whilst the search is proceding, so a simple boolean cannot be used instead. The following procedure converts the \TeX\ font referenced by |TeX_font| into the correct strings for the directory and file name under which \.{DVItoLN03} expects to find the pixel file. @p procedure name_pxl_file ( @!TeX_font : integer;@/ var @!open_file_name: file_spec; var @!what_it_was: pixel_types); var @!p,@!dir_off,@!file_stat,@!find_ctxt,@!found_file : integer;@/ @!found_name,@!expected_file_spec : file_spec; begin found_file := -1; expected_file_spec:='';@/ @;@/ if found_file<0 then @; if found_file<0 then begin warning('cannot find file ',expected_file_spec); @:Warning: cannot find file}{\quad\.{cannot find file}@> what_it_was:=unavailable end end; @ We only look for packed pixel files if the \.{/PK\_FONT\_DIRECTORY} qualifier was present, in which case its value will have been placed into the string |tex_pk_font|. If the final characters of (the first logical name translation of) this string were `\.{.]}', then a nested directory structure is assumed (as indicated in |pk_rooted|), and the file is sought in the form \hbox{\meta{disk}\meta{directory}\.[\meta{size}\.]\meta{name}\.{.PK}}. Alternatively, if the directory is flat, the filename sought will be \hbox{\meta{disk}\meta{directory}\meta{name}\..\meta{size}\.{PK}}. Unfortunately, it is necessary to reconstruct the desired string for each offset tried. The \meta{size} is effectively computed from 1.5 times the font design size compared with the \TeX ware default of 200 pixels/inch, yielding 300 for unmagnified fonts. @d append_name==for n:=font_name[TeX_font] to font_name[TeX_font+1]-1 do open_file_name:=open_file_name+xchr[names[n]] @d flat_file_spec(#)==begin open_file_name := # ; append_name; open_file_name:=open_file_name+'.'+str_int(p+dir_off) end @d rooted_file_spec(#)==begin open_file_name:= # + '[' + str_int(p+dir_off) + ']'; append_name; open_file_name:=open_file_name+'.' end @== if tex_pk_font.length > 0 then begin dir_off:=0; p:=round(((resolution/1000)*mag*font_scaled_size[TeX_font])/font_design_size[TeX_font]); repeat if not pk_rooted then flat_file_spec(tex_pk_font) else rooted_file_spec(tex_pk_font); open_file_name:=open_file_name+'PK'; @ until found_file<>0; if found_file>0 then begin open_file_name:=found_name; what_it_was:=packed_file end end; @ To determine the correct file of pixels to be read, the quantity |p| is computed; since it is a \TeX ware convention that all pixel files are assumed (at magnification=1000) to be at a |resolution| of 200 dots/inch, and the LN03's |resolution| is \&{300} dots/inch, then an ordinary unmagnified font will be sought with a $\langle$\\{size}$\rangle$ of \.{1500}, whilst one magnified \.{\BS magstep 1} will have a $\langle$\\{size}$\rangle$ of \.{1800}, \\{etc.} Computation of |p| yields the appropriate value for the Vax/VMS directory or file type to be used, and |VAX_lib_find_file| is used to verify that the file exists; to allow for rounding errors, the directories |p|$\pm1$ are also sought. @== if tex_pxl_font.length > 0 then begin dir_off:=0; p:=round(((resolution/200)*mag*font_scaled_size[TeX_font])/font_design_size[TeX_font]); repeat if not pxl_rooted then flat_file_spec(tex_pxl_font) else rooted_file_spec(tex_pxl_font); open_file_name:=open_file_name+'PXL'; @ until found_file<>0; if found_file>0 then begin open_file_name:=found_name; what_it_was:=pixel_file end end @ Now that we've got a full file specification (barring the version number) in |open_file_name|, we call upon the Vax/VMS system services to try to locate the file. Before doing this, we make a note of the file specification if it's that for the default file name (with |dir_off = 0|), and we haven't previously noted a file name. The find\_file system service requires a context variable (because it is capable of finding a number of files matching a `wild-carded' file specification), but we're not interested in that; therefore we always set |find_ctxt| to zero before searching. \&{N.B.} This may not be written as the constant |0| in the procedure call, because the system service writes a value back to this variable. The system service returns an odd status if the search is successful; otherwise we change |dir_off| to a suitable new value, unless we've already tried |p| and $p\pm1$, in which case we indicate the absence of the file by setting |found_file = -1|. @= if (dir_off=0) and (expected_file_spec='') then expected_file_spec:=open_file_name; find_ctxt:=0; file_stat:=VAX_lib_find_file(open_file_name,found_name,find_ctxt); found_file:=0; if odd(file_stat) then found_file:=1 else begin if dir_off=1 then found_file:=-1; if dir_off=-1 then dir_off:=1 else dir_off:=-1 end @ Although up to |max_lnfonts| may be downloaded by this program, the LN03 is only capable of accessing any ten of these at any one time; it accomplishes this by allocating the |SGR| (Select Graphic Rendition) designators |10..19| to the first ten fonts downloaded. Fonts are designated by means of their 16-character internal font identifiers, allocated during the generation of each font. @= for m:=0 to VAX_min(max_SGR,next_lnf-1) do {Match first 10 fonts to |SGR| designators} begin font_in_use[m]:=m; who_uses[m]:=m; write_ln(ln3_file,dcs,'1;1',m:1,'}U00000',m:1,'002SK00GG',st) end; write_ln(ln3_file,csi,'10m'); {|SGR| 10 --- select most used font} cur_out:=0 {We won't need to |SGR| if first output uses |SGR| 10} @*Format of Virtual Fonts. The following description is cribbed almost verbatim from Knuth's description of \.{VFtoVP}. @^Knuth, D.~E.@> @:VFtoVP program}{\.{VFtoVP} program@> The idea behind \.{VF} files is that a general interface mechanism is needed to switch between the myriad font layouts provided by different suppliers of typesetting equipment. Without such a mechanism, people must go to great lengths writing inscrutable macros whenever they want to use typesetting conventions based on one font layout in connection with actual fonts that have another layout. This puts an extra burden on the typesetting system, interfering with the other things it needs to do (like kerning, hyphenation, and ligature formation). These difficulties go away when we have a ``virtual font,'' i.e., a font that exists in a logical sense but not a physical sense. A typesetting system like \TeX\ can do its job without knowing where the actual characters come from; a device driver can then do its job by letting a \.{VF} file tell what actual characters correspond to the characters \TeX\ imagined were present. The actual characters can be shifted and/or magnified and/or combined with other characters from many different fonts. A virtual font can even make use of characters from virtual fonts, including itself. Virtual fonts also allow convenient character substitutions for proofreading purposes, when fonts designed for one output device are unavailable on another. @ A \.{VF} file is organized as a stream of 8-bit bytes, using conventions borrowed from \.{DVI} and \.{PK} files. Thus, a device driver that knows about \.{DVI} and \.{PK} format will already contain most of the mechanisms necessary to process \.{VF} files. A preamble appears at the beginning, followed by a sequence of character definitions, followed by a postamble. More precisely, the first byte of every \.{VF} file must be the first byte of the following ``preamble command'': \yskip\hang|pre| 247 |i[1]| |k[1]| |x[k]| |cs[4]| |ds[4]|. Here |i| is the identification byte of \.{VF}, currently 202. The string |x| is merely a comment, usually indicating the source of the \.{VF} file. Parameters |cs| and |ds| are respectively the check sum and the design size of the virtual font; they should match the first two words in the header of the \.{TFM} file for the virtual font. \yskip After the |pre| command, the preamble continues with font definitions; every font needed to specify ``actual'' characters in later \\{set\_char} commands is defined here. The font definitions are exactly the same in \.{VF} files as they are in \.{DVI} files, except that the scaled size |s| is relative and the design size |d| is absolute: \yskip\hang|fnt_def1| 243 |k[1]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. Define font |k|, where |0<=k<256|. \yskip\hang|@!fnt_def2| 244 |k[2]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. Define font |k|, where |0<=k<65536|. \yskip\hang|@!fnt_def3| 245 |k[3]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. Define font |k|, where |0<=k<@t$2^{24}$@>|. \yskip\hang|@!fnt_def4| 246 |k[4]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. Define font |k|, where |@t$-2^{31}$@><=k<@t$2^{31}$@>|. \yskip\noindent These font numbers |k| are ``local''; they have no relation to font numbers defined in the \.{DVI} file that uses this virtual font. The dimension~|s|, which represents the scaled size of the local font being defined, is a |fix_word| relative to the design size of the virtual font. Thus if the local font is to be used at the same size as the design size of the virtual font itself, |s| will be the integer value $2^{20}$. The value of |s| must be positive and less than $2^{24}$ (thus less than 16 when considered as a |fix_word|). The dimension~|d| is a |fix_word| in units of printer's points; hence it is identical to the design size found in the corresponding \.{TFM} file. @d vf_id_byte=202 @= @!vf_file:byte_file; @ The preamble is followed by zero or more character packets, where each character packet begins with a byte that is $<243$. Character packets have two formats, one long and one short: \yskip\hang|long_char| 242 |pl[4]| |cc[4]| |tfm[4]| |dvi[pl]|. This long form specifies a virtual character in the general case. \yskip\hang|short_char0..short_char241| |pl[1]| |cc[1]| |tfm[3]| |dvi[pl]|. This short form specifies a virtual character in the common case when |0<=pl<242| and |0<=cc<256| and $0\le|tfm|<2^{24}$. \yskip\noindent Here |pl| denotes the packet length following the |tfm| value; |cc| is the character code; and |tfm| is the character width copied from the \.{TFM} file for this virtual font. There should be at most one character packet having any given |cc| code. The |dvi| bytes are a sequence of complete \.{DVI} commands, properly nested with respect to |push| and |pop|. All \.{DVI} operations are permitted except |bop|, |eop|, and commands with opcodes |>=243|. Font selection commands (|fnt_num0| through |fnt4|) must refer to fonts defined in the preamble. Dimensions that appear in the \.{DVI} instructions are analogous to |fix_word| quantities; i.e., they are integer multiples of $2^{-20}$ times the design size of the virtual font. For example, if the virtual font has design size $10\,$pt, the \.{DVI} command to move down $5\,$pt would be a \\{down} instruction with parameter $2^{19}$. The virtual font itself might be used at a different size, say $12\,$pt; then that \\{down} instruction would move down $6\,$pt instead. Each dimension must be less than $2^{24}$ in absolute value. Device drivers processing \.{VF} files treat the sequences of |dvi| bytes as subroutines or macros, implicitly enclosing them with |push| and |pop|. Each subroutine begins with |w=x=y=z=0|, and with current font~|f| the number of the first-defined in the preamble (undefined if there's no such font). After the |dvi| commands have been performed, the |h| and~|v| position registers of \.{DVI} format and the current font~|f| are restored to their former values; then, if the subroutine has been invoked by a \\{set\_char} or \\{set} command, |h|~is increased by the \.{TFM} width (properly scaled)---just as if a simple character had been typeset. (The following commands, already defined as macros with respect to the \.{DVI} file, have the same interpretation when they appear in a virtual font file: \hang |set_char_0|, |set1|, |set_rule|, |put1|, |put_rule|, |nop|, |push|, |pop|, |right1|, |w0|, |w1|, |x0|, |x1|, |down1|, |y0|, |y1|, |z0|, |z1|, |fnt_num_0|, |fnt1|, |xxx1|, |xxx4|, |fnt_def1|, |pre|, |post|.) @d long_char=242 {\.{VF} command for general character packet} @d improper_DVI_for_VF==139,140,243,244,245,246,247,248,249,250,251,252, 253,254,255 @ The character packets are followed by a trivial postamble, consisting of one or more bytes all equal to |post| (248). The total number of bytes in the file should be a multiple of~4. @ Here are some functions, analogous to those for the |dvi_file|, for reading one or more bytes from the |vf_file|. Note that variable |flushing| is used to suppress the warning (when |eof| is met on the |vf_file|) if bytes are merely being read to discard the postamble. @p procedure read_vf_file(var x:eight_bits); label 9999; begin while vf_count >= VAX_block_length do begin get(vf_file,VAX_continue); vf_count:=vf_count-VAX_block_length; incr(vf_block); if eof(vf_file) then begin if not flushing then warning('VF file ended without postamble!'); @:Warning: VF file ended without postamble}{\quad\.{VF file ended without postamble}@> x := post; goto 9999 end end; x := vf_file^[vf_count]; incr(vf_count); incr(vf_loc); 9999: end; @# function @!vf_one: integer; {returns the next byte, unsigned} var @!b: eight_bits; begin read_vf_file(b); vf_one:=b end; @# function vf_byte:integer; {returns the next byte, signed} var b:eight_bits; begin read_vf_file(b); if b<128 then vf_byte:=b @+ else vf_byte:=b-256; end; @# function vf_two:integer; {returns the next two bytes, unsigned} var a,@!b:eight_bits; begin read_vf_file(a); read_vf_file(b); vf_two:=a*256+b; end; @# function vf_pair:integer; {returns the next two bytes, signed} var a,@!b:eight_bits; begin read_vf_file(a); read_vf_file(b); if a<128 then vf_pair:=a*256+b else vf_pair:=(a-256)*256+b; end; @# function vf_three:integer; {returns the next three bytes, unsigned} var a,@!b,@!c:eight_bits; begin read_vf_file(a); read_vf_file(b); read_vf_file(c); vf_three:=(a*256+b)*256+c; end; @# function vf_trio:integer; {returns the next three bytes, signed} var a,@!b,@!c:eight_bits; begin read_vf_file(a); read_vf_file(b); read_vf_file(c); if a<128 then vf_trio:=(a*256+b)*256+c else vf_trio:=((a-256)*256+b)*256+c; end; @# function vf_quad:integer; {returns the next four bytes, signed} var a,@!b,@!c,@!d:eight_bits; begin read_vf_file(a); read_vf_file(b); read_vf_file(c); read_vf_file(d); if a<128 then vf_quad:=((a*256+b)*256+c)*256+d else vf_quad:=(((a-256)*256+b)*256+c)*256+d; end; @ Here are the globals required to manipulate the VF file. @= @!vf_count, @!vf_block, @!vf_loc : integer; @!flushing : boolean; @*Handling Virtual Fonts. Before we look around the fonts, we'd better see if any of the fonts so far loaded are virtual ones; this can be determined by whether a \.{VF} file exists for the given font. We use a |while| loop, because the analysis of a virtual font most probably will result in the addition of one or more new fonts, some of which may themselves be virtual; to prevent infinite recursion with a self-referential font, such additional fonts are only loaded if glyphs within the font are activated. @= TeX_font:=0; vf_ptr:=0; while TeX_font < nf do {Currently defined fonts, and any this defines anew} begin if font_type[TeX_font]=wanted then @; incr(TeX_font) end; @ This module determines the name of the \.{VF} file, if it exists, corresponding to the current \TeX\ font. @= if not no_virt_support then begin file_name:=''; for n:=font_name[TeX_font] to font_name[TeX_font+1]-1 do if (names[n]>="a") and (names[n]<="z") then file_name:=file_name+xchr[names[n]+"A"-"a"] else file_name:=file_name+xchr[names[n]]; open(vf_file,file_name,@=readonly@>,@=access_method@>:=@=direct@>, @=user_action@>:=file_open,@=default@>:=tex_virtual+'.VF;0', VAX_continue); file_stat:=status(vf_file); if (file_stat<>0) and (file_stat<>3) then begin case file_stat of 2: file_message := 'PAS-E-ERRDUROPE'; 5: file_message := 'PAS-E-ACCMETINC'; 6: file_message := 'PAS-E-RECLENINC'; 7: file_message := 'PAS-E-RECTYPINC'; 8: file_message := 'PAS-E-ORDSPEINC'; othercases file_message := str_int(file_stat); endcases; abort('Couldn''t open virtual font ',file_name,'. Status=',file_message); @:fatal error Couldn't open virtual font}{\quad\.{Couldn't open virtual font}@> end else if file_stat=0 then begin reset(vf_file); vf_block:=0; vf_count:=0; vf_loc:=0; font_type[TeX_font]:=virtual; open_file_name:=def_file_name;@/ @;@/ @;@/ print_ln(' [virtual]'); write_ln(term_out,' is virtual',crlf);@/ read_VF;@/ close(vf_file,@=disposition@>:=@=save@>,VAX_continue); end; end @ Globals for storing virtual font files: unlike \.{VFtoVP}, @.VFtoVP@> we don't read the \\{entire} file into memory. We do, however, store those |dvi| bytes that belong to some wanted character packets, but only for glyphs which are more complex than simply imaging a character from some other font; this is efficient, and also feasible, since VF files are never very large (unlike physical font files!) Much of this code is, once again, lifted from \.{VFtoVP}. The array |vf| is global, along with the pointer into it, |vf_ptr|, because we accumulate therein all setting sequences read from all virtual font files processed. Most of the other variables are only required when actually reading a particular virtual font, so are local to |read_VF|. \yskip\hang |vf_take| When |dvi| bytes are to be ``read'' from array |vf|, the variable |vf_take| will be non-negative and hence index an element of the array; during normal input from the \.{DVI} file, a negative value ensures that input is taken from the file itself. \yskip\hang |TeX_font| will always point to the current virtual font being processed, amongst all the fonts (both virtual and physical) that are in the usual data structures. This global variable has the same name as parameters of the procedures |name_pxl_file| and |add_txf_to_lnf|, in order that common sections of code may be shared from the W{\mc EB}. \yskip\hang |font_type| records whether a font is |virtual| or physical (in which case the value |wanted| will have been set when the font's \.{TFM} file was first read. @= @!vf:packed array[0..vf_size] of eight_bits; {the \.{VF} input data goes here} @!vf_ptr:0..vf_size; {first unused location in |vf|} @!vf_take:-1..vf_size; {next byte to be ``read'' from |vf|} @!TeX_font:0..max_fonts; {index into font metrics database for current virtual} @!font_type: array [0..max_fonts] of download_status; @ To ensure that we start by reading genuine |dvi| bytes from the \.{DVI} file, we must make |vf_take| negative. @== vf_take:=-1; @ Here's the main procedure for reading virtual font files. It exists as a procedure purely in order that local variable declarations can be made to cater for those code sections which are also invoked within |define_font|. When we encounter a font definition in the \.{VF} file, we read its associated \.{TFM} file in the usual fashion; we associate a ``\TeX\ font number'' with this font, determined from the highest font previously used within the previous actual or virtual fonts, via the variable |font_base|. @p procedure read_VF; var @!f: 0..max_fonts; {Local index} @!p: integer; {length of the area/directory spec} @!n: integer; {length of the font name proper} @!c,@!q,@!d: integer; {check sum, scaled size, design size} @!r: 0..name_length; {index into |cur_name|} @!j,@!k: 0..name_size; {indices into |names|} @!mismatch: boolean; {names differ} @!font_base: 0..max_fonts; {offset for local font numbers} @!z,@!alpha,@!beta: integer; {to facilitate |fix_word| multiplications} @!font_ref:array[0..max_fonts] of integer; {local font numbers} @!font_ext:array[0..max_fonts] of integer; {``external'' font numbers} @!font_ptr:0..max_fonts; {number of local fonts} @!packet_start:array[0..255] of 0..vf_size; {character packet boundaries} @!packet_found:boolean; {at least one packet has appeared} @!temp_byte:eight_bits;@+@!temp_int:integer; {registers for simple calculations} @!pl:integer; {packet length} @@; begin font_base:=highest_font+1; flushing:=false;@/ @; end; @ Again we cautiously verify that we've been given decent data. @d vf_abort(#)== abort('(VF file) ',#,crlf) @!@:fatal error VF file ...}{\quad\.{(VF file)}@> @= temp_byte:=vf_one; if temp_byte<>pre then vf_abort('First byte isn''t `pre'''); @:fatal error VF file First byte}{\qquad\.{First byte isn't \it pre}@> @; @; @ @ Here's where we read the preamble of the virtual font file. Note that we make the assumption throughout that a `DVI unit' is \.{1sp} (as it always is in output generated by \TeX), so that $\hbox{`DVI unit'}=16\hbox{\.{fix}}$. @= temp_byte:=vf_one; if temp_byte<>vf_id_byte then vf_abort('Wrong version number in second byte!'); @:fatal error VF file Wrong VF version}{\qquad\.{Wrong VF version...}@> temp_byte:=vf_one; {read the length of introductory comment} for k:=1 to temp_byte do temp_int:=vf_one; {and discard it} temp_int:=vf_quad; if (temp_int<>0) and (font_check_sum[TeX_font]<>0) and (temp_int<>font_check_sum[TeX_font]) then error('VF checksum disagreed! TFM:',font_check_sum[TeX_font]:1, ' VF:',temp_int:1); @:Error: VF checksum disagreed}{\quad\.{VF checksum disagreed}@> z:=vf_quad div 16; {convert to |dvi| units} if z<>font_design_size[TeX_font] then error('VF design size disagreed! TFM:',font_design_size[TeX_font]:1, ' VF:',z:1); @:Error: VF design size disagreed}{\quad\.{VF design size disagreed}@> z:=font_scaled_size[TeX_font]; {but we're using it at this size} @; @ The remainder of the file will consist of font definitions and character packets; once any of the latter has been met, it is not legal for the former to appear again. @= for k:=0 to 255 do packet_start[k]:=vf_size; font_ptr:=0; packet_found:=false; repeat temp_byte:=vf_one; if temp_byte<>post then if temp_byte>long_char then @ else @; until temp_byte=post @ The postamble of the virtual font file should consist purely of |post| bytes; furthermore, the file should be a multiple of four bytes long. @= flushing:=true; while (temp_byte=post)and not eof(vf_file) do temp_byte:=vf_one; if not eof(vf_file) then warning('extra junk at end of the VF file.'); @:Warning: extra junk at end of VF file}{\quad\.{extra junk...VF file}@> if vf_loc mod 4 <> 0 then warning('VF data not a multiple of 4 bytes') @:Warning: VF data not a multiple of 4 bytes}{\quad\.{VF data not a multiple of 4 bytes}@> @ The font definition reads as follows: \yskip\hang|fnt_defN| 243..6 |k[1..4]| |c[4]| |s[4]| |d[4]| |a[1]| |l[1]| |n[a+l]|. \noindent where |k| is a local font number; this is used as an offset from |font_base| when reading the associated \.{TFM} file. @= begin if packet_found or(temp_byte>=pre) then vf_abort('Illegal byte ',temp_byte:1,' at start of character packet!'); @:fatal error VF file Illegal byte}{\qquad\.{Illegal byte...at start...}@> case temp_byte of fnt_def1: font_ref[font_ptr]:=vf_one; fnt_def1+1: font_ref[font_ptr]:=vf_two; fnt_def1+2: font_ref[font_ptr]:=vf_three; fnt_def1+3: font_ref[font_ptr]:=vf_quad {there are no other cases} endcases;@/ font_ext[font_ptr]:=font_base+font_ptr; {invent ``external'' font number} if font_ptr=max_fonts then vf_abort('References too many fonts!'); @:fatal error VF file References}{\qquad\.{References too many fonts}@> @; print(' (local font ',font_ref[font_ptr]:1,' of font ',font_num[TeX_font]:1,')'); if highest_font; if f; incr(font_ptr); end @ We need to position some of this information in a similar fashion to when \.{TFM} files are read during normal font definitions from the \.{DVI} file: @= if nf = max_fonts then capacity_exceeded('too many fonts (max fonts=',max_fonts:1,')'); @:capacity exceeded too many fonts}{\quad\.{too many fonts}@> font_num[nf]:=font_ext[font_ptr]; {record invented ``external'' font number} font_map[nf]:=nf; {until we know better, this font is new} c:=vf_quad; font_check_sum[nf]:=c; {|c[4]|} q:=vf_quad; font_scaled_size[nf]:=q; {|s[4]|} if (q<0) or (q>@'77777777) then {|s| is negative or exceeds $2^{24}-1$} vf_abort('Mapped font size is too big!'); @:fatal error VF file Mapped font size too big}{\qquad\.{Mapped font size too big}@> d:=vf_quad div 16; font_design_size[nf]:=d; {|d[4]| in sp} @; font_scaled_size[nf]:=q; {store revised scaling} p:=vf_one; {|a[1]|} n:=vf_one; {|l[1]|} if font_name[nf]+n+p>name_size then capacity_exceeded('too many names (name size=',name_size:1,')'); @:capacity exceeded too many names}{\quad\.{too many names}@> font_name[nf+1]:=font_name[nf]+n+p; print('Font ',font_num[nf]:1,': '); if n+p=0 then print('null font name!') @.null font name@> else for k:=font_name[nf] to font_name[nf+1]-1 do names[k]:=lower(vf_one); incr(nf); print_font(nf-1); decr(nf); f:=nf; @ When a |fnt_defN| is read from a virtual font file, the parameter |s| (in variable |q|) represents the scaled size relative to the design size of the virtual font. The latter was stored (in |font_design_size[TeX_font]|; scaled by $2^{-4}$) when the original definition of the virtual font was read from the \.{DVI} file, and has been checked during the reading of the virtual font's preamble. Therefore, we will later call |in_TFM| to read the \.{TFM} file for the physical font and adjust all dimensions by the appropriate amount. To do this, we have to compute a new |q| by performing ``correct'' arithmetic on the expression $\hbox{|font_design_size[TeX_font]|}\times q$. We utilize the same algorithm as is used for converting the width information in a \.{TFM} file; local variable |z| has already been set to the virtual font's design size, and converted to $|z|^\prime$, so we just need to split up the value |s| into its constituent bytes and perform the multiplications. By applying a cast to treat |q| as |unsigned|, we ensure that |b0<>0| for negative values of |q|. @d VAX_unsigned_type == unsigned @= b0:=(q::VAX_unsigned_type div @'100000000)::eight_bits; b1:=(q mod @'100000000) div @'200000; b2:=(q mod @'200000) div @'400; b3:=q mod @'400; q:=(((((b3*z) div @'400)+(b2*z)) div @'400)+(b1*z))div beta; if b0>0 then q:=q-alpha; {assume $|q|<2^{25}$} @ @= begin @; open_tfm_file; font_bc[nf]:=0; font_ec[nf]:=256; {will cause error if not modified soon} if in_TFM(q) then @; if font_bc[nf]<=font_ec[nf] then if font_ec[nf]>255 then abort('---not loaded, bad TFM file!'); @:fatal error Font not loaded Bad TFM file}{\qquad\.{Bad TFM file}@> print_ln('') end @ Here is a procedure for ``outputting'' a |dvi| sequence by storing it in |vf|; each command is output in the minimum number of bytes. We cheat and abuse VAX-\PASCAL's variant records to provide a simple conversion between integers (signed or unsigned) and bytes in bigendian order. Variable |byte_ct| determines how many bytes are actually required. Parameter |code| is the command code being output; this will be incremented (by |1..3|) if |2..4| parameter bytes are necessary. |param| is obviously the parameter for |code|; |signed| is negative if |param| is to be interpreted in twos-complement notation. Otherwise, |signed=0|, except for |set_rule| and |put_rule| (which require that all four bytes are output for the parameter) when |signed=1|. For output of the second parameter of these latter two commands, |code=0|, which suppresses output of the |code| byte. The following definition is used for any bytes which must be copied to array |vf|. @d vf_store(#)==begin vf[vf_ptr]:=#; incr(vf_ptr) end @= procedure vf_to_dvi(@!code:eight_bits;@!param:integer;@!signed:integer); var @!byte_ct:0..3; {offset to be added to |code|} @!cheat:packed record case boolean of false: (b0,b1,b2,b3:eight_bits); true: (fullword:integer) end; begin cheat.fullword:=param; byte_ct:=3; {assume needs 4-byte parameter} if signed<=0 then with cheat do if ((b3=0) and ((signed=0) or (b2<128))) or ((b3=255) and (signed<0) and (b2>=128)) then begin byte_ct:=2; {if |b3| superfluous, store 3-byte parameter} if ((b2=0) and ((signed=0) or (b1<128))) or ((b2=255) and (signed<0) and (b1>=128)) then begin byte_ct:=1; {if |b3| and |b2| superfluous, store two bytes} if ((b1=0) and ((signed=0) or (b0<128))) or ((b1=255) and (signed<0) and (b0>=128)) then byte_ct:=0; {|b3|, |b2| and |b1| all superfluous; one byte parameter} end end; if code<>0 then vf_store(code+byte_ct); {store the appropriate command byte} if byte_ct>2 then vf_store(cheat.b3); if byte_ct>1 then vf_store(cheat.b2); if byte_ct>0 then vf_store(cheat.b1); vf_store(cheat.b0); end; @ This function computes how many bytes form the parameters of a command taken from a character packet in the \.{VF} file; in the case of any special sequences amongst these bytes (|xxx1..xxx4|), we read and discard all the bytes here, since we don't support such sequences in virtual fonts. The number of bytes thus discarded is returned, negated. Any occurrence of a command which is illegal in a character packet causes the font to be rejected. @= function vf_skip( @!o:eight_bits) : integer; var @!temp : integer; begin case o of sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64), w0,x0,y0,push,pop,sixty_four_cases(fnt_num_0): vf_skip:=0; set1,put1,fnt1,right1,w1,x1,down1,y1,z1: vf_skip:=1; set1+1,put1+1,fnt1+1,right1+1,w1+1,x1+1,down1+1,y1+1,z1+1: vf_skip:=2; set1+2,put1+2,fnt1+2,right1+2,w1+2,x1+2,down1+2,y1+2,z1+2: vf_skip:=3; set1+3,set_rule,put1+3,put_rule,right1+3,w1+3,x1+3,down1+3, y1+3,z1+3,fnt1+3: vf_skip:=4; xxx1: begin temp:=vf_one; vf_skip:=-1-temp @+end; xxx1+1: begin temp:=vf_two; vf_skip:=-2-temp @+end; xxx1+2: begin temp:=vf_three; vf_skip:=-3-temp @+end; xxx1+3: begin temp:=vf_quad; vf_skip:=-4-temp @+end; improper_DVI_for_VF: vf_abort('contains illegal command in character packet'); @:fatal error VF file contains illegal command}{\qquad\.{contains illegal command...}@> endcases; if (o>=xxx1) and (o-xxx1<4) then begin decr(vf_ptr); {``unstore'' the |xxx| command byte} while temp>0 do begin o:=vf_one; decr(temp) end {discard |xxx| sequence bytes} end end; @ This procedure adds to the saved packet a command to select a font; this will be an pseudo external font number, converted from the local font number used when a physical font is mapped into the virtual one. The procedure has the side-effect of setting its second parameter to index the stored metrics of the newly-selected font. @= procedure store_fnt(@!num:integer; var @!f:integer); var@!k:0..max_fonts; begin f:=-1; {this value allows us to detect lack of the font} for k:=0 to font_ptr-1 do {scan the local font reference numbers} if font_ref[k]=num then f:=k; {find its entry} if f<0 then vf_abort('packet ',c:1,'invokes unmapped font ',num:1); @:fatal error VF file packet invokes}{\qquad\.{packet invokes unmapped font}@> num:=font_ext[f]; {convert to pseudo external number} f:=0; while font_num[f]<>num do incr(f); if num < 64 then vf_store(fnt_num_0+num) else vf_to_dvi(fnt1,num,0); {|fntN k[N]|} end; @ This procedure adds to the saved packet a command to set a character; it is required because we have to read all the bytes of such a command (in order to determine to what character a packet is being mapped). @= procedure store_char(@!cc:integer); begin if cc < 128 then vf_store(set_char_0+cc) else vf_to_dvi(set1,cc,0); {|setN c[N]|} end; @ When a character packet is being read from the virtual font file, we utilize this procedure to store the bytes, translating any font selection commands to reference the pseudo external fonts that have been created corresponding to each local font of the virtual font file. The packet taken from the \.{VF} file is surrounded by a |push|/|pop| pair to maintain the program's conception of the current position. We start by assuming local font $0$, which is that loaded as pseudo external font |font_base|. After the packet has been copied, an |eop| is inserted into the stored packet to permit detection of the end when the packet is later ``played back''. @= procedure vf_copy(@!pl,@!cc : integer); var @!f:-1..max_fonts; {index into physical font data structures} @!param_len:integer; {count of parameter bytes} @!local_font:integer; {index into local font data structures} @!mapped_char:integer; {index into a locally referenced font} @!command:eight_bits; {next |dvi| command in \.{VF} file} @!simple_packet, {packet contains only one |setN|, with maybe one |fntN|} @!one_set,@!one_fnt:boolean; {|false| if more than one of each} @!start_ptr,seq_ptr:0..vf_size; {pointers into bytes stored in |vf|} @!scaling_required:boolean; {dimension needs revision before storing} begin if vf_ptr+pl>=vf_size then capacity_exceeded('too many virtual characters'); @:capacity exceeded too many virt}{\quad\.{too many virtual characters}@> start_ptr:=vf_ptr; vf_store(push); store_fnt(0,f); glyph_map(TeX_font)(cc).font_code:=f; one_set:=false; one_fnt:=false; simple_packet:=true; seq_ptr:=vf_ptr; while pl>0 do begin command:=vf_one; vf_store(command); decr(pl); param_len:=vf_skip(command); @; @; @@; case command of sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64), four_cases(set1): one_set:=true; sixty_four_cases(fnt_num_0),four_cases(fnt1): one_fnt:=true; set_rule, put_rule: {these have \\{two} 4-byte parameters} begin param_len:=param_len-4; simple_packet:=false end; othercases simple_packet:=false; endcases; if param_len<0 then {|abs(param_len)| bytes already copied} pl:=pl+param_len else while param_len>0 do {copy remaining bytes of |command|} begin command:=vf_one; vf_store(command); decr(pl); decr(param_len) end end; vf_store(pop); vf_store(eop); if simple_packet then vf_ptr:=start_ptr {discard the packet} else with glyph_map(TeX_font)(cc) do begin loaded:=virtual; {indicate where \AM\ how to find it} seq_off:=start_ptr end end; @ If a character packet contains any command to select a font, the command must be expanded, and the local font number converted into a reference to the appropriate pseudo external font. Since this may have the effect of reading some bytes from the packet, we reduce our goal by the appropriate quantity. If this is the first font changing command in the packet, the internal font it defines is placed into the current character's |glyph_map| entry, to permit later overwriting with the entry for the physical character. If, moreover, it is the first command \\{stored} in the packet, apart from the initial |push| and the invocation of local font $0$, we can over-write the latter. @= case command of sixty_four_cases(fnt_num_0): local_font:=command-fnt_num_0; fnt1: local_font:=vf_one; fnt1+1: local_font:=vf_two; fnt1+2: local_font:=vf_three; fnt1+3: local_font:=vf_quad; othercases do_nothing endcases; if (command>=fnt_num_0) and (command<=fnt1+3) then begin decr(vf_ptr); {``unstore'' the command byte} store_fnt(local_font,f); param_len := -param_len; {indicate bytes already copied} if vf_ptr=seq_ptr then {we can overwrite the `|fnt_num_0|'} vf_ptr:=start_ptr+1; {by writing immediately after the |push|} if not one_fnt then glyph_map(TeX_font)(cc).font_code:=f else simple_packet:=false end; @ Character packets usually contain one or more commands to set a character; this code reads all necessary bytes to determine the code for the character being set. Note that this code is \&{not} activated for commands to \\{put} characters, since these usually appear only in more complex character packets. If no previous character setting command has been met, then the character code is written into the |glyph_map|. @= case command of sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64): mapped_char:=command-set_char_0; set1: mapped_char:=vf_one; set1+1: mapped_char:=vf_two; set1+2: mapped_char:=vf_three; set1+3: mapped_char:=vf_quad; othercases do_nothing endcases; if (command>=set_char_0) and (command<=set1+3) then begin decr(vf_ptr); {``unstore'' the command byte previously copied} store_char(mapped_char); {store a new character setting sequence} param_len:=-param_len; {indicate this many bytes already consumed} with glyph_map(f)(mapped_char) do if loaded=unused then loaded:=wanted; if not one_set then glyph_map(TeX_font)(cc).char_code:=mapped_char else simple_packet:=false; if font_type[f]=unused then font_type[f]:=wanted; end; @ Many other |dvi| commands within a character packet require that the parameters be read, because they must be scaled by the size at which the virtual font is used. We start by assuming that the |dvi| bytes \&{will} be read from the \.{VF} file, and thus negate |param_len| so that the main loop will know that those bytes have been read. If we \&{don't} read the command's parameters, |param_len| will be negated back again, and thus lead to that number of bytes being copied verbatim from the \.{VF} file to |vf|. @== param_len:=-param_len; scaling_required:=false; case command of set_rule,put_rule: begin q:=vf_quad; {read height of rule} decr(vf_ptr); {``unstore'' command byte} @; vf_to_dvi(command-3,q,1); {store command and first parameter} q:=vf_quad; {read width of rule} param_len:=-8; {we've consumed 8 bytes from |pl|} @; vf_to_dvi(0,q,1) {store second parameter} end; right1,w1,x1,down1,y1,z1: begin q:=vf_byte; scaling_required:=true end; right1+1,w1+1,x1+1,down1+1,y1+1,z1+1: begin q:=vf_pair; scaling_required:=true end; right1+2,w1+2,x1+2,down1+2,y1+2,z1+2: begin q:=vf_trio; scaling_required:=true end; right1+3,w1+3,x1+3,down1+3,y1+3,z1+3: begin q:=vf_quad; scaling_required:=true end; othercases param_len:=-param_len endcases; if scaling_required then begin decr(vf_ptr); {``unstore'' the command byte} @; vf_to_dvi(command+param_len+1,q,-1) {store command again, with revised dimension} end; @ When a character packet is found, we only bother with it if it defines a character that's used in the document being processed; other character packets are merely skipped. If the packet consists merely of commands to set a single character from one of the physical fonts to which the virtual font is mapped, then we can simply alter the information in the |glyph_map| to redirect requests to the appropriate character of the physical font; the |glyph_map| of that font is in turn marked to indicate that the character is used in the current document. On the other hand, if the character packet requires more complex positioning commands, the whole packet is stored in array |vf|; the starting position of the sequence is recorded in field |seq_off| of the |glyph_map| entry, whilst the |loaded| field is given the value |virtual|. Field |font_code| will contain the internal font number assigned to local font zero of the virtual font, whereas the font selection commands stored in |vf| use the pseudo external font numbers, since these bytes will require interpretation by the normal dvi-reading routines. @==,@!virtual @ @==@!seq_off : integer; @ The \.{VF} format supports arbitrary 4-byte character codes, but \.{VPL} format presently does not. Since the |tfm| widths in the character packets are unscaled, it would be difficult to ensure that they correspond with those determined when the virtual font's metrics were read, so they will be ignored at this time. @d nonexistent(#)==((#font_ec[TeX_font])or (char_width(TeX_font)(#)=invalid_width)) @= begin if temp_byte=long_char then begin pl:=vf_quad; c:=vf_quad; temp_int:=vf_quad; {|pl[4]| |cc[4]| |tfm[4]|} end else begin pl:=temp_byte; c:=vf_one; temp_int:=vf_trio; {|pl[1]| |cc[1]| |tfm[3]|} end; if nonexistent(c) then vf_abort('Character ',c:1,' does not exist!'); @:fatal error VF file Character c does not exist}{\qquad\.{Character does not exist}@> if packet_start[c] if pl<0 then vf_abort('Negative packet length!'); @:fatal error VF file Negative packet length}{\qquad\.{Negative packet length}@> packet_start[c]:=vf_ptr; if glyph_map(TeX_font)(c).loaded <> unused then vf_copy(pl,c) else while pl>0 do {discard unwanted character packet} begin temp_byte:=vf_one; decr(pl) end; packet_found:=true; end @ Once all characters from physical fonts have been downloaded (and hence may be assumed to have been mapped to character positions in the LN03's downloaded fonts), we can complete the |glyph_map| entries for any virtual fonts that may be present; for simple characters, this just involves copying the entry for the appropriate character in the physical font. We start with the most recently loaded font, so that chains through a number of virtual fonts will be completed correctly. @= for TeX_font:=nf-1 downto 0 do if font_type[TeX_font]=virtual then {only process virtual fonts} begin for m:=first_glyph_index(TeX_font) to last_glyph_index(TeX_font) do {cover all possible characters} with glyphs[m] do if loaded=wanted then glyphs[m]:=glyph_map(font_code)(char_code) else if loaded=virtual then {first char in sequence from this \TeX\ font} if font_type[font_code]=wanted then {provided it's a physical font} font_code:=txf_to_lnf[font_code] {maps to this LN03 font} end; @*Description of LN03 Font Files. The font files downloaded conform to DEC standard 180; the definitions given below have been derived by the author from the description of this standard included in the ``Font File Format Manual'', Version 1, Variant 0, available on request from DEC. Although we refer herein to font `files' they actually are always considered to reside in memory, either that available in the basic printer, or extra memory provided by plug-in RAM cartridges; presumably the same format is also used to encode the built-in fonts, and those available in plug-in ROM cartridges. A font file is divided into ten regions; all data items may be referenced \\{via} their offsets within one of these regions, which are predeterminable for any particular combination of characters included in the file. All multi-byte quantities in a font file are stored in Vax/VMS standard form, with the least-significant byte being at the \&{lower} address; this is the opposite to the normal `Bigendian' order of most \TeX ware. Since it is necessary to build the entire font file in memory before it can be included in the \.{LN3} file for downloading to the printer, we allocate a large array of memory sufficiently large to store the rasters for the largest imaginable font file; the definition of |lnf_bufsize| probably exceeds the maximum amount of memory available on the printer by a comfortable margin! This program refers to this `file' in memory as the \.{LNF} file, although it is never written to a file as such; if this program is adapted for use on a computer without virtual memory, it might @^system dependencies@> be necessary to utilize a random-access file to build the |lnf_file|. Since the partially-built LN03 font file must be retained between successive calls of |add_txf_to_lnf|, its data structures need to be declared globally. Information in this ``file'' can consist of single bytes, unsigned (two-byte) words, and signed (four-byte) longwords, as well as four-character strings, so we define an |lnf_block| as a \PASCAL\ variant record so that this may be (ab)used to permit the different methods of access. We also declare here the |pxl_dir_type| which describes the 16-byte directory record in a \.{PXL} file for any character's glyph. @= type @!lnf_block=packed record case integer of 0: (@!bytes:packed array[0..511] of eight_bits); 1: (@!words:packed array[0..255] of sixteen_bits); 2: (@!longs:packed array [0..127] of integer); 3: (@!str:packed array [0..127] of packed array [1..4] of char) end; @ Many variables used by |add_txf_to_lnf| are declared globally, so that the information contained therein is preserved between successive calls of the function. |lnf| is the large memory-held `file' referred to above. The variables |psize|, |lsize| and |msize| accumulate counts of raster bytes generated in portrait, landscape and mixed modes (this program only creates portrait mode rasters). |ras_beg| and |ras_len| are used to form the descriptor pointing to the Character Definition Region, whilst |lnf_pool_offset| points to the string pool. |lnf_len| keeps track of the total size of the `file', whilst |using_GR| is set true when the |GR| (right-hand half) of the font is being generated. (The LN03 supports use of the |GL| and |GR| characters sets, corresponding to characters in the (hexadecimal) range |@"21..@"7E| and |@"A1..@"FE|, respectively.) The variable |file_stat| receives the exit status from VAX-Pascal's file handling routines. @= @!lnf: array[0..lnf_bufsize] of lnf_block; @!psize,@!lsize,@!msize: integer; @!ras_beg,@!ras_len: integer; @!lnf_pool_offset,@!lnf_len: integer; @!using_GR: boolean; @!file_stat:integer; @ The first region of an |lnf_file| is the file header. This contains, as its first longword (32-bit) quantity, a count of the total size of the file, in bytes. This is followed by the string |'FONT'|, to confirm that this is a DEC Std 180 font. This is followed immediately by a ``string descriptor'' pointing to the font-file identifier. Font files contain many such descriptors; each consists of two longwords, the first contains the string's length (in its lower half), whilst the latter is the offset (relative to the start of the file) of the first character of the string. The font-file identifier descriptor is followed immediately (which is unusual) by the characters of the identifier itself; for details of the content of this string, read DEC Std 180. The date and time of creation of the font follows; this program arbitrarily sets these to 14:00:00 on 11-SEP-1973. There then follow a number of descriptors pointing to the remaining regions of the font file; these are, in order: |fnt_attr| font attributes region; |fnt_params| font parameters region; |fnt_char_directory| font character directory region; |fnt_seg_list| font segment list region; |fnt_future| font future information region; |fnt_strings| font string pool region; |fnt_kerns| font kerning information region; |fnt_chars| font character defintions region. The header finishes with the character count information field, which contains five longwords and three character locators (|fnt_first|$\to$|fnt_space|), the organization flags, a count of the number of parameters used in each raster definition and the raster expansion information field, which records the number of characters defined in this file, and the number of bytes of rasters required to define them; this field occupies a further 12 longwords (|font_in_file_count|$\to$|fnt_mix_comp|). @d fnt_total_size=0 @d fnt_identifier=fnt_total_size+4 @d fnt_version=fnt_identifier+4 @d fnt_file_id=@"14 {Where font identification appears} @d fnt_date=@"58 {Where creation date starts} @d fnt_attr=fnt_date+12 {Font attributes descriptor} @d fnt_params=fnt_attr+8 {Font parameters descriptor} @d fnt_char_directory=fnt_params+8 {Character directory descriptor} @d fnt_seg_list=fnt_char_directory+8 {Font segment list descriptor} @d fnt_future=fnt_seg_list+8 {Future info descriptor} @d fnt_strings=fnt_future+8 {String pool descriptor} @d fnt_kerns=fnt_strings+8 {Font kerning infor descriptor} @d fnt_chars=fnt_kerns+8 {Character definitions descriptor} @d fnt_first=fnt_chars+8 {First character in font} @d fnt_last=fnt_first+4 {Last character in font} @d fnt_less_than=fnt_last+4 {Locator for characters less then first} @d fnt_greater_than=fnt_less_than+4 {Locator for characters greater than last} @d fnt_error=fnt_greater_than+4 {Locator for the error character} @d fnt_extension=fnt_error+4 {Extension count and use} @d fnt_space=fnt_extension+8 {Space character code} @d fnt_organisation=fnt_space+4 {Organisation flags} @d fnt_no_of_params=fnt_organisation+4 {Number of character parameters} @d fnt_in_file_count=fnt_no_of_params+4 {Number of locators in this file} @d fnt_null_count=fnt_in_file_count+4 {Null locator count} @d fnt_char_count=fnt_null_count+4 {Number of character definitions} @d fnt_alt_blocks=fnt_char_count+4 {Number of alternate character blocks} @d fnt_rasters=fnt_alt_blocks+4 {Number of rasters} @d fnt_compressed=fnt_rasters+4 {Number of compressed rasters} @d fnt_portrait=fnt_compressed+4 {Portrait mode byte count} @d fnt_landscape=fnt_portrait+4 {Landscape byte count} @d fnt_mixed=fnt_landscape+4 {``Mixed'' byte count} @d fnt_port_comp=fnt_mixed+4 {Portrait compressed byte count} @d fnt_land_comp=fnt_port_comp+4 {Landscape compressed byte count} @d fnt_mix_comp=fnt_land_comp+4 {``Mixed'' compressed byte count} @ The font attributes region always follows the header; items within this region are referenced through their local offsets from the start of the region. The |fnt_attr_flags| carries 32 bits of font attribute information; these are set arbitrarily by this program --- see the coding for details. The character set designator (within the string pool) uniquely identifies the character set contained within this font, and is accessed by the descriptor |fnt_char_set|. Similarly the |fnt_type_ID|, |fnt_type_family|, |fnt_font_ID|, |fnt_type_cat| and |fnt_descr| descriptors access further strings in the string pool. Further fields in the font attributes region define numerical attributes of the font (|fnt_type_size| thru |fnt_dev_char|). The attributes region concludes with a further two descriptors, pointing to strings giving the names of the foundry and designer responsible for the font. Numerical quantities referring to the dimensions of characters in DEC LN03 font files are commonly in ``points'' or ``centipoints'' (the latter obviously being $1\over{100}$th of the former). However, these are not true printer's points (of which there are 72.27 to an inch); rather they are that horrible compromise which imagines that there are exactly 72 points to an inch (and which \TeX\ refers to as a \.{bp}). Centipoints are also referred to as `Gutenbergs', in tribute to the inventor of movable type. It is quite convenient for the LN03 (with 300 dots per inch) to work internally in Gutenbergs, of which there are 7200 to an inch, since 24 Gutenbergs therefore translate into a single pixel. @d fnt_attributes=fnt_mix_comp+4 {Start of attributes region}@# {Following define offsets within the Font Attributes Region of the ``file''} @d fnt_attr_flags=0 {Flags e.g. roman} @d fnt_char_set=fnt_attr_flags+4 {DESCR for character set designator} @d fnt_designator_len=7 {Number of characters in set desginator string} @d fnt_type_ID=fnt_char_set+8 {DESCR for Font Type Family ID} @d fnt_type_ID_len=7 {Number of chars in Font Family ID string} @d fnt_type_family=fnt_type_ID+8 {DESCR for Type Family Name} @d fnt_type_family_len=16 {Number of chars in Type Family String} @d fnt_font_ID=fnt_type_family+8 {DESCR for Font ID string} @d fnt_font_ID_len=16 {Number of chars in Font ID string} @d fnt_string_pool_len=fnt_designator_len+fnt_type_ID_len+fnt_type_family_len+fnt_font_ID_len @d fnt_type_cat=fnt_font_ID+8 {DESCR for Type Category} @d fnt_descr=fnt_type_cat+8 {DESCR for Font Description} @d fnt_type_size=fnt_descr+8 {Font type size, in LN03 points} @d fnt_average_width=fnt_type_size+4 {in centipoints} @d fnt_resolution=fnt_average_width+4 {in $2^{-16}$ centipoints per pixel} @d fnt_weight=fnt_resolution+4 {Coded 0..35. Normal is 16} @d fnt_horiz_prop=fnt_weight+4 {0..35. Unexpanded/uncompressed is 16} @d fnt_horiz_ratio=fnt_horiz_prop+4 {Ratio to a "normal" font} @d fnt_pixel_aspect=fnt_horiz_ratio+4 {Ratio of X:Y of device's pixels} @d fnt_rotation=fnt_pixel_aspect+4 {Ratio giving tangent of rotation} @d fnt_dev_char=fnt_rotation+4 {Device specific characteristics} @d fnt_foundry=fnt_dev_char+4 {DESCR of name of foundry} @d fnt_designer=fnt_foundry+8 {DESCR of name of font designer}@# @d fnt_attr_len=fnt_designer+8 {Length of Font Attributes Region of file} @ The next region of the font file is the font parameters region. All fields of this region are referenced by means of their offsets within the region. The first field is a 32-bit flags field, of which only two bits have defined meanings. The next field contains 8 items (|fnt_underline_offset|$\to$|fnt_shadow_offset|) which define the position for electronic over-, through- and under-lining, and the slant of the characters and the delta offsets for emboldening. None of these features are utilized by DVItoLN03. The next field (|fnt_superscript|$\to$|fnt_subscript|) describe the horizontal and vertical offsets within the characters for the placement of super- and sub-scripts. The following field (|fnt_centreline|$\to$|fnt_digit_width|) define various horizontal spacing parameters, whilst the final field of the font parameters region (|fnt_topline_offset| thru |fnt_white_below|) define vertical spacing parameters. @d fnt_parameters=fnt_attributes+fnt_attr_len {Start of Parameters Region}@# {Following define offsets within the Font Parameters Region of the ``file''} @d fnt_param_flags=0 @d fnt_underline_offset=fnt_param_flags+4 {All following in LN03 centipoints} @d fnt_underline_thickness=fnt_underline_offset+4 @d fnt_thru_offset=fnt_underline_thickness+4 {Where strike-through line goes} @d fnt_thru_thickness=fnt_thru_offset+4 @d fnt_overline_offset=fnt_thru_thickness+4 @d fnt_overline_thickness=fnt_overline_offset+4 @d fnt_slant=fnt_overline_thickness+4 {Ratio giving tan of slope from vertical} @d fnt_shadow_offset=fnt_slant+4 {$\delta x$ and $\delta y$} @d fnt_superscript=fnt_shadow_offset+4 {|y| and |x| offsets} @d fnt_subscript=fnt_superscript+8 @d fnt_centreline=fnt_subscript+8 {Distance from left to centre widest char} @d fnt_min_space=fnt_centreline+4 {Minimum acceptable width of interword space} @d fnt_max_space=fnt_min_space+4 @d fnt_mean_space=fnt_max_space+4 {Width of space for monospace fonts} @d fnt_em_width=fnt_mean_space+4 {Width of an |em| space} @d fnt_en_width=fnt_em_width+4 {Width of an |en| space} @d fnt_thin_space=fnt_en_width+4 @d fnt_digit_width=fnt_thin_space+4 {All digits usually same width} @d fnt_topline_offset=fnt_digit_width+4 {From baseline to top of type size} @d fnt_accent=fnt_topline_offset+4 {Where floating accents go} @d fnt_halfline_offset=fnt_accent+4 {Where centre bar of an \.H appears} @d fnt_overall_height=fnt_halfline_offset+4 {Sum of |height+depth|} @d fnt_height=fnt_overall_height+4 {Includes ``leading'' above} @d fnt_depth=fnt_height+4 @d fnt_H_height=fnt_depth+4 @d fnt_ex_height=fnt_H_height+4 @d fnt_white_above=fnt_ex_height+4 @d fnt_white_below=fnt_white_above+4@# @d fnt_params_len=fnt_white_below+4 {Length of Font Parameters Region} @ The next major region contains the character directory. @d fnt_char_dir=fnt_parameters+fnt_params_len {Where directory goes} @ The next regions are: Table of Font Segments, Future Information, String Pool and Kerning Information. These are followed by the Character Definition Region, which contains details of the rasters for each character's glyph. Each character definition commences with |fnt_char_pars| of parameter information; these are accessed through the offsets defined below. In this version of the standard, each character is allocated 16 bytes of parameter information. @d fnt_char_pars=16 {Size of character parameters --- fixed}@# @d fnt_kern_index=0 {Zero, or index into kerning table} @d fnt_char_flags=fnt_kern_index+1 {Only 24 bits of flags} @d fnt_char_width=fnt_char_flags+3 {In LN03 centipoints (or ``Gutenbergs'')} @d fnt_left_bearing=fnt_char_width+4 {White space to left of raster} @d fnt_raster_baseline=fnt_left_bearing+4 {Baseline relative to top of raster} @ The character's parameters are followed by the encoded character raster: this itself contains a number of counts preceding the rasters themselves; the following definitions facilitate access to these fields. @d fnt_char_orient=0 {0=Portrait, etc.} @d fnt_char_type1=fnt_char_orient+1 {81H=Aligned expanded bit string} @d fnt_char_type2=fnt_char_type1+1 {Unused if |fnt_char_type1| = 81H} @d fnt_char_rows=fnt_char_orient+4 {Number of rows in raster} @d fnt_char_cols=fnt_char_rows+2 {Number of columns in raster} @d fnt_char_raster=fnt_char_cols+2 {Where rasters actually start} @* Locating files containing character definitions. This procedure determines the ``name'' of the required pixel file, and compares it with that which is currently open, if any. If these names differ, we close the currently open file, if any, and open the new one. Although the file will have already been opened and checked for validity during font file generation, we still perform the checks, because that also sets certain pointers correctly. This procedure is used by |print_glyph| to output a bitmap image of any oversized rasters. Note that it \&{has} parameter list |( TeX_font : integer)|; this is omitted here because it has already been declared |forward|. Note also that the variable |open_file_name| referenced is the \&{global} variable of the same name, used by |print_glyph|, and not the identically named formal parameter of |add_txf_to_lnf|. The same name has to be used because these \.{WEB} code sections incorporated are used in that function also. @p procedure change_pixel_file; {|change_pixel_file(TeX_font : integer);|} var @!new_file : file_spec; i: integer; {Used when reading the directory} begin name_pxl_file(TeX_font,new_file,pxl_ident); if new_file<>open_file_name then begin if (open_file_name<>'') then close(pxl_file,@=disposition@>:=@=save@>,VAX_continue); open_file_name:=new_file;@/ @; if pxl_ident=pixel_file then begin @; @ end else begin @; skip_specials end end end; @* Creating the font file. The following function allocates the used characters of the |TeX_font| to the predetermined locations in the |ln_font|; the character rasters are taken from the LN03 font file |open_file_name|, which has been set up to include the appropriate directory and file-name. After each font file has been constructed in memory, it is converted to sixel format (See below) and added to the records in the \.{LN3} file |ln3_file|. The use of each of the parameters and local variables is as follows: \yskip\hang|ln_font| The serial number of the LN03 font being generated. Should a font have to cross the boundaries between internal fonts, this will be updated (it's not a |var| parameter, so won't upset anything in the caller). It is used in the procedure as a cross-check that the character glyph currently being considered actually belongs to the font being created. \yskip\hang|TeX_font| The identifying number of the \TeX\ font; |font_num[TeX_font]| is that given by the \.{DVI} file. \yskip\hang|open_file_name| Full file specification from which the rasters should be read. \yskip\hang|file_type| Indicates whether the |open_file_name| contains a |packed_file| or a |pixel_file|. \yskip\hang|rasters| Counts the number of bytes created in the current |font_file|. The variable is initialized to four times the number of characters added since each character requires a four-byte character locator in the font file directory region. \yskip\hang|i| and |j| General loop counters. Further local variables will be declared for the function as necessary. @p function add_txf_to_lnf(ln_font,TeX_font:integer; open_file_name:file_spec; file_type:pixel_types):integer; var @@; {will be declared later} @!rasters:integer; {bytes added to font} @!i,@!j:integer; {general purpose pointers} @t\4@>@@; begin if file_type<>unavailable then @; @ ; case file_type of pixel_file: @; packed_file: @ ; unavailable: begin font_type[TeX_font]:=missing; open_file_name := '[unavailable]' end; endcases; max_ctr := font_ec[TeX_font]; @ ; if file_type=unavailable then write(' unavailable; printing rules for glyphs;'); print(' with '); write_ln(term_out,' loaded with '); @; rasters:=4*font_occupancy[TeX_font]; @ ; @; end; {|add_txf_to_lnf|} @ These variables used to be local to |add_txf_to_lnf|; however, some W{\mc EB} modules invoked therein are now also used externally, but reference the variables. Therefore they are now global. \yskip\hang|percent| Gives the magnification of the current font, for use in font loading reports. (An unmagnified font is at 100\%.) \yskip\hang|name| Holds the ``\TeX-oriented'' font name, for inclusion in reports on the terminal, whilst the full file specification (from |open_file_name|) is reported in the log file. @= @!percent:integer; @!name: file_spec; @!name_ptr: 0..name_size; {pointer for indexing |name|} @ As each font is downloaded, we want to report its name and its magnification (if not \.{\BS magstep 0}) on the terminal, whilst the logfile will receive the full file specification. So we can commence by saving the name of the font file, and the percentage magnification. @= name := ''; for name_ptr:=font_name[TeX_font] to font_name[TeX_font+1]-1 do name := name + xchr[names[name_ptr]]; {ASCII representation of font name} percent:=round((mag*0.1*font_scaled_size[TeX_font])/font_design_size[TeX_font]); @ With rasters held in an unpacked pixel file, we need to check the file's validity, and then transfer the directory information into |pxl_dir|. @= begin @; @ end @ On the other hand, with packed pixel files, we need to read through the preamble and skip over any specials which precede the first character raster in the file. @= begin @; skip_specials; if font_check_sum[TeX_font]<>checksum then warning('checksum doesn''t match in PK file') @.checksum doesn't match in PK file@> end @ Once we've successfully opened the font file, we can report its name (and any non-standard magnification). @= print('Font ',name,' (at ',percent:1,'%) loaded from ',open_file_name); write(term_out,'Font ',name); if percent<> 100 then write(' (at ',percent:1,'%)'); @ We now set about creating (a portion of) an LN03 font file from the required characters of |TeX_font|. We assume at first that it will be possible to pack the entire |TeX_font| into a single LN03 font, but may need to revise this opinion if the font turns out to be longer than 188 characters. A |while| control construct is used instead of the obvious |for| loop, because there may be a necessity for premature termination of the loop. @= char_ctr:=font_bc[TeX_font]; while (char_ctr<=max_ctr) do begin @; incr(char_ctr); end; @; @ For each character in the |TeX_font|, we need to see whether it has been marked as being used, and, if so, whether it belongs to the |ln_font| currently being created. @= with glyph_map(TeX_font)(char_ctr) do if loaded<>unused then begin @ ; @ ; case file_type of packed_file: @; pixel_file: copy_char(char_ctr,to_where); unavailable: @; endcases; end @ We need to decide whereabouts to put this character glyph in the LN03 font file being created. In the case of unpacked pixel files, we can simply use the mapping determined at an earlier stage. However, the glyphs in packed pixel files appear in the same order as the original \MF\ source, so it is necessary to download them to the LN03 in the order of arrival, otherwise we may find ourselves jumping between the ``left-hand'' and ``right-hand'' parts of the code table. It is at this point that we update |first_txfc| to the next available slot in the |ln_font|, if necessary, to skip over the non-existent characters between |left_last| and |right_first|, or from |right_last| to the beginning of an overflow font (if we're using packed files; unpacked ones, being restricted to 128 characters, can never require an overflow). @= if first_txfc=left_last+1 then first_txfc:=right_first else if (first_txfc=right_last+1) and (file_type=packed_file) then @; to_where:=first_txfc @ If a font is completely unavailable (perhaps the user has requested it at a magnification not provided on the current system), we still have to go ahead and `complete' the font for the LN03, because the font file's headers already reflect the previously determined mapping. However, we shall \\{never} image characters from the font, so we just make the character locator entries in the font's ``directory'' zero. @= begin @; loaded:=yes end @ Sometimes we find that we cannot complete the bitmap information relating to a particular character, because the glyph is too large, or missing (or the font itself is missing). Since we will already have completed the LN03 font's description relating to number of characters, etc., we must still provide an entry in the font directory; the LN03 will not attempt to image a character for which the directory entry is zero, so that's what we put there! @== begin lnf_long(fnt_char_dir+4*(to_where-dir_base),0); decr(num_chars); incr(null_chars) end @ Whenever we first meet a character taken from the ``right-hand'' half of the LN03 font (the set designated as GR, in ISO2022/ANSI X3.64 parlance), we need to finish off the half font which has been created in the left-hand table. Similarly, if we meet a character from the ``left-hand'' half (GL) when we had previously been adding characters to the GR set, it implies that we are commencing output, to GL, of characters taken from a \TeX\ font which was too large to be loaded into the 188 characters of the previous |ln_font|. The font file to date is written out and a new one initialized to hold the GR character set. Such successive font files are separated from each other by a simple `\.,' character in the \.{LN3} file. @= if ((not using_GR) and (to_where>=right_first)) or (using_GR and (to_where<=left_last)) then begin output_LNF; write_ln(ln3_file,',');@/ using_GR:=not using_GR; init_LNF end @ If glyphs are being taken from a file of packed pixel rasters, we need to copy the glyph to the LN03 file as the raster is built up from the file. Having thus disposed of this glyph, we then scan through the file until finding the \&{next} wanted glyph. @= begin repeat @; skip_specials until (glyph_map(TeX_font)(char_num).loaded<>unused) or (flag_byte=pk_post); with glyph_map(TeX_font)(char_num) do if loaded<>unused then begin char_code:=first_txfc; font_code:=ln_font; incr(first_txfc) {This might later be corrected} end; if flag_byte=pk_post then char_ctr := 255 {Break out} end @ Here are some local variables of |add_txf_to_lnf|, used in the above code: \yskip\hang|char_ctr| Used to cycle through all possible character codes in the |TeX_font|. \yskip\hang|max_ctr| Unpacked pixel files (by their very architecture) can contain a maximum of 128 glyphs; packed files (in this implementation) may contain up to 256. This variable permits more efficient operation by limiting the values assumed by |char_ctr|. @== @!char_ctr:integer; @!max_ctr:integer; @ After having downloaded the last character of |TeX_font| which belongs in |ln_font|, we arrange things so that any further characters will be loaded into |ln_font+1|. We accordingly want to start at |left_first| (which ought to be the value held in |txfc_next|), and ensure that the font is written out to the GR part of |ln_font|, before incrementing to the next font. This code can only be invoked when working with packed pixel files, because fonts which run over more than one LN03 font must exceed 188 characters in length, and unpacked pixel files are limited to 128 characters! @= begin incr(ln_font); last_txfc := final_txfc; {|first_txfc>right_last|, therefore\dots} @; {|begin_txf:=false|} first_txfc := txfc_next {This will be our next character} end @ The first job |add_txf_to_lnf| has to do is to open the (packed or unpacked) pixel file; the file directory and font name are passed in to the function (as parameter |open_file_name|). Note that this \.{WEB} section is expanded by \.{TANGLE} in the context of procedure |change_pixel_file| \&{and} of function |add_txf_to_lnf|. In the first context, the string variable |open_file_name| referenced herein is the \&{global} variable of that name, whilst within |add_txf_to_lnf| it is the formal parameter of that function. Assuming the file is opened succesfully, the size of the file is taken from variable |file_len|, which is set up by the \.{user\_action} procedure with which the file is opened. The first block of the file is then read (into the associated file variable |pxl_file|). @= begin open(pxl_file,open_file_name,@=readonly@>,@=access_method@>:=@=direct@>, @=user_action@>:=file_open,VAX_continue); file_stat:=status(pxl_file); ask:=file_stat<>0; if ask then begin case file_stat of 2: file_message := 'PAS-E-ERRDUROPE'; 3: file_message := 'PAS-E-FILNOTFOU'; 5: file_message := 'PAS-E-ACCMETINC'; 6: file_message := 'PAS-E-RECLENINC'; 7: file_message := 'PAS-E-RECTYPINC'; 8: file_message := 'PAS-E-ORDSPEINC'; othercases file_message := str_int(file_stat); endcases; abort('Couldn''t open ',open_file_name,'. Status=',file_message); @:fatal error Couldn't open file}{\quad\.{Couldn't open file} (pxl/pk)@> end; reset(pxl_file) end @ We apply some simple checks to determine whether the \.{PXL} file passed in has a valid format. At the same time, we set up the local variable |pxl_size| which gives the total length of the \.{PXL} file, in bytes, excluding the identification longword of 1001 at its end. @= pxl_size:=file_len; pxl_block:=0; move_to_pxl(pxl_size-4); i:=pxl_ptr+4; {Point {\bf after} the last block} repeat i:=i-4 until (i<0) or (pxl_long(i)=pxl_id); pxl_size:=i+VAX_block_length*pxl_block; if i<0 then abort('PXL file has bad format'); @:fatal error PXL file has bad format}{\quad\.{PXL file has bad format}@> @ Having found the end of the \.{PXL} file, we are able to extract the last four longwords. These give us, starting with the last, the longword address of the start of the character directory within the file, the ``design size'' (in scaled points), the ``magnification'' of the font (will be 1500 for fonts designed for 300 pixels/inch devices) and the font checksum. This latter quantity should match that read from the \.{TFM} file by \TeX. After using these longwords, we then go to the first byte of the character directory, and copy it into |pxl_dir|. Each character's entry consists of the following fields: \yskip\hang|num_cols| the number of pixels printed for each row of the character; \yskip\hang|num_rows| number of rows of pixels printed; \yskip\hang|x_off| offset (in pixels) of the left-hand edge of the raster, relative to the reference point; \yskip\hang|y_off| similarly for top of raster relative to the baseline; \yskip\hang|addr| the longword address within the \.{PXL} file of the start of the rasters; \yskip\hang|tfm_width| the character's width, in the same format as for |tfm_file|. @= move_to_pxl(pxl_size-16); if font_check_sum[TeX_font]<>long_pxl(pxl_ptr) then warning('checksum doesn''t match in PXL file'); @.checksum doesn't match in PXL file@> magnification:=long_pxl(pxl_ptr); dsize:=long_pxl(pxl_ptr); dsize:=(dsize/two_to_the_20th)*(magnification/1500.0);@/ move_to_pxl(long_pxl(pxl_ptr)*4); {Go to start of directory} for i:=0 to 127 do with pxl_dir[i] do begin num_cols:=word_pxl(pxl_ptr); num_rows:=word_pxl(pxl_ptr); x_off:=sign_pxl_word(pxl_ptr); y_off:=sign_pxl_word(pxl_ptr); addr:=long_pxl(pxl_ptr); tfm_width:=long_pxl(pxl_ptr) end @ We need to determine the lowest and highest ordinal character positions of those characters used in the \TeX\ font being encoded into the LN03 font file; the variables |first_txfc| and |last_txfc| are used for this purpose. If there are no characters used at all in the \TeX\ font some malfunction must have occurred, because |add_txf_to_lnf| should not have been invoked under those circumstances. The character numbers will be those of the LN03 font file, having already been mapped to these during the mapping phase; therefore, if the lowest character number used is |= left_first| then we must be starting a new font, so must initialize the empty font file and indicate that this is a |GL| font (left-hand half of eight-bit character table). @= first_txfc:=-1; last_txfc:=-1; {For |ln_font|} txfc_next:=-1; final_txfc:=-1; {and for |ln_font+1|} for i:=first_glyph_index(TeX_font) to last_glyph_index(TeX_font) do with glyphs[i] do @ ; @ @ As we cycle through all the allocated characters, we note each code in turn, thus determining the highest code used. We also note the lowest one when we meet the first code used. If the |TeX_font| extends into a second |ln_font|, the corresponding information will be recorded in |final_txfc| and |txfc_next|, respectively. @= if loaded = wanted then begin if txf_to_lnf[font_code]=ln_font then begin if first_txfc=-1 then first_txfc:=char_code; last_txfc:=char_code; font_code:=ln_font end else {This must be overflow of |TeX_font| into |ln_font+1|} begin if txfc_next=-1 then txfc_next:=char_code; final_txfc:=char_code; font_code:=ln_font+1 end end @ If the lowest character code used is |left_first|, then we shall have to initialize a new LN03 font file. Similarly, if the last character used is the last character allocated to this LN03 font, we shall later have to finish off the latter. We therefore note these two conditions in |begin_txf| and |end_txf|, and initialize the font ``file'' if the former condition pertains. @= if last_txfc<0 then abort('Internal error (empty font)'); @:fatal error Internal error empty font}{\qquad\.{(empty font)}@> begin_txf:=first_txfc=left_first; end_txf:=last_txfc=last_lnf[ln_font]; if begin_txf then begin using_GR:=false; init_LNF end @ Here are some more local variables of |add_txf_to_lnf|, used in the above code: \yskip\hang|first_txfc| The lowest character code allocated to a character of |TeX_font| when the mapping took place. \yskip\hang|last_txfc| Similarly, the highest code used. \yskip\hang|txfc_next| The lowest character code for a character of |TeX_font| allocated to |ln_font+1| when the mapping took place. \yskip\hang|final_txfc| Similarly, the highest code used by |TeX_font| in |ln_font+1| \yskip\hang|begin_txf| A boolean, which is |true| if this |TeX_font| commences a new font file in the LN03. \yskip\hang|end_txf| This is |true| if the last character of the present |TeX_font| is the last character mapped to |ln_font|. @== @!first_txfc, @!last_txfc : integer; @!txfc_next, @!final_txfc : integer; @!begin_txf, @!end_txf : boolean; @ After all the required character glyphs have been copied from the \.{PXL} file, we can close it. If, and only if, the very last character code was that of the highest mapped into this font, we can output the LN03 font to the \.{LN3} file. Finally, we report the number of bytes of rasters created in the font file. @= close(pxl_file,@=disposition@>:=@=save@>,VAX_continue); if end_txf then output_LNF; print_ln(rasters:0,' bytes of rasters (',font_occupancy[TeX_font]:1, ' characters)'); write_ln(term_out,rasters:0,' bytes of rasters (',font_occupancy[TeX_font]:1, ' characters)' ,crlf); add_txf_to_lnf:=rasters {Total bytes of rasters added to file} @* Procedures to access the font file. The definitions which follow facilitate access to the individual bytes, words, longwords and strings of the ``|lnf_file|'' by accessing the appropriate entity in the variant record representation of the file's blocks. The procedures allow us to write bytes, words and longwords into the memory representation of the |lnf_file|. @d lnf_bytes(#)==lnf[(#) div VAX_block_length].bytes[(#) mod VAX_block_length] @d lnf_words(#)==lnf[(#) div VAX_block_length].words[((#) mod VAX_block_length) div 2] @d lnf_longs(#)==lnf[(#) div VAX_block_length].longs[((#) mod VAX_block_length) div 4] @d lnf_str(#)==lnf[(#) div VAX_block_length].str[((#) mod VAX_block_length) div 4] @= procedure lnf_byte(byte_address:integer; byte_val:eight_bits); begin lnf_bytes(byte_address):=byte_val end; procedure lnf_word(byte_address:integer; word_val:sixteen_bits); begin if odd(byte_address) then abort('Internal error (word at odd byte ',byte_address:1,')'); @:fatal error Internal error word at odd byte}{\qquad\.{(word at odd byte)}@> lnf_words(byte_address):=word_val end; procedure lnf_long(byte_address,long_val:integer); begin if odd(byte_address) then abort('Internal error (longword at odd byte ',byte_address:1,')'); @:fatal error Internal error longword at odd byte}{\qquad\.{(longword at odd byte)}@> if byte_address mod 4 = 0 then lnf_longs(byte_address):=long_val else begin if long_val<0 then begin long_val:=long_val+@'17777777777; {$2^{31}-1$} long_val:=long_val+1; lnf_words(byte_address+2):=(long_val div 65536)+32768 end else lnf_words(byte_address+2):=(long_val div 65536); lnf_words(byte_address):=long_val mod 65536 end end; @ When we are ``outputting'' to the |lnf_file| from \.{PK} files, it's more convenient to call the following procedure; in fact, it might prove handy for use in |copy_char| too! @= procedure put_lnf(@!item : integer); begin lnf_bytes(base):=item; incr(base) end; @# procedure repeat_row; begin put_lnf(lnf_bytes(row_addr)); incr(row_addr) end; @ For downloading the bitmap of an oversized glyph, we need to ``read'' six rows of pixels from either the |pxl_file| itself, or from the character's bitmap which has been formed in |lnf_file| from the |packed_file|. @= if pxl_ident=packed_file then for j:=1 to ((num_cols+7) div 8) do begin sixel_line[k,j]:=reverse[lnf_bytes(base)]; incr(base) end else begin for j:=1 to (num_cols+7) div 8 do begin sixel_line[k,j]:=pxl_byte(pxl_ptr); incr(pxl_ptr); must_get(pxl_ptr) end; while (pxl_ptr mod 4) <> 0 do begin incr(pxl_ptr); must_get(pxl_ptr) end end @ It could happen that a glyph is imaged that doesn't exist in the font; we then mark this in the |loaded| field of the |glyph_map| as |missing|; such glyphs will result in appropriate $(x,y)$ positioning commands being output to the LN03, by derivation from the font metrics data. @= ,@!missing @ After we've read through the (packed or unpacked) pixel file, we should have managed to download glyphs for all the characters used in the current document. If the following check finds a character without an associated glyph, it downloads a dummy, invisible glyph of the appropriate width. @= for char_num := font_bc[TeX_font] to font_ec[TeX_font] do with glyph_map(TeX_font)(char_num) do if loaded=wanted then begin loaded := missing; {Ensure we don't try to image the glyph} term_offset := 20; {Ensure we get a newline before warning} warning('Font ',name,' doesn''t contain character ',char_num:1,crlf); @:Warning: Font doesn't contain character}{\quad\.{Font doesn't contain...}@> @; @; tfm_width := width; num_cols := pixel_width; base := ras_beg + ras_len; @; char_code:=first_txfc; font_code:=ln_font end @*Copy one character's rasters. For each character taken from an unpacked (\.{PXL}) file, the function |add_txf_to_lnf| calls this procedure |copy_char|, which provides the main action of the function, by converting the \.{PXL} file rasters to the format required by the \.{LN03}, and storing them away in the appropriate part of the ``|lnf_file|'' in memory. The global variable |base| is used as a pointer into the appropriate byte of the Character Definitions Region of the LN03 font file, and commences at the location determined from the ``size so far'' as recorded by |ras_beg| and |ras_len|. Having verified that the \.{PXL} file contains the character glyph required (if the character is absent, the address field of the directory entry will not point to an address within the file), the |move_to_pxl| procedure is called to make the appropriate bytes of the characters rasters accessible to the program. The character parameters section of the character definition is then completed in the font file; the first four bytes contain the kerning information (null, because \TeX\ does all the kerning for us, and we don't want the LN03 to upset it) and 24 bits of flags --- the only bit that we set in this flag field is the `flag flag' bit. LN03 pixels (of which there are 300 to an inch) can be converted to DEC centipoints (or Gutenbergs) by multiplying by 7200/300 = 24. @d Pxl_to_Gut=24 @= procedure copy_char(char_num,to_where:integer); var @!i,@!j,@!k,@!n,@!len:integer; begin base:=ras_beg+ras_len; {Where to start saving the parameters/rasters} with pxl_dir[char_num] do if (addr>0) and (addr<=(pxl_size div 4)-8) then {Only process characters which lie within the file!} with glyph_map(TeX_font)(char_num) do begin move_to_pxl(addr*4); char_code:=to_where;@/ @; @; @; @; @; @ end {|with|} end; @ The true character width (in scaled points) is taken from the |tfm_width| field of the pixel file directory; this value is converted to pixels and stored in the |ch_widths| array, where it is used by the procedures which set characters on the page. The value is also converted to Gutenbergs and stored in the |fnt_char_width| field for the character raster in the font file. If the width differs from that computed from the \.{TFM} file by more than one pixel, this is reported, and the value derived from the \.{PXL} file substituted in preference. @= ch_wid:=round((dsize*resolution*tfm_width)/(two_to_the_20th*72.27)); lnf_word(base,@"0000); {No kerning, lowest byte of flags=0} lnf_word(base+2,@"8000); {Highest bit of flags (flag flag) = 1} lnf_long(base+fnt_char_width,trunc(ch_wid*(7200.0/resolution))); ch_widths[ln_font,to_where]:=ch_wid; tfm_wid:=pixel_width; if abs(ch_wid-tfm_wid) > 1 then begin warning('Pixel width discrepancy; font ',open_file_name,' char ',char_num:1); @:Warning: Pixel width discrepancy}{\quad\.{Pixel width discrepancy}@> show('---computed width: ',tfm_wid:1,'; actual width: ',ch_wid:1); end; pixel_width:=ch_wid @ The above section of code needs some workspace in which to perform those width computations. They may as well be local to |add_txf_to_lnf|. \yskip\hang|ch_wid| Holds the actual width of the character, in pixels, derived from the |tfm_width| field of the pixel file's directory. \yskip\hang|tfm_wid| Holds the character width as calculated from the |ch_widths| entry which came from the TFM file for this font. @== @!ch_wid,@!tfm_wid:integer; @ The \.{PXL} file directory entry contains |x_off| and |y_off| fields, which define the starting point of the raster relative to the character's reference point. These are passed to the font file as the |fnt_left_bearing| and |fnt_raster_baseline| fields of the raster entry, in Gutenbergs. The flags longword of the raster is then created, and indicates that the raster is in portrait orientation (relative to memory, not to the output device) and is an aligned expanded bit string. (Unfortunately, although DEC Std 180 permits run-length encoding of rasters, the LN03 itself does not support this facility, so rasters have to be downloaded in fully expanded form.) Invisible fonts (used by Sli\TeX) will have the bounding box info (|num_cols| and |num_rows|) both equal to zero. Since the LN03 isn't very happy with such characters (!), we will later insert a null character locator in the font's directory; we still go through the motions of generating character parameters and the (empty) bitmap in the font file, but bytes occupied by these will be overwritten with the next real bitmap. Although instructions to image such a null character are accepted by the printer, unfortunately the printer seems to image some other character at random! Therefore we mark the glyph as |missing|, so that no attempt is made to image it later. Oversized glyphs are also not inserted into the font file; so it is only for genuine printable glyphs that the value of |base| will be advanced to reserve the space occupied by the character's parameters. This module is always called in the context |with glyph_map(TeX_font)(char_num) do|. @= lnf_long(base+fnt_left_bearing,int(-Pxl_to_Gut*x_off)); lnf_long(base+fnt_raster_baseline,int(-Pxl_to_Gut*y_off)); lnf_long(base+fnt_char_pars+fnt_char_orient,@"00008100); {Aligned,bit-expanded} if num_rows=0 then loaded := missing; {``Invisible'' glyph} lnf_word(base+fnt_char_pars+fnt_char_cols,num_cols); lnf_word(base+fnt_char_pars+fnt_char_rows,num_rows); if loaded=yes then base:=base+fnt_char_pars+fnt_char_raster @ Here are some further values than can be allocated to the |loaded| field of |glyph_map|: values could be:\par \item{$\bullet$} |yes| if character's glyph was included in a downloaded LN03 font; \item{$\bullet$} |no| if the glyph exceeded the LN03's capability for the area of a downloaded glyph. In this case a null character locator will have been inserted in the directory of the downloaded font, and the actual glyph's bitmap will be sent to the printer each and every time it occurs in the document. @= ,@!yes ,@!no @ The LN03 has a physical (apparently arbitrary) restriction on the maximum size of a downloadable glyph. For a glyph to be acceptable, it must satisfy the inequality $$ 2\times\lceil|num_cols|/2\rceil\times\lceil|num_rows|/8\rceil\le5700$$ The following section prevents such an oversized glyph from being included in the downloaded font; it is always called in the context |with glyph_map(TeX_font)(char_num) do|. @= if ((num_rows+7) div 8) * 2 * ((num_cols+1) div 2) > largest_glyph then loaded:=no {Indicate oversized glyph unavailable} else loaded:=yes {Indicate glyph successfully downloaded} @ After further setting up of values in the font file, we copy the bits of the rasters from the \.{PXL} file to the LN03 font file; this requires that we reverse each byte. As each byte is copied, we get another; the |must_get| procedure will read in a new block from the \.{PXL} file if necessary. At the end of each row, the next is started from the next complete longword of the \.{PXL} file. Only character glyphs which are capable of being downloaded are entered into the file; invisible and oversized glyphs don't need to have the bitmap copied, and they'll eventually end up with a null character locator in the font file's directory. This module is always called in the context |with glyph_map(TeX_font)(char_num) do|. @= if loaded=yes then for i:=1 to num_rows do begin for j:=1 to (num_cols+7) div 8 do {Whole bytes required} begin lnf_bytes(base):=reverse[pxl_byte(pxl_ptr)];@/ incr(pxl_ptr); must_get(pxl_ptr); incr(base) end; {Copied bytes for one row of pixels} while (pxl_ptr mod 4)<>0 do begin incr(pxl_ptr); {Row held in 32-bit words in \.{PXL} file} must_get(pxl_ptr) end end @ The |copy_char| procedure requires an array to be set up to ``reverse'' individual bytes' bits, since the \.{PXL} file stores the first pixel of a row at the most significant bit of the long-word, whilst DEC Std 180 requires the first pixel to be the least significant bit of the first byte. We therefore create a global array |reverse|, which we initialize with appropriate patterns to reverse any byte proferred as an index. @= @!reverse: packed array [0..255] of eight_bits; @ @= procedure Generate_Reversal; var @!index,@!patt,@!cnt,@!rev: eight_bits; begin for index:=0 to 255 do begin rev:=0; patt:=index; for cnt:=1 to 8 do begin rev:=2*rev; if odd(patt) then rev:=rev+1; patt:=patt div 2 end; reverse[index]:=rev end; end; @ @= Generate_Reversal; @ The number of bytes required for the character glyph in landscape and portrait modes is computed, and added to the accumulated sum of these for all characters; the ``mixed'' mode count is increased by the larger of these quantities. The value |len| is also computed to be added to the Character Definitions descriptor held in |ras_beg,ras_len|. None of these quantities are updated unless the character glyph has actually been copied into the font file, i.e., |loaded=yes|. @= if loaded=yes then begin i:=((num_rows+7) div 8)*num_cols; {Bytes required for raster in landscape mode} lsize:=lsize+i; j:=((num_cols+7) div 8); {Bytes/column} psize:=psize+j*num_rows; {bytes added to portrait rasters} msize:=msize+VAX_max(i,j*num_rows); {bytes added to mixed mode rasters} len:=j*num_rows+fnt_char_pars+fnt_char_raster {Allow for the parameters} end else len:=0 @ After transferring the rasters for one character's glyph, we finally complete the entry in the font file's character directory. We record the address of the character's rasters in the appropriate character locator, update the count of rasters stored, and round up to the next \\{word} boundary in the font file (note that it is not a requirement of DEC Std 180 that rasters start on a \\{longword} boundary.) If the glyph was not copied into the font file, because it exceeded the LN03's maximum size of glyph, or because it was invisible, then a null entry is made for the character locator, and the next raster, if any, will overwrite that just stored. @= if loaded<>yes then begin lnf_long(fnt_char_dir+4*(to_where-dir_base),0); incr(null_chars); decr(num_chars) end else begin lnf_long(fnt_char_dir+4*(to_where-dir_base),ras_beg+ras_len); ras_len:=ras_len+len; rasters:=rasters+len; if odd(ras_len) then begin lnf_byte(base,0); {Null byte to round raster to word boundary} incr(ras_len); incr(rasters) end end @ The following procedure initialises the ``fixed'' area of the \.{LN03} font file as held in memory. We start by clearing the whole of the first block of the file; this is sufficient to ensure that the whole of the file header, attributes and parameters regions are zeroed, which reduces the number of individual assigments necessary. The counts of space occupied by the rasters, and the raster sizes in portrait, landscape and mixed modes are also initialized. @= procedure init_LNF; var @!i,@!j:integer; begin for i:=0 to 127 do lnf_longs(4*i):=0; {Clear the lnf header} ras_len:=0; psize:=0; lsize:=0; msize:=0;@/ @@; @@; @@; @@; end; @ The font file's header contains some identification material, and a number of descriptors which point to other regions of the font file. @= lnf_str(fnt_identifier):='FONT'; lnf_long(fnt_version,1); {Font File Format is V1.0} file_ID:='U0000'+@=dec@>(ln_font,2,2)+'002SK00GG0001UZZZZ02F000'; if using_GR then file_ID[21]:='O'; lnf_long(12,VAX_length(file_ID)); lnf_long(16,fnt_file_id); for j:=1 to VAX_length(file_ID) do lnf_byte(j+fnt_file_id-1,xord[file_ID[j]]); {For the moment, we set creation date to 11-SEP-1973 14:00:00} lnf_word(fnt_date,1973); lnf_word(fnt_date+2,9); lnf_word(fnt_date+4,11); lnf_word(fnt_date+6,14); lnf_word(fnt_date+8,0); lnf_word(fnt_date+10,0); lnf_long(fnt_attr,fnt_attr_len); lnf_long(fnt_attr+4,fnt_attributes); lnf_long(fnt_params,fnt_params_len); lnf_long(fnt_params+4,fnt_parameters); if using_GR then num_chars:=last_lnf[ln_font]-right_first+1 else if last_lnf[ln_font]= @!file_ID:packed array [1..31] of char; @!num_chars,@!null_chars:integer; @!dir_base:integer; @ The font file's attributes region contains mostly descriptors (of strings in the string pool region) which identify the font, font family, \\{etc.} @= lnf_long(fnt_attributes+fnt_attr_flags,2); {Roman} lnf_long(fnt_attributes+fnt_char_set,fnt_designator_len); lnf_long(fnt_attributes+fnt_char_set+4,lnf_pool_offset); lnf_long(fnt_attributes+fnt_type_ID,fnt_type_ID_len); lnf_long(fnt_attributes+fnt_type_ID+4,lnf_pool_offset+fnt_designator_len); lnf_long(fnt_attributes+fnt_type_family,fnt_type_family_len); lnf_long(fnt_attributes+fnt_type_family+4, lnf_pool_offset+fnt_designator_len+fnt_type_ID_len); lnf_long(fnt_attributes+fnt_font_ID,fnt_font_ID_len); lnf_long(fnt_attributes+fnt_font_ID+4, lnf_pool_offset+fnt_designator_len+fnt_type_ID_len+fnt_type_family_len); {Type category, Font description descriptors empty} {Following values are purely arbitrary at present; try harder later} lnf_long(fnt_attributes+fnt_type_size,10); {``10pt''} lnf_long(fnt_attributes+fnt_average_width,500); {5.00pt} lnf_word(fnt_attributes+fnt_resolution+2,24); {24.0000 centipoints/pixel} lnf_long(fnt_attributes+fnt_weight,16); {``Normal'' weight} lnf_long(fnt_attributes+fnt_horiz_prop,16); {Neither expanded nor compressed} lnf_word(fnt_attributes+fnt_horiz_ratio,1); {1:1 relative to a ``normal'' font} lnf_word(fnt_attributes+fnt_horiz_ratio+2,1); lnf_word(fnt_attributes+fnt_pixel_aspect,1); {``Square'' pixels} lnf_word(fnt_attributes+fnt_pixel_aspect+2,1); lnf_word(fnt_attributes+fnt_rotation+2,1); {tan = 0/1 = $0^\circ$} {Device characteristics = 0} {Foundry and Designer have empty descriptors} @ The font file's parameters region contain information relating to all the characters as a whole. Since they relate to such features as electronic underlining, which are not utilized by the \.{LN3} file generated by DVItoLN03, the values used are purely arbitrary. (The values are those used by Flavio Rose's original \.{LN03toPP} (PL/I) program.) @= {Params flags = 0} lnf_long(fnt_parameters+fnt_underline_offset,30); lnf_long(fnt_parameters+fnt_underline_thickness,20); lnf_long(fnt_parameters+fnt_thru_offset,-60); lnf_long(fnt_parameters+fnt_thru_thickness,20); lnf_long(fnt_parameters+fnt_overline_offset,-150); lnf_long(fnt_parameters+fnt_overline_thickness,20); lnf_word(fnt_parameters+fnt_slant+2,1); {tan = 0/1 = $0^\circ$} lnf_word(fnt_parameters+fnt_shadow_offset+2,30); {$\delta x$ only} lnf_long(fnt_parameters+fnt_superscript,-90); {$\delta y$ only} lnf_long(fnt_parameters+fnt_subscript,40); {$\delta y$ only} lnf_long(fnt_parameters+fnt_centreline,60); lnf_long(fnt_parameters+fnt_min_space,240); {Note |min>max|!} lnf_long(fnt_parameters+fnt_max_space,60); lnf_long(fnt_parameters+fnt_mean_space,100); lnf_long(fnt_parameters+fnt_em_width,240); lnf_long(fnt_parameters+fnt_en_width,120); lnf_long(fnt_parameters+fnt_thin_space,40); lnf_long(fnt_parameters+fnt_digit_width,120); lnf_long(fnt_parameters+fnt_topline_offset,-160); lnf_long(fnt_parameters+fnt_accent,-120); lnf_long(fnt_parameters+fnt_halfline_offset,-70); lnf_long(fnt_parameters+fnt_overall_height,240); lnf_long(fnt_parameters+fnt_height,-180); lnf_long(fnt_parameters+fnt_depth,60); lnf_long(fnt_parameters+fnt_H_height,160); lnf_long(fnt_parameters+fnt_ex_height,120); lnf_long(fnt_parameters+fnt_white_above,20); lnf_long(fnt_parameters+fnt_white_below,20); @ The string pool region of the font file contains the majority of the character strings which are required to identify the font. @= lnf_byte(lnf_pool_offset,"0"); {Char set designator} if using_GR then lnf_byte(lnf_pool_offset+1,"<") else lnf_byte(lnf_pool_offset+1,"B"); lnf_byte(lnf_pool_offset+2,9); {HT character} for i:=4 to fnt_designator_len do lnf_byte(lnf_pool_offset+i-1,"Z"); {|'ZZZZ'|---full char set} for i:=1 to fnt_type_ID_len do lnf_bytes(lnf_pool_offset+i-1+fnt_designator_len):= lnf_bytes(i-1+fnt_file_id); {Copy first 7 of |file_ID|} for i:=1 to fnt_type_family_len do lnf_bytes(lnf_pool_offset+i-1+fnt_designator_len@|+fnt_type_ID_len) := " "; {Type family is all spaces} for i:=1 to fnt_font_ID_len do lnf_bytes(lnf_pool_offset+i-1+fnt_designator_len@|+fnt_type_ID_len@| +fnt_type_family_len) := lnf_bytes(i-1+fnt_file_id); {Copy first 16 of |file_ID|} @ The following procedure is invoked whenever an \.{LN03} font file in memory is ready to be written to the output |ln3_file|. Firstly we complete those fields of the font file header region which we were unable to complete before, storing away the total length (at front \&{and} back) and repeating the `\.{FONT}' indication at the end of the file. We also complete the character definitions region descriptor, and fill in the counts of bytes loaded as portrait, landscape and mixed mode rasters. Obviously it is not possible to include the file \\{verbatim} in the output as a binary image, since the terminal output driver and/or the printer symbiont would probably eliminate most of those characters which are unprintable. Therefore the LN03 requires that the binary file be encoded into ``sixel'' format; this is more usually used to transfer graphics information to DEC printers, one sixel being a printable character (in the range `\.?'$\to$`\.\~') which encodes the numbers |0..63| representing a column of six pixels. @= procedure output_LNF; var @!i,@!j,@!k,@!records,@!sixel_ct:integer; @!line_out:varying [128] of char; @!transform : six_pack; begin lnf_len:=ras_beg+ras_len+8; {Make room for Trailing identifier} lnf_long(fnt_total_size,lnf_len); {Total size noted into first longword} lnf_long(lnf_len-8,lnf_len); {and into penultimate longword} lnf_long(lnf_len-4,lnf_longs(fnt_identifier)); {Copy |'FONT'| from 2nd longword to last} lnf_long(fnt_chars,ras_len); {Length of character definitions} lnf_long(fnt_in_file_count,num_chars); {We might have excluded some} lnf_long(fnt_null_count,null_chars); {which will be reflected here} lnf_long(fnt_rasters,num_chars); {and so we only made this many rasters} lnf_long(fnt_portrait,psize); lnf_long(fnt_landscape,lsize); lnf_long(fnt_mixed,msize); @ end; @ To convert eight-bit bytes to sixels, which the \.{LN03} requires, we use (abuse?) \PASCAL's variant record mechanism, to map three bytes into 4 six-bit characters. @== @!six_pack = packed record case boolean of false: (@!bytes : packed array [0..2] of eight_bits); true: (@!sixels: packed array [1..4] of [bit(6)] 0..63) end; @ The three bytes have to be placed in the record such that the earlier bytes (least significant in the file) are at the left-ward end of the record. The resultant six-bit integers are converted to ``printable'' characters by adding the |ASCII| character `\.{?}', thus giving characters in the range `\.{?}'$\to$`\.{\~}'. It is these characters that DEC calls ``sixels'', being a representation of six pixels in one character. It is most convenient to process 96 bytes at a time, thus yielding 128 characters records (of sixel characters). Accordingly, we firstly ensure that the file is some multiple of 96 bytes long. @= while (lnf_len mod 96) <> 0 do begin lnf_word(lnf_len,0); { Clear out remainder of ``last record'' } lnf_len:=lnf_len+2 end; { Can't work in longwords, in case on odd word boundary } records:=lnf_len div 96; i:=0; j:=0; {Block/byte in |lnf| array} while records > 0 do begin line_out:=''; {Null string} sixel_ct:=0; {We write 128 sixels (=96 bytes) per record} repeat k:=2; repeat transform.bytes[k]:=lnf[i].bytes[j]; {Store bytes from left} incr(j); decr(k); while j>=512 do begin incr(i); j:=j-512 end until k<0; {3 bytes at a time} for k:=4 downto 1 do begin line_out:=line_out+chr(transform.sixels[k]+"?"); incr(sixel_ct) end; until sixel_ct=128; write_ln(ln3_file,line_out); decr(records) end @* Skipping pages. A routine that's much simpler than |do_page| is used to pass over pages that are not being translated. The |skip_pages| subroutine is assumed to begin just after the preamble has been read, or just after a |bop| has been processed. It continues until either finding a |bop| that matches the desired starting page specifications, or until running into the postamble. In this version of \.{DVItoLN03}, it is necessary to ``read'' through all the pages twice; on the first pass, we gather statistics about the usage of each character in every font, to minimize the number of rasters which have to be loaded into the LN03. This procedure |skip_pages| has therefore been modified to permit its use during this scan. In such use, it is first called in the normal fashion, truly to skip pages until |start_match| indicates that the first page to be output has been found (or the beginning of the postamble, in the event that the specified first page does not exist). This results in |started| becoming |true|. A further call of |skip_pages|, with |started| asserted, records the use of each character. Whilst this ``scanning'' phase is taking place, the Boolean |scanning| inhibits writing to the \.{TYP} file. @p procedure skip_pages; label 9999; {end of this subroutine} var p:integer; {a parameter} @!k:0..255; {command code} @!down_the_drain:integer; {garbage} @!pages_counted:integer; {counts ``wanted'' pages whilst usage is calculated } begin pages_counted:=1; {We've already met the first page requested} while true do begin if eof(dvi_file) then bad_dvi('the file ended prematurely'); @:Bad DVI file the file ended prematurely}{\quad\.{the file ended prematurely}@> k:=get_byte; p:=first_par(k); case k of bop: begin @; if not started and start_match then begin started:=true; goto 9999; end; if started then begin incr(pages_counted); {We've got another page that's wanted} if (pages_counted mod 10)=0 then monitor('.'); {show progress} if pages_counted>max_pages then {We can skip to the postamble} begin old_backpointer:=first_backpointer; move_to_byte(post_loc); monitor('done.'+crlf); term_offset:=0 end; end; end; set_rule,put_rule: down_the_drain:=signed_quad; fnt_def1,fnt_def1+1,fnt_def1+2,fnt_def1+3: begin define_font(p); print_ln(' '); end; xxx1,xxx1+1,xxx1+2,xxx1+3: while p>0 do begin down_the_drain:=get_byte; decr(p); end; post: begin in_postamble:=true; monitor('done.'+crlf); term_offset:=0; goto 9999; end; @; @; othercases do_nothing endcases; end; 9999:end; @ If (!) we meet a command that would require a character to be set on the paper, we note that the relevant character has been used, provided the page being ``skipped'' is one that is to be printed (\\{i.e.} |started| has been set). The |pages_counted| variable is used to inhibit the gathering of such statistics after the specified number of pages have been ``read''. We mark both the individual glyph and the font itself as being |wanted|. @= sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64), @/ four_cases(set1),four_cases(put1): @/ if started and (pages_counted<=max_pages) then begin cur_font_glyph(p).loaded:=wanted; font_type[cur_font]:=wanted end @ Whenever a font change would have occurred, we determine the |cur_font| so that the above section, which notes the use of a character, can relate this to the correct font. @= sixty_four_cases(fnt_num_0),four_cases(fnt1): @/ begin font_num[nf]:=p; cur_font:=0; while font_num[cur_font]<>p do incr(cur_font); cur_font:=font_map[cur_font]; cur_base:=glyph_base[cur_font] end @ Global variables called |old_backpointer| and |new_backpointer| are used to check whether the back pointers are properly set up. Another one tells whether we have already found the starting page. @= @!old_backpointer:integer; {the previous |bop| command location} @!new_backpointer:integer; {the current |bop| command location} @!started:boolean; {has the starting page been found?} @ @= old_backpointer:=-1; started:=false; @ @= new_backpointer:=cur_loc-1; incr(page_count); for k:=0 to 9 do count[k]:=signed_quad; if signed_quad<>old_backpointer then error('backpointer in byte ',cur_loc-4:1, ' should be ',old_backpointer:1,'!'); @:Error: backpointer in byte should be}{\quad\.{backpointer...should be...}@> old_backpointer:=new_backpointer @* Using the backpointers. The routines in this section of the program are brought into play only if |random_reading| is |true|. First comes a routine that illustrates how to find the postamble quickly. @= n:=dvi_length; if n<53 then bad_dvi('only ',n:1,' bytes long'); @:Bad DVI file only n bytes long}{\quad\.{only $n$ bytes long}@> m:=n-1; repeat if m=0 then bad_dvi('no 223s'); {ignore trailing zeroes} @:Bad DVI file no 223s}{\quad\.{no 223s}@> move_to_byte(m); k:=get_byte; decr(m); until k<>0; repeat if m=0 then bad_dvi('all 223s'); @:Bad DVI file all 223s}{\quad\.{all 223s}@> move_to_byte(m); k:=get_byte; decr(m); until k<>223; if k<>id_byte then bad_dvi('ID byte is ',k:1); @:Bad DVI file ID byte is wrong}{\quad\.{ID byte is wrong}@> move_to_byte(m-3); q:=signed_quad; if (q<0)or(q>m-33) then bad_dvi('post pointer ',q:1,' at byte ',m-3:1); @:Bad DVI file post pointer is wrong}{\quad\.{post pointer is wrong}@> move_to_byte(q); k:=get_byte; if k<>post then bad_dvi('byte ',q:1,' is not post'); @:Bad DVI file byte n is not post}{\quad\.{byte $n$ is not }\\{post}@> post_loc:=q; first_backpointer:=signed_quad @ Note that the last steps of the above code save the locations of the the |post| byte and the final |bop|. We had better declare these global variables, together with another one that we will need shortly. @= @!post_loc:integer; {byte location where the postamble begins} @!first_backpointer:integer; {the pointer following |post|} @!start_loc:integer; {byte location of the first page to process} @ The next little routine shows how the backpointers can be followed to move through a \.{DVI} file in reverse order. Ordinarily a \.{DVI}-reading program would do this only if it wants to print the pages backwards or if it wants to find a specified starting page that is not necessarily the first page in the file; otherwise it would of course be simpler and faster just to read the whole file from the beginning. @= monitor('Finding starting page...'); @.Finding starting page@> q:=post_loc; p:=first_backpointer; start_loc:=-1; if p<0 then in_postamble:=true else begin repeat {now |q| points to a |post| or |bop| command; |p>=0| is prev pointer} if p>q-46 then bad_dvi('page link ',p:1,' after byte ',q:1); @:Bad DVI file page link wrong}{\quad\.{page link wrong...}@> q:=p; move_to_byte(q); k:=get_byte; if k=bop then incr(page_count) else bad_dvi('byte ',q:1,' is not bop'); @:Bad DVI file byte n is not bop}{\quad\.{byte $n$ is not }\\{bop}@> for k:=0 to 9 do count[k]:=signed_quad; if start_match then start_loc:=q; p:=signed_quad; @; until p<0; if start_loc<0 then abort('starting page number could not be found!'); @:fatal error starting page number}{\quad\.{starting page number...}@> move_to_byte(start_loc+1); old_backpointer:=start_loc; for k:=0 to 9 do count[k]:=signed_quad; p:=signed_quad; started:=true; end; if page_count<>total_pages then warning('there are really ',page_count:1,' pages, not ',total_pages:1,'!'); @:Warning: there are really n pages}{\quad\.{there are really $n$ pages}@> monitor('found.'+crlf); term_offset:=0 @ One of the reasons for using the above routine (which seems rather wasteful if the user wants to print pages starting from the beginning of the \.{DVI} file) is so that we can determine whether any particular \\{recto} page is followed by another, without an intervening \\{verso} page. This information can then be used at printing time to change the printing mode of the DEClaser~2200 (LN06), which supports duplex printing, such that the first of these pages be printed in simplex mode. Doing this speeds up the printing cycle compared with merely outputting a blank \\{verso} page, since the mechanical operation of turning over the paper is thereby avoided. Therefore we construct an array of pointers into the \.{DVI} file where an entry indicates that the page is followed by a blank \\{verso} sheet. Here are the necessary data structures: @= @!blank_follows:array[1..max_blank_pages] of integer; @!ptr_blanks:0..max_blank_pages; @!next_is_recto:boolean; @ Naturally we initialize this such that a final \\{recto} page will have its blank \\{verso} side treated by the same mechanism. @= next_is_recto:=true; ptr_blanks:=0; @ As we work our way backwards through the file, we have the opportunity to recognize that a \\{recto} page is followed directly by another of the same, without an intervening \\{verso} page. This code relies upon \TeX\ having used its \.{\\count0} for the page number, which is the usual convention, even when other counters are used to provide additional information in page numbering (such as the chapter number). When we later process the information in the |blank_follows| array, we do so in reverse order, which therefore corresponds with the processing of the file in the forward direction. @= if odd_page and next_is_recto then if ptr_blanks=max_blank_pages then capacity_exceeded('too many blank verso pages') @:capacity exceeded too many blank verso}{\quad\.{too many blank }\\{verso}\.{ pages}@> else begin incr(ptr_blanks); blank_follows[ptr_blanks]:=cur_loc end; next_is_recto:=odd_page @* Reading the postamble. Now imagine that we are reading the \.{DVI} file and positioned just four bytes after the |post| command. That, in fact, is the situation, when the following part of \.{DVItoLN03} is called upon to read, translate, and check the rest of the postamble. @p procedure read_postamble; var k:integer; {loop index} @!p,@!q,@!m:integer; {general purpose registers} begin post_loc:=cur_loc-5; print_ln('Postamble starts at byte ',post_loc:1,'.'); @.Postamble starts at byte n@> if signed_quad<>numerator then warning('numerator doesn''t match the preamble!'); @:warning numerator doesn't match}{\quad\.{numerator doesn't match...}@> if signed_quad<>denominator then warning('denominator doesn''t match the preamble!'); @:warning denominator doesn't match}{\quad\.{denominator doesn't match...}@> if signed_quad<>mag then if new_mag=0 then warning('magnification doesn''t match the preamble!'); @:warning magnification doesn't match}{\quad\.{magnification doesn't match...}@> max_v:=signed_quad; max_h:=signed_quad;@/ max_v_saved:=max_v; max_h_saved:=max_h;@/ print('maxv=',max_v:1,', maxh=',max_h:1);@/ max_s:=get_two_bytes; total_pages:=get_two_bytes;@/ print_ln(', maxstackdepth=',max_s:1,', totalpages=',total_pages:1); @; @; end; @ When we get to the present code, the |post_post| command has just been read. @= q:=signed_quad; if q<>post_loc then error('bad postamble pointer in byte ',cur_loc-4:1,'!'); @:Error: bad postamble pointer}{\quad\.{bad postamble pointer}@> m:=get_byte; if m<>id_byte then error('identification in byte ',cur_loc-1:1, @:Error: identification in byte p should be n}{\quad\.{identification...should be $n$}@> ' should be ',id_byte:1,'!'); k:=cur_loc; m:=223; while (m=223)and not eof(dvi_file) do m:=get_byte; if not eof(dvi_file) and (m<>0) then bad_dvi('signature in byte ',cur_loc-1:1, @:Bad DVI file signature...should be}{\quad\.{signature...should be...}@> ' should be 223') else if cur_loc cur_loc-k:1,')'); @ @= repeat k:=get_byte; if (k>=fnt_def1)and(knop; if k<>post_post then error('byte ',cur_loc-1:1,' is not postpost!') @:Error: byte n is not postpost}{\quad\.{byte $n$ is not }\\{postpost}@> @* The main program. Now we are ready to put it all together. This is where \.{DVItoLN03} starts, and where it ends. These macros define various possible exit values for the program, through the VAX/VMS system service \.{\$exit}. @^system dependencies@> @d VAX_exit==@=$exit@> @d VAX_ss_normal==@= sts$k_success @> @d VAX_ss_warning==@= sts$k_warning + sts$m_inhib_msg @> @d VAX_ss_error==@= sts$k_error + sts$m_inhib_msg @> @d VAX_ss_fatal==@= sts$k_severe + sts$m_inhib_msg @> @p begin initialize; {get all variables initialized} dialog; {set up all the options} write_ln(term_out,'(',actual_file_spec,crlf); print_ln('Reading from file ',actual_file_spec); @; @; in_postamble:=true; read_postamble; in_postamble:=false; @; if not in_postamble then @; if not in_postamble then @; close(ln3_file,@=disposition:=save@>,VAX_continue); monitor(')'); final_end: ask := (history > spotless) or odd(VAX_cli_present('LOG')); if VAX_cli_present('LOG') = VAX_cli_negated then ask := false; if ask then close(type_file,@=disposition:=save@>,VAX_continue) else close(type_file,@=disposition:=delete@>,VAX_continue); case history of { Issue an appropriate VAX exit status } spotless: VAX_exit(VAX_ss_normal); { Everything OK! } warning_given: VAX_exit(VAX_ss_warning); error_given: VAX_exit(VAX_ss_error); fatal_error: VAX_exit(VAX_ss_fatal) end; end. @ The main program needs a few global variables in order to do its work. @= @!k,@!m,@!n,@!p,@!q:integer; {general purpose registers} @ A \.{DVI}-reading program that reads the postamble first need not look at the preamble; but \.{DVItoLN03} looks at the preamble in order to do error checking, and to display the introductory comment. @= open_dvi_file; p:=get_byte; {fetch the first byte} if p<>pre then bad_dvi('First byte isn''t start of preamble!'); @:Bad DVI file First byte isn\'t}{\quad\.{First byte isn't...}@> p:=get_byte; {fetch the identification byte} if p<>id_byte then error('identification in byte 1 should be ',id_byte:1,'!'); @:Error: identification in byte p should be n}{\quad\.{identification...should be $n$}@> @; p:=get_byte; {fetch the length of the introductory comment} print(''''); while p>0 do begin decr(p); print(xchr[get_byte]); end; print_ln('''') @ The conversion factor |conv| is figured as follows: There are exactly |n/d| \.{DVI} units per decimicron, and 254000 decimicrons per inch, and |resolution| pixels per inch. Then we have to adjust this by the stated amount of magnification. @= numerator:=signed_quad; denominator:=signed_quad; if numerator<=0 then bad_dvi('numerator is ',numerator:1); @:Bad DVI file numerator is wrong}{\quad\.{numerator is wrong}@> if denominator<=0 then bad_dvi('denominator is ',denominator:1); @:Bad DVI file denominator is wrong}{\quad\.{denominator is wrong}@> print_ln('numerator/denominator=',numerator:1,'/',denominator:1); conv:=(numerator/254000.0)*(resolution/denominator); mag:=signed_quad; if new_mag>0 then mag:=new_mag else if mag<=0 then bad_dvi('magnification is ',mag:1); @:Bad DVI file magnification is wrong}{\quad\.{magnification is wrong}@> true_conv:=conv; conv:=true_conv*(mag/1000.0); print_ln('magnification=',mag:1,'; ',conv:16:8,' pixels per DVI unit') @ The code shown here uses a convention that has proved to be useful: If the starting page was specified as, e.g., `\.{1.*.-5}', then all page numbers in the file are displayed by showing the values of counts 0, 1, and~2, separated by dots. These numbers are displayed on the terminal when DVItoLN03 is working on that page. The parameter |-1| in the call of |do_page| indicates to the latter that this is \&{not} a recursive call of the function, and hence that it should initialize its data structures for a fresh page. @= begin while max_pages>0 do begin decr(max_pages); print_ln(' '); print(cur_loc-45:1,': beginning of page '); text_out:='['; last_counter:=0; for k:=0 to 9 do if count[k]<>0 then last_counter:=k; for k:=0 to last_counter do begin if (k=0) or (count[k]<>0) then begin print(count[k]:1); text_out:=text_out+str_int(count[k]) end; if k incr(pages_printed); repeat k:=get_byte; if (k>=fnt_def1)and(knop; write_ln(term_out,']'); incr(term_offset); {Let user know page has finished} if k=post then begin in_postamble:=true; goto done; end; if k<>bop then bad_dvi('byte ',cur_loc-1:1,' is not bop'); @:Bad DVI file byte n is not bop}{\quad\.{byte $n$ is not }\\{bop}@> @; end; done: @ end @ A string variable |text_out| is provided to hold the characters of a page number. We also use |last_counter| to indicate the last significant item in the |count| array when displaying the page number. @= @!text_out : file_spec; @!last_counter : 0..9; @ No output for the terminal occurs before the pages are processed except for error and font load messages, all of which end with a newline. Therefore, we are quite safe in resetting |term_offset| during the initialisation phase. @= term_offset:=0; @* System-dependent changes. Here are the remaining changes to the program that are necessary to make \.{DVItoLN03} work on Vax/VMS. @^system dependencies@> We define here various variables required by the VAX. \yskip\hang |history| is used to record whether any warning or error messages (including fatal errors) have been reported. \yskip\hang |type_file| and |ln3_file| are |file| variables used for writing the log and output files, respectively. Advantage is taken of VAX-\PASCAL's |varying| arrays to hold variable length strings: |file_name| is used generally for names of files, whilst |def_file_name| initially holds the specification of the \.{.dvi} file being processed, from which the name is later extracted for use in forming the names of generated files. The remaining variables are used during the parsing of the file specification. @== @!history: exit_status; @!type_file: text; @!ln3_file: text; @!file_name,@!actual_file_spec,@!log_file_name, @!output_file_name:file_spec; @!file_message:file_spec; @!cmd_j,@!cmd_k:integer; @!ask : boolean; @ Here is the enumeration type used for |history|. @== @!exit_status = (@!spotless,@!warning_given,@!error_given,@!fatal_error); @ We now do all the VMS specific things to open the files required, etc. The \.{DVIfile} is opened with a fixed block length, and with \.{direct} access method, to permit |random_reading|. A |user_action| procedure is called, to perform the actual opening of the file; this gives us access to the various VAX data structures (|FAB|,|RAB| and |XAB|) and thus we are able to determine the actual length of the file. The |output| file (directed to the terminal) is opened without |carriage_control|, in order that |write_ln| may be used to ensure that each partial line of output is displayed on the screen. @= open(output,'SYS$OUTPUT',@=carriage_control:=none@>,VAX_continue); history := spotless; { We haven't had any errors (yet!) } @@; @@; @@; @@; @; @@; @@; @@; @@; @ When we parse the qualifiers pertaining to the physical font directories, we use the variable length strings |tex_pxl_font| and |tex_pk_font| to hold the directory specifications derived from the command line qualifiers \.{/PXL\_FONT\_DIRECTORY} and \.{/PK\_FONT\_DIRECTORY}, respectively. Iff, after applying logical name translation (if necessary), a file directory specification is found to end in the sequence `\.{.]}', then a rooted file directory structure will be assumed; this is recorded in the variables |pk_rooted| and |pxl_rooted|. There is no such complication for the \.{/TFM\_DIRECTORY} qualifier; its value is merely copied to |tfm_directory|. @= @!tex_pxl_font,@!tex_pk_font,@!tfm_directory:file_spec; @!pxl_rooted,@!pk_rooted:boolean; @ Originally, this program had the location of the \TeX\ Font Metrics files (\.{.TFM}) `hard-wired' into it, just as the VMS implementation of \TeX\ itself did, through the logical name \.{TEX\$FONTS}. \TeX\ has since been modified so that such information is communicated to the program through a command-line qualifier, and this program too now makes use of the same mechanism. This permits two things: \yskip\hang$\bullet$ The site manager can chose whether to use logical names of the form \.{TEX\UL FONTS} or \.{TEX\$FONTS} ({\sl Digital\/} recommends that customers should \&{not} define logical names containg the `\.\$' character, since the logicals which they themselves define \&{always} contain this character, and may at some future date conflict with customer-defined logicals). \yskip\hang$\bullet$ Individual users may define their own logical names that provide a search-list for a number of directories; this can prove useful whilst testing a new font that should not be installed for general access. @= if odd(VAX_cli_present('TFM_DIRECTORY')) then k:=VAX_cli_get_value('TFM_DIRECTORY',tfm_directory) else tfm_directory:='' @ At this point we ``read'' the font directory specifications passed as the values of the qualifiers \.{/PXL\_FONT\_DIRECTORY} and \.{/PK\_FONT\_DIRECTORY}. At least one of these must be provided with a value. The specifications are transferred to the variables |tex_pxl_font| and |tex_pk_font| respectively, and logical name translation is applied. If the resultant string doesn't end with a `\.]', an error is signalled. If the character \&{preceding} this bracket is `\..', the final `\.]' is discarded. @== tex_pk_font:=''; tex_pxl_font:=''; ask:=odd(VAX_cli_present('PK_FONT_DIRECTORY')); if ask then begin VAX_cli_get_value('PK_FONT_DIRECTORY',tex_pk_font); Translate(tex_pk_font,file_name); if file_name[file_name.length] <> ']' then abort('Bad /PK_FONT_DIRECTORY qualifier; doesn''t end with a `]'''); @:fatal error Bad pk_font_directory}{\quad\.{Bad /PK_FONT_DIRECTORY qualifier}@> pk_rooted := file_name[file_name.length-1] = '.'; end; if odd(VAX_cli_present('PXL_FONT_DIRECTORY')) then begin VAX_cli_get_value('PXL_FONT_DIRECTORY',tex_pxl_font); Translate(tex_pxl_font,file_name); if file_name[file_name.length] <> ']' then abort('Bad /PXL_FONT_DIRECTORY qualifier; doesn''t end with a `]'''); @:fatal error Bad pxl_font_directory}{\quad\.{Bad /PXL_FONT_DIRECTORY qualifier}@> pxl_rooted := file_name[file_name.length-1] = '.'; ask:=true; end; if not ask then abort('Where are the font directories? (/PK_FONT_DIRECTORY, etc)'); @:fatal error Where are the font directories}{\quad\.{Where are the font directories?}@> @ Similarly, we determine the directory in which virtual font metrics are to be found; if the qualifier \.{/VIRTUAL\UL DIRECTORY} is negated, then virtual font support is explicitly suppressed. @== tex_virtual:=''; i := VAX_cli_present('VIRTUAL_DIRECTORY'); no_virt_support := i = VAX_cli_negated; if odd(i) then VAX_cli_get_value('VIRTUAL_DIRECTORY',tex_virtual); @ The variable length string |tex_virtual| receives this definition. If \.{/NOVIRTUAL\UL DIRECTORY} has been specified, this is recorded in |no_virt_support|. @= @!tex_virtual:file_spec; @!no_virt_support:boolean; @ We now ask the Vax/VMS command-line interpreter to give us the file specification furnished by the user to the command which invokes DVItoLN03. The file is opened \.{readonly} to preclude any possibility of corrupting or deleting it, and the \.{user\_action} paramter of the VAX-\PASCAL\ |open| procedure is used, so that we can determine the full file specification and length of the file, which is also opened for random (\.{direct}) access. We can't use |abort| for the fatal error reports, because the log file hasn't yet been opened, so we define this instead: @d bomb_out(#)==begin write_ln(output,crlf,'Fatal error: ',#,crlf); jump_out end @= def_file_name:=''; ask:=odd(VAX_cli_get_value('FILESPEC',def_file_name)); if not ask then bomb_out('No file name provided'); @.No file name provided@> @:fatal error no file name provided}{\quad\.{No file name provided}@> open(dvi_file,def_file_name,@=readonly@>,@=access_method:=direct@>, @=user_action@>:=file_open,@=default@>:='.DVI;0',VAX_continue); ask:=status(dvi_file)<>0; dvi_size:=file_len; if ask then bomb_out('Couldn''t open ',def_file_name); @:fatal error couldn\'t open DVI}{\quad\.{Couldn't open .DVI file}@> actual_file_spec:=def_file_name; @ The user has the option to specify an explicit file name on the \.{/LOG} qualifier. If none is specified, or the qualifier is absent, the log file will be created in the current directory, with the same name as the input file and a file extension of `\.{.TYP}'. These are also the defaults used if the relevant portion of the file specification is not given with the \.{/LOG} qualifier. @= log_file_name:=''; {Null name specified in case defaults required} if odd(VAX_cli_present('LOG')) then VAX_cli_get_value('LOG',log_file_name); @ After we've successfully opened the \.{DVI} file (and are thus assured that it exists), we strip off any node, device or directory specification from the front of the string, and also remove any trailing file type or version number specification. This makes use of the VAX-\PASCAL\ |substr| function, but could readily be recoded into standard \PASCAL. @= cmd_j:=1; {Strip off any leading node, device or directory name} for cmd_k:=1 to def_file_name.length do if (def_file_name[cmd_k]=']') or (def_file_name[cmd_k]=':') or (def_file_name[cmd_k]='>') then cmd_j:=cmd_k+1; if cmd_j<=def_file_name.length then def_file_name:=substr(def_file_name,cmd_j,def_file_name.length-cmd_j+1);@/ cmd_j:=0; {and then strip off any trailing file type or version indication} for cmd_k:=1 to def_file_name.length do if (cmd_j=0) and @/ ((def_file_name[cmd_k]='.') or (def_file_name[cmd_k]=';')) then cmd_j:=cmd_k; if cmd_j=0 then cmd_j:=def_file_name.length+1; def_file_name:=substr(def_file_name,1,cmd_j-1); @ We now |open| the \.{TYP} file in the current directory, to act as a log file for this run of DVItoLN03. The file is opened with |disposition:=save| so that it will still `exist' even in the event of a crash; this means that the user can interrupt the program (with Ctrl-Y) and be assured that the only file that will be retained is the log. @= file_name:=def_file_name+'.TYP'; open(type_file,log_file_name,@=new@>,@=32767@>,@=disposition:=save@>, @=default@>:=file_name,VAX_continue); ask:=status(type_file)>0; if ask then bomb_out('Couldn''t create ',file_name); @:fatal error couldn\'t create TYP}{\quad\.{Couldn't create .TYP file}@> rewrite(type_file); @ A particularly useful qualifier for those with limited disk quota is \.{/OUTPUT}, which permits a user to specify a different output file specification from the default, which is to open the output file in the current directory with a name taken from the input (\.{.DVI}) file, and file extension `\.{.LN3}'. Using this qualifier permits a user to divert the output to a scratch disk. @= output_file_name := ''; {Prepare to use the default name} if odd(VAX_cli_present('OUTPUT')) then VAX_cli_get_value('OUTPUT',output_file_name); @ Finally, we create and |open| the \.{LN3} file. Again this is opened with |disposition:=delete|. @= file_name := def_file_name+'.LN3'; open(ln3_file,output_file_name,@=new@>,@=256@>,@=disposition@>:=@=delete@>, @=carriage_control@>:=@=none@>, @=default@>:=file_name,VAX_continue); ask:=status(ln3_file)>0; if ask then begin abort('Couldn''t create ',file_name); @:fatal error couldn\'t create LN3 file}{\quad\.{Couldn't create .LN3 file}@> goto final_end end; rewrite(ln3_file); @ Whenever a binary file is opened, the optional |user_action| parameter is provided to the call of the VAX-\PASCAL\ |open| procedure; this therefore invokes the procedure |file_open| (in the next section) which is therefore able to determine the full specification of the file opened, and also its exact length; the former is placed into |def_file_name|, and the latter in |file_len|. The following definitions permit more \PASCAL-like references to the VAX/RMS data structures within |file_open| @d VAX_FAB_type == @=fab$type@> @d VAX_RAB_type == @=rab$type@> @d VAX_NAM_type == @=nam$type@> @d VAX_XAB_type == @=xab$type@> @d VAX_RMS_open == @=$OPEN@> @d VAX_RMS_connect == @=$CONNECT@> @d VAX_fab_to_nam == @=fab$l_nam@> {Pointer in FAB to NAM block} @d VAX_nam_length == @=nam$b_rsl@> {Length of resultant file name} @d VAX_nam_address == @=nam$l_rsa@> {and its whereabouts} @d VAX_fab_to_xab == @=fab$l_xab@> {Pointer in FAB to first XAB} @d VAX_xab_code == @=xab$b_cod@> {Byte in XAB which identifies its type} @d VAX_xab_fhc == @=xab$c_fhc@> {XAB contains file header characteristics} @d VAX_xab_next == @=xab$l_nxt@> {Pointer to another XAB} @d VAX_end_block == @=xab$l_ebk@> {Number of file block containing EOF} @d VAX_first_free == @=xab$w_ffb@> {First free byte in last block} @= @!def_file_name : file_spec; @!file_len : integer; @ Here is the procedure |file_open|, which determines the actual length of the file being opened. It also extracts the full file specification of the \.{DVI} which it opens, and reports this back in |def_file_name|. Vax/VMS uses RMS (Record Management Services) to handle file operations, and this utilizes various structures, in particular, the FAB (File Access Block) and the RAB (Record Access Block). There are also a number of different XABs (eXtension Access Block), one of which contains the required information relating to the size of the file. A |user_action| procedure such as this is invoked by the VAX-\PASCAL\ |open| procedure after it has set up the FAB and RAB structures. The user is therefore able at that time to change these structures to alter the processing algorithms used by RMS. The file has \&{not}, however, been opened at that time; it is necessary to call the RMS \.{\$OPEN} service. Once the RAB has been completed to the user's satisfaction, the \.{\$CONNECT} service is called to associate the file variable with the records of the file. Due to the nonsensical manner in which the |FAB|, etc., are declared in \.{SYS\$LIBRARY:STARLET.PAS}, wherein the pointers to |XAB|s are declared to be of type |unsigned|, it is necessary for us to use type casts for assignments and usage as a pointer! @= function file_open(var @!fab:VAX_FAB_type; var @!rab:VAX_RAB_type; var @!f_spec:byte_file): integer; type @!xab_ptr = ^VAX_XAB_type; @!nam_ptr = ^VAX_NAM_type; @!char_ptr = ^char; var @!status:integer;@/ @!nam : nam_ptr; @!xab : xab_ptr; @!actual_length : integer; @!next : char_ptr; @!i : integer; begin file_len:=-1; {In case we can't determine actual file size} status:=VAX_RMS_open(fab); {Fill in the FAB and RAB} if odd(status) then begin status:=VAX_RMS_connect(rab); {Link the RAB to RMS} if odd(status) then begin nam:=fab.VAX_fab_to_nam::nam_ptr; if nam<>nil then begin actual_length := nam^.VAX_nam_length; def_file_name := ''; {Reset to empty} next:=nam^.VAX_nam_address::char_ptr; for i:=1 to actual_length do begin def_file_name := def_file_name + next^; next:=(next::integer + 1)::char_ptr; end; end; xab:=fab.VAX_fab_to_xab::xab_ptr; {Find the File Header Characteristics} while (xab<>nil) and (xab^.VAX_xab_code<>VAX_xab_fhc) do xab:=xab^.VAX_xab_next::xab_ptr; if xab<>nil then with xab^ do file_len:=int((VAX_end_block-1)*VAX_block_length+VAX_first_free); end; end; file_open:=status; {Return success or failure} end; {|file_open|} @ This function provides us with logical name translation. It utilizes the \.{SYS\$TRNLNM} system service in preference to the obsolete \.{SYS\$TRNLOG}. On entry, any terminal `\.:' on the string |log_name| is discarded before attempting logical name translation. If the latter succeeds, the translation is written back to |equivalence_string| and the function returns |true|; if translation fails, the original |log_name| is returned in |equivalence_string| and |false| returned. It is necessary to provide the local variable |copy_name| during the translation process, because VAX-\PASCAL\ does not permit the address of a |var| parameter to be taken, so we cannot use |equivalence_string| to receive the successive translations, and yet the address of the string must be passed to the system service as the buffer to receive the translated logical name. Similarly, we cannot use |log_name|, since that will be a local copy, so that |string_size| will be that of the original logical name. The next macro is used to hide the VAX-\PASCAL\ conformant schema from W{\mc EAVE}, and thus permit more meaningful formatting. @d VAX_conformant_schema(#)==VAX_volatile varying [#] of char @# @d VAX_trnlnm == @=$TRNLNM@> @d VAX_lnm_case_blind==@= lnm$m_case_blind@> @== function Translate(log_name : VAX_conformant_schema(string_size); var equivalence_string : VAX_conformant_schema(buf_size) ) : boolean; var @!i, @!status : integer; @!copy_name : VAX_volatile file_spec; @!return_length : VAX_volatile integer; @!attributes : unsigned; @!item_list : VAX_volatile array [0..1] of VMS_item_list; begin equivalence_string := log_name; {In case translation fails} copy_name := log_name; if copy_name[copy_name.length] = ':' then decr(copy_name.length); attributes := VAX_lnm_case_blind; return_length := 0; with item_list[0] do begin buffer_length := buf_size; item_code := @=LNM$_STRING@>; buffer_addr := @=iaddress@>(copy_name.body); ret_len_addr := @=iaddress@>(return_length); end; item_list[1].next_item := 0; status := VAX_trnlnm(attributes,'LNM$DCL_LOGICAL',copy_name,,item_list); if not odd(status) then Translate := false else begin copy_name.length := return_length; return_length := 0; while odd(VAX_trnlnm(attributes,'LNM$DCL_LOGICAL',copy_name,,item_list)) do begin copy_name.length := return_length; return_length := 0; end; Translate := true; equivalence_string := copy_name; end; end; @ Here are the new types introduced within that function. Many Vax/VMS system services make use of an |item_list| to pass information in and out. An |item_list| consists of a number of |item_list| elements, with each element containing the following fields: \centerline{\vtop{\offinterlineskip\hrule \halign{\vrule#&\ \strut#\hfil\ &\ #\hfil\ &\ #\hfil\ &\vrule#\cr height2pt&\omit&\omit&\omit&\cr &\hfil Name & \hfil Type & \hfil Usage&\cr height2pt&\omit&\omit&\omit&\cr \noalign{\hrule} height2pt&\omit&\omit&\omit&\cr &|buffer_length| & 16-bit word & Size of buffer&\cr &|item_code| & unsigned 16-bit word & Code for desired operation&\cr &|buffer_address| & Pointer to char & Address of buffer&\cr &|ret_len_addr| & Pointer to integer & To receive length of translation&\cr height2pt&\omit&\omit&\omit&\cr} \hrule }}\yskip \noindent This structure is overlaid with a single 32-bit integer whose use is solely to hold the value zero indicating the end of the list. @== @!VMS_item_list = packed record case boolean of true: ( @!buffer_length : sixteen_bits;@/ @!item_code : sixteen_bits;@/ @!buffer_addr : integer;@/ @!ret_len_addr : integer); false: ( @!next_item : integer) end; @* Index. Pointers to error messages appear here together with the section numbers where each ident\-i\-fier is used.