
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, 1985
 - Expanded to include routines and data structures for interpreting DVI pages.
 - Allow client to provide a routine for handling TeX's \special command.
 - Page data structures are now much more dynamic.  No longer any limit on the
   number of fonts, the number of characters per font, or the number of rules.

   September, 1986
 - The SpecialRoutine has two new parameters to pass the current page position.

   November, 1986
 - Increased maxTeXchar from 127 to 255.
 - Lowered maxfontspec from 80 to 64.
 - Added fontexists flag in fontinfo record.
 - Added bitmap address in pixeltable record.

   November, 1987
 - Removed SpecialRoutine and added speciallist stuff.
 - Added psfont flag in fontinfo record.  It is used to indicate a resident
   PostScript font (for which character bitmaps do not exist).
   FontReader will set this flag if it sees a font name beginning with "PS-".
   For such a font, character metrics will have to be read from the TFM file.

   November, 1988 (Niel Kempson <rmcs-tex@uk.ac.cranfield.cdvc>,
		  RMCS Shrivenham, SWINDON SN6 8LA, United Kingdom)
 - Font loading strategy changed. Before PK OR PXL fonts were selected, based
   on the file specification of the dummy font. DVItoVDU will now attempt to
   load the font in PK format first, and if that is unsuccessful try the PXL
   format. Reversion to the dummy font and handling of PostScript is unaffected.
 - RECORD `fontinfo' changed to specify a list of font types, not just the
   BOOLEAN `psfont'. Added are 2 procedure variables to be used for filling 
   the pixel table and getting glyph bitmaps.

   September--October, 1989 (while at Aston University, 2nd time)
 - \special info is now added to tail of speciallist.

   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 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 special list >
            < process the rule list >
            < process the character list for each font used on the page >
         END;
      END;
      CloseDVIFile;
*)


  FROM SYSTEM IMPORT 
                     ADDRESS;                 (* for bitmap *)

  EXPORT QUALIFIED
         	ruletablesize, chartablesize,
         	maxfontspec, maxTeXchar,
         	GetBitmapProc, FontType, fontstring, fontinfo, fontinfoptr,
         	pixeltable, pixeltableptr,
         	currfont, ruleinfo, ruleinfoptr,
         	specialstring, specialinfo, specialinfoptr,
         	charinfo, charinfoptr,
         	TeXcounters, TeXpageinfo,
         	DVIerrorcodes, DVImag,
         	totalpages, totalfonts, totalrules,
         	currDVIpage, currTeXpage,
         	rulelist, speciallist, fontlist,
         	minhp, minvp, maxhp, maxvp, pageempty, DVIErrorRoutine,
         	PixelTableRoutine, OpenDVIFile, SetConversionFactor, MoveToNextPage,
         	MoveToDVIPage, MoveToTeXPage, InterpretPage, SortFonts, CloseDVIFile,
         	PixelRound;


  CONST
    specialsize   = 64;
    (* maximum length of a stored \special command    *)
    ruletablesize = 300;
    (* maximum number of rules in a ruletable         *)
    chartablesize = 3000;
    (* maximum number of chars in a chartable         *)
    maxfontspec   = 80;
    (* 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;

    specialstring  = ARRAY [0..specialsize-1] OF CHAR;
    specialinfoptr = POINTER TO specialinfo;
    specialinfo    = 
    (* a node in a list of specials     *)
      RECORD
         hp, vp : INTEGER;                (* pixel coords of special          *)
         contents : specialstring;        (* special bytes from DVI file      *)
         nextspecial : specialinfoptr;    (* next node in special 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 bitmap 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)             *)
                        bitmap : ADDRESS;
                        (* address of bitmap                            *)
                      END;

    GetBitmapProc = PROCEDURE (CARDINAL,       (* pixel height of bitmap *)
                               CARDINAL,       (* pixel width of bitmap *)
                               CARDINAL,       (* mapadr in font file *)
                               VAR ADDRESS);   (* starting address of bitmap *)
   (* 
      Allocate space for bitmap and fill it with raster info at mapadr
      in the currently open font file.
      Each bitmap row uses an integral number of words (each 32 bits).
      Note that byte-aligned rows would use about 35% less memory but
      would increase the processing time needed to display each bitmap.
      It was felt that speed is more important than memory.
   *)

    FontType    = (UnknownFont, PostScriptFont, PKFont, PXLFont);
    fontstring  = ARRAY [0..maxfontspec-1] OF CHAR;
    fontinfoptr = POINTER TO fontinfo;

    fontinfo    = 
    (* a node in list of fonts            *)
      RECORD
        	 fonttype    : FontType;
        	(* What type of font is this ?	      *)
        	 GetBitmap   : GetBitmapProc;
        (* and the procedure to get its bitmap*)
         FillPixelTable : PROC;
        		(* the proc to fill the pixel table   *)
         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., "cmr10"           *)
         fontnamelen : CARDINAL;        (* length of font name                *)
         fontspec    : fontstring;      (* client's font file specification   *)
         fontspeclen : CARDINAL;        (* length of fontspec                 *)
         fontexists  : BOOLEAN;         (* can fontspec be opened?            *)
         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    *)


  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         *)
    speciallist  : specialinfoptr;(* head of special information list        *)
    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 DVItoVDU for an example DVIErrorRoutine.
   *)

    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 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)

      speciallist^. (nodes are added to HEAD of this list)
         hp, vp      := reference point of \special
         contents    := string of bytes in \special command
         nextspecial := next node in special 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 the glyph's bitmap info
            bitmap  := NIL
         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.
