DEFINITION MODULE DVIReader; (* Author: Andrew Trevorrow Implementation: University of Hamburg Modula-2 under VAX/VMS version 4 Date Started: August, 1984 Description: DVIReader exports routines and data structures for moving about randomly in a TeX82 DVI file and interpreting pages. Revised: August, 1986 - The SpecialRoutine has two new parameters to pass the current page position. - The pixeltable record has a new flag that indicates if the character's bitmap has been loaded. - The fontinfo record has a new flag that indicates if the font exists, and a new string that stores a unique font identifier needed by PostScript. - Export CurrMatchesNew to help locate TeX page subrange. December, 1986 - Increased maxTeXchar from 127 to 255. - Lowered maxfontspec from 80 to 64. November, 1987 (while at The Open University) - Added psfont flag in fontinfo record. It is used to indicate a resident PostScript font (for which character bitmaps do not exist). For such a font, character metrics will have to be read from the TFM file. Limitations: - DVIReader does not look at the preamble and so ignores any comments there. - DVIReader does not do any terminal i/o, even upon detecting a fatal error. The client must write a DVIErrorRoutine that will be invoked at the time an error is detected. See below for details. - DVIReader knows nothing about the format of font files. It only knows they contain crucial information for typesetting a DVI file. The client must write a PixelTableRoutine to read some sort of font file and fill in the pixel table information used to interpret a DVI page. See below for details. - The data structures used to represent an interpreted DVI page are best suited to a client whose output device is randomly addressable. Some clients may need to convert the information in the data structures into a more suitable format. Algorithm for a simple DVI translator: < initialize the DVIErrorRoutine used to handle various errors > < initialize filespec, resolution, etc. > OpenDVIFile(filespec); < initialize magnification, probably with DVImag > SetConversionFactor(resolution,magnification); < initialize the SpecialRoutine used in InterpretPage > < initialize the PixelTableRoutine used in InterpretPage > FOR p := 1 TO totalpages DO MoveToDVIPage(p); InterpretPage; IF pageempty THEN < process an empty page > ELSE < check that the page edges are within the paper edges > < process the rule list > < process the character list for each font used on the page > END; END; CloseDVIFile; *) CONST ruletablesize = 300; (* maximum number of rules in a ruletable *) chartablesize = 3000; (* maximum number of chars in a chartable *) maxfontspec = 64; (* maximum length of a font file specification *) maxTeXchar = 255; (* ignore character codes > 255 *) TYPE (* The major page structures are defined first. Information about the rules and characters appearing on a page is stored in dynamic one-way lists to avoid imposing any limit on their numbers. To reduce pointer overheads, the nodes in these lists contain large tables (the values of ruletablesize and chartablesize have been chosen so that the vast majority of DVI pages will only require one-node lists). When interpreting a DVI page, DVIReader adds a new rule or character node to the TAIL of the relevant list. This is done so that when the client accesses such lists (starting at the head), rules and characters will be processed in somewhat the same sequence as seen in the DVI file; i.e., top-to-bottom and left-to-right across the page. Since a character list is part of the information stored for a font, the precise sequence in which DVI characters are seen is not remembered. Font information is also linked together in a one-way list, but the ordering is more or less random (see, however, the SortFonts routine). *) ruleinfoptr = POINTER TO ruleinfo; ruleinfo = (* a node in a list of ruletables *) RECORD rulecount : CARDINAL; (* number of rules in ruletable *) ruletable : ARRAY [0..ruletablesize-1] OF RECORD hp, vp : INTEGER; (* pixel coords of rule's ref point *) wd, ht : INTEGER; (* dimensions of rule in pixels *) END; nextrule : ruleinfoptr; (* next node in rule list *) END; charinfoptr = POINTER TO charinfo; charinfo = (* a node in list of chartables *) RECORD charcount : CARDINAL; (* number of chars in chartable *) chartable : ARRAY [0..chartablesize-1] OF RECORD hp, vp : INTEGER; (* pixel coords of char's ref point *) code : [0..maxTeXchar]; (* char's code and pixeltable index *) END; nextchar : charinfoptr; (* next node in char list *) END; (* pixeltable must be filled in by the client's PixelTableRoutine. DVIReader uses wd, ht, xo and yo to calculate minhp, minvp, maxhp and maxvp. dwidth and pwidth are used to advance horizontally. The mapadr and loaded fields are not used by DVIReader. *) pixeltableptr = POINTER TO pixeltable; pixeltable = ARRAY [0..maxTeXchar] OF RECORD wd, ht : INTEGER; (* glyph width and height in pixels; they define the size of the smallest box containing all the black pixels *) xo, yo : INTEGER; (* x and y offsets from top left corner of glyph to character's reference point *) dwidth : INTEGER; (* advance width in DVI units computed from fix width stored in font file *) pwidth : INTEGER; (* advance width in pixels computed from fix width stored in font file *) mapadr : CARDINAL; (* starting address of bitmap info in font file (could be a word or byte offset) *) loaded : BOOLEAN; (* has bitmap been downloaded? *) END; fontstring = ARRAY [0..maxfontspec-1] OF CHAR; fontinfoptr = POINTER TO fontinfo; fontinfo = (* a node in list of fonts *) RECORD psfont : BOOLEAN; (* is this a PostScript font? *) fontused : BOOLEAN; (* is font used on current page? *) fontnum : INTEGER; (* DVI font number: -2^31 .. 2^30 - 1 *) scaledsize : CARDINAL; (* scaled font size in DVI units *) designsize : CARDINAL; (* design size in DVI units *) fontarea : fontstring; (* explicit font directory *) fontarealen : CARDINAL; (* length of fontarea *) fontname : fontstring; (* font name; e.g., "amr10" *) fontnamelen : CARDINAL; (* length of font name *) fontspec : fontstring; (* client's font file specification *) fontspeclen : CARDINAL; (* length of fontspec *) fontexists : BOOLEAN; (* could fontspec be opened? *) fontid : fontstring; (* unique font identifier *) totalchars : CARDINAL; (* number of chars from font on page *) charlist : charinfoptr; (* head of char information list *) chartail : charinfoptr; (* tail of char information list *) pixelptr : pixeltableptr; (* allocated once: 1st time font used *) nextfont : fontinfoptr; (* next node in font list *) END; (* For the parameter in MoveToTeXPage: *) TeXcounters = ARRAY [0..9] OF INTEGER; TeXpageinfo = RECORD value : TeXcounters; (* \count0..\count9 values *) present : ARRAY [0..9] OF BOOLEAN; (* is counter relevant? *) lastvalue : [0..9]; (* last relevant counter *) END; (* For the parameter in client's DVIErrorRoutine: *) DVIerrorcodes = ( DVIunopened, (* OpenDVIFile could not open the given file *) DVIempty, (* OpenDVIFile detected an empty file *) DVIbadid, (* OpenDVIFile detected an invalid TeX82 DVI file *) DVIstackoverflow, (* OpenDVIFile found DVIReader's stack is too small *) DVIbadchar, (* InterpretPage is ignoring a char code > maxTeXchar (detected while processing currfont^) *) DVIcatastrophe); (* DVIReader detected a situation that should never occur; client should scream for help and halt *) (* For the function parameter in client's SpecialRoutine: *) GetByteFunction = PROCEDURE () : INTEGER; (* returns next DVI byte *) VAR (* Most of these should be treated as read-only parameters: *) DVImag : CARDINAL; (* magnification stored in DVI file *) totalpages : CARDINAL; (* number of pages in DVI file *) totalfonts : CARDINAL; (* number of fonts in DVI file *) currDVIpage : CARDINAL; (* updated by MoveTo... calls *) currTeXpage : TeXcounters; (* ditto *) totalrules : CARDINAL; (* number of rules on current page *) rulelist : ruleinfoptr; (* head of rule information list *) fontlist : fontinfoptr; (* head of font information list *) currfont : fontinfoptr; (* InterpretPage's current font info *) pageempty : BOOLEAN; (* is page empty of rules and chars? *) minhp : INTEGER; (* minimum horizontal pixel coordinate *) minvp : INTEGER; (* minimum vertical pixel coordinate *) maxhp : INTEGER; (* maximum horizontal pixel coordinate *) maxvp : INTEGER; (* maximum vertical pixel coordinate *) DVIErrorRoutine : PROCEDURE (DVIerrorcodes); (* The client MUST assign a procedure before the first OpenDVIFile call. Various DVIReader routines will call this procedure if they detect some sort of problem; the given argument will be one of the error codes described above. The client's routine can use this code to see what sort of error has occurred and take appropriate action. Most errors detected by DVIReader are fatal; the client should print some sort of message and halt. See PSDVI for an example DVIErrorRoutine. *) SpecialRoutine : PROCEDURE (INTEGER, INTEGER, INTEGER, GetByteFunction); (* The client can assign a procedure before the first InterpretPage call. InterpretPage will call this procedure to process the bytes belonging to a \special command. The first two arguments give the (x,y) pixel coordinates defining the position of the \special command. The third argument gives the number of bytes; the last argument argument is a function that returns the value of the next byte (0..255, but usually the ordinal value of a displayable ASCII character). If the client does not supply a SpecialRoutine, DVIReader will simply ignore any \special bytes. *) PixelTableRoutine : PROC; (* The client MUST assign a procedure before the first InterpretPage call. InterpretPage will call this procedure immediately after allocating a pixel table for currfont^. This will only occur once per font (the very first time the font is used). DVIReader only knows about DVI files; the task of the PixelTableRoutine is to fill in the current font's pixel table by reading the fontspec file. (The fontspec string must first be built using the fontname.) This file could be a PXL file, or some other type of font file. *) PROCEDURE OpenDVIFile (filespec : ARRAY OF CHAR); (* This must be the first DVIReader routine called. If the given filespec can be opened (and is a valid TeX82 DVI file) then the following global variables are initialized: DVImag := magnification value stored in DVI file (TeX's \mag) totalpages := total number of pages in DVI file currDVIpage := 0 (and remains so until a page is selected) currTeXpage := ten 0s (ditto) totalfonts := total number of fonts in DVI file (= nodes in font list) fontlist^. (nodes are added to head of list) fontused := FALSE fontnum := internal DVI font number scaledsize := scaled size of font (in DVI units) designsize := design size of font (in DVI units) fontarea := a string of min(fontarealen,maxfontspec) characters fontname := a string of min(fontnamelen,maxfontspec) characters fontspec := a null string (fontspeclen := 0) fontexists := FALSE totalchars := 0 charlist := NIL chartail := NIL pixelptr := NIL nextfont := next node in font list (if not NIL) *) PROCEDURE SetConversionFactor (resolution, magnification : CARDINAL); (* This routine must be called before the first InterpretPage call. DVIReader needs to know the client's resolution and magnification values before it attempts to convert DVI dimensions into pixel values. *) PROCEDURE MoveToDVIPage (n : CARDINAL); PROCEDURE MoveToNextPage (ascending : BOOLEAN); PROCEDURE MoveToTeXPage (VAR newTeXpage : TeXpageinfo) : BOOLEAN; (* Before calling InterpretPage, the client must position DVIReader to the desired page by calling one of these MoveTo... routines. MoveToDVIPage will select the nth page in the DVI file; nothing will happen if n is not in 1..totalpages. MoveToNextPage will select the next page, depending on the current page and the specified direction. If the value of currDVIpage is 0 (set in OpenDVIFile), then MoveToNextPage will select the first page if ascending is TRUE and the last page if ascending is FALSE. If currDVIpage is > 0 then MoveToNextPage will select currDVIpage+1 if ascending (unless currDVIpage = totalpages, in which case it does nothing), or currDVIpage-1 if descending (unless currDVIpage = 0). MoveToTeXPage will search for the lowest DVI page matching the given TeX page specification. (TeX stores the values of \count0,\count1,...,\count9 with every DVI page. Plain TeX uses \count0 to control page numbering.) newTeXpage is a VAR parameter only for efficiency; it won't be changed. The value array stores the requested counter values, the present array indicates which counters are relevant and lastvalue indicates the position (0..9) of the last relevant counter. The client will probably need to first convert a more friendly representation of a TeX page request into the TeXpageinfo format. For example, [2..5] would be converted to: and [] would be converted to: value = [2,?,5,?,?,?,?,?,?,?] value = [?,?,?,?,?,?,?,?,?,?] present = [T,F,T,?,?,?,?,?,?,?] present = [F,?,?,?,?,?,?,?,?,?] lastvalue = 2 lastvalue = 0 MoveToTeXPage returns TRUE iff the requested TeX page is located. The global variables updated if a page is located by any MoveTo... call are: currDVIpage := the current DVI page (1..totalpages) currTeXpage := the ten TeX counter values stored with this page Note that currDVIpage is initially 0 until one of these routines succeeds. *) PROCEDURE CurrMatchesNew (VAR newTeXpage : TeXpageinfo) : BOOLEAN; (* Return TRUE iff currTeXpage matches the \count information in newTeXpage. *) PROCEDURE PixelRound (DVIunits : INTEGER) : INTEGER; (* Converts the given DVI dimension (+ve or -ve) into pixels. *) PROCEDURE InterpretPage; (* This routine will interpret the current DVI page and update the major data structures: totalrules := number of rules on page rulelist^. (nodes are added to tail of rule list) rulecount := number of rules in this ruletable ruletable[0..rulecount-1]. hp, vp := reference point of a rule wd, ht := pixel dimensions of a rule (both > 0) nextrule := next node in rule list (if not NIL) fontlist^. (the following fontinfo is relevant only if fontused is TRUE) totalchars := number of chars on page from this font charlist^. (nodes are added to tail of char list) charcount := number of chars in this chartable chartable[0..charcount-1]. hp, vp := reference point of a character code := TeX character code (and index into pixel table) nextchar := next node in char list (if not NIL) chartail := pointer to last node in char list pixelptr^[0..maxTeXchar]. (filled in by client's PixelTableRoutine) wd, ht := glyph width and height in pixels xo, yo := offsets from the character's reference point dwidth := advance width in DVI units pwidth := advance width in pixels mapadr := offset in fontspec of bitmap info (0 if absent or white) loaded := FALSE nextfont := next node in font list (if not NIL) pageempty := TRUE iff the page has no rules and no characters minhp, minvp, maxhp, maxvp := the edges of the page (undefined if pageempty is TRUE) They define the smallest rectangle containing all black pixels AND all reference points on the page; (minhp,minvp) is the top left corner. Reference points for rules and characters are stored as a pair of horizontal and vertical pixel coordinates. The point (0,0) is assumed to be the pixel 1 inch in from the top and left edges of an imaginary sheet of paper. Horizontal coordinates increase to the right and vertical coordinates increase down the paper. The number of pixels per inch is defined by the resolution parameter given to SetConversionFactor. *) PROCEDURE SortFonts (VAR unusedlist : fontinfoptr); (* This routine will sort fontlist in ascending order of totalchars. The client may wish to do this after interpreting a page so that fonts with the least number of characters on the page will be processed first. The routine will also move all unused fonts to the end of fontlist so that the client need only process fonts up to (but excluding) unusedlist and not have to worry about checking the fontused flag. For example: SortFont(unusedlist); thisfont := fontlist; WHILE thisfont <> unusedlist DO < process the font information in thisfont^ > thisfont := thisfont^.nextfont; END; If unusedlist is NIL then either 1) all fonts are used on the current page or 2) fontlist is also NIL (totalfonts = 0). *) PROCEDURE CloseDVIFile; (* This routine closes the currently open DVI file; it is only mandatory if OpenDVIFile is going to be called again. All dynamic data structures are deallocated. *) END DVIReader.