[net.sources] DVIDOC part 2/2

elwell@osu-eddie.UUCP (Clayton M. Elwell) (09/05/86)

This is part 2 of the DVIDOC posting.  Make sure and unshar part 1 first.
--------------------------------CUT HERE----------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to complete the file:
#	dvidoc.web
# This archive created: Fri Sep  5 12:27:57 1986
export PATH; PATH=/bin:$PATH
if test ! -f 'dvidoc.web'
then
	echo shar: please extract part 1 first...
	exit 1
else
cat << \SHAR_EOF >> 'dvidoc.web'
@.DVIDOC capacity exceeded...@>
font_name[nf+1]:=font_name[nf]+n+p;
write(term_out,'Font ',e:0,': ');
if n+p=0 then write(term_out,'null font name!')
@.null font name@>
else for k:=font_name[nf] to font_name[nf+1]-1 do names[k]:=get_byte;
incr(nf); print_font(nf-1); decr(nf)

@ @<Load the new font, unless there are problems@>=
begin @<Move font name into the |cur_name| string@>;
open_tfm_file;
if eof(tfm_file) then
  write(term_out,'---not loaded, TFM file can''t be opened!')
@.TFM file can\'t be opened@>
else  begin if (q<=0)or(q>=@'1000000000) then
    write(term_out,'---not loaded, bad scale (',q:0,')!')
@.bad scale@>
  else if (d<=0)or(d>=@'1000000000) then
    write(term_out,'---not loaded, bad design size (',d:0,')!')
@.bad design size@>
  else if in_TFM(q) then @<Finish loading the new font info@>;
  end;
write_ln(term_out,' ');
end

@ @<Finish loading...@>=
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
  begin write_ln(term_out,'---beware: check sums do not agree!');
@.beware: check sums do not agree@>
@.check sums do not agree@>
  write_ln(term_out,'   (',c:0,' vs. ',tfm_check_sum:0,')');
  write(term_out,'   ');
  end;
write(term_out,'---loaded at size ',q:0,' DVI units');
d:=trunc((100.0*horiz_conv*q)/(true_horiz_conv*d)+0.5);
if d<>100 then
  begin write_ln(term_out,' '); write(term_out,' (this font is magnified ',d:0,'%)');
  end;
@.this font is magnified@>
incr(nf); {now the new font is officially present}
end

@ If |p=0|, i.e., if no font directory has been specified, \.{DVIDOC}
is supposed to use the default font directory, which is a
system-dependent place where the standard fonts are kept.
The string variable |default_directory| contains the name of this area.
@^system dependencies@>
@^changed module@>

@d default_directory_name=='TEXFONTS:' {changed to the correct name}
@d default_directory_name_length=9 {changed to the correct length}

@<Glob...@>=
@!default_directory:packed array[1..default_directory_name_length] of char;

@ @<Set init...@>=
default_directory:=default_directory_name;

@ 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.
@^system dependencies@>

@<Move font name into the |cur_name| string@>=
for k:=1 to name_length do cur_name[k]:=' ';
if p=0 then
  begin for k:=1 to default_directory_name_length do
    cur_name[k]:=default_directory[k];
  r:=default_directory_name_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
    abort('DVIDOC capacity exceeded (max font name length=',
      name_length:0,')!');
@.DVIDOC capacity exceeded...@>
  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'

@* Low level output routines.
Characters set by the \.{DVI} file are placed in |page_buffer|, a two
dimensional array of characters with one element for each print
position on the page.  The |page_buffer| is cleared at the beginning
of each page and printed at the end of each page.  
|doc_file|, the file to which the document is destined, is an ordinary text
file.

To optimize the initialization and printing of |page_buffer|, a high
water mark line number, |page_hwm|, is kept to indicate the last line
that contains any printable characters, and for each line a high water
mark character number, |line_hwm|, is kept to indicate the location of
the last printable character in the line.

@<Glob...@>=
@!doc_file:text_file;
@!page_buffer:packed array[1..page_width_max,1..page_length_max] of ascii_code;
                 {storage for a document page}
@!line_hwm:array[1..page_length_max] of 0..page_width_max;
                 {high water marks for each line}
@!page_hwm: 0..page_length_max;  {high water mark for page}

@ |doc_file| needs to be opened.

@<Set initial values@>=
rewrite(doc_file);

@ The |flush_page| procedure will print the |page_buffer|.

@p procedure flush_page;
var i:0..page_width_max; j:0..page_length_max;
begin
  for j := 1 to page_hwm do begin
    for i := 1 to line_hwm[j] do
      write (doc_file, xchr[page_buffer[i,j]]);
    write_ln (doc_file) end;
  write (doc_file, chr(12))  {end the page with a form feed}  
end;

@ The |empty_page| procedure will empty the |page_buffer| data structure.

@p procedure empty_page;
begin page_hwm := 0 end;

@ And the |out_char| procedure puts something into it.  The usual printable
ascii characters will be put into the buffer as is.  Non-printable characters,
including the blank, will be put into the buffer as question mark chracters.

@p procedure out_char(p,hh,vv:integer);
var i:1..page_width_max; j:1..page_length_max;
     {|hh| and |vv| range from zero up while |i| and |j| range from one up.}
    k: integer;
    c: ascii_code;
begin
  if 
    (p>" ")and(p<="~") then c:=p
    else c:=xord['?'];
  if 
    (hh>page_width_max-1) or (vv>page_length_max-1) then begin 
      write_ln (term_out);
      write (term_out, 'Character "', xchr[c], '" set at column ', hh+1:0);
      write_ln (term_out, ' and row ', vv+1:0, ',');
      write (term_out, 'outside the range of DVIDOC (');
@.outside the range of DVIDOC@>
      write (term_out, page_width_max:0, ',', page_length_max:0, ').');
      write_ln (term_out) end
    else begin
      i := hh + 1;
      j := vv + 1;
      if j>page_hwm then begin {initialize any as yet untouched lines}
        for k := page_hwm+1 to j do line_hwm[k]:=0;
        page_hwm := j end;
      if i>line_hwm[j] then begin {initialize any as yet untouched characters}
        for k := line_hwm[j]+1 to i do page_buffer[k,j] := xord[' '];
        line_hwm[j] := i end;
      page_buffer[i,j] := c {put the character in its place}  end
end;

@* Translation to symbolic form.
The main work of \.{DVIDOC} is accomplished by the |do_page| procedure,
which produces the output for an entire page, assuming that the |bop|
command for that page has already been processed. This procedure is
essentially an interpretive routine that reads and acts on the \.{DVI}
commands.

@ The definition of \.{DVI} files refers to six registers,
$(h,v,w,x,y,z)$, which hold integer values in \.{DVI} units.  In practice,
we also need registers |hh| and |vv|, the pixel analogs of $h$ and $v$,
since it is not always true that |hh=horiz_pixel_round(h)| or
|vv=vert_pixel_round(v)|.

The stack of $(h,v,w,x,y,z)$ values is represented by eight arrays
called |hstack|, $\ldotss$, |zstack|, |hhstack|, and |vvstack|.

@<Glob...@>=
@!h,@!v,@!w,@!x,@!y,@!z,@!hh,@!vv:integer; {current state values}
@!hstack,@!vstack,@!wstack,@!xstack,@!ystack,@!zstack:
  array [0..stack_size] of integer; {pushed down values in \.{DVI} units}
@!hhstack,@!vvstack:
  array [0..stack_size] of integer; {pushed down values in pixels}

@ Three characteristics of the pages (their |max_v|, |max_h|, and
|max_s|) are specified in the postamble, and a warning message
is printed if these limits are exceeded. Actually |max_v| is set to
the maximum height plus depth of a page, and |max_h| to the maximum width,
for purposes of page layout. Since characters can legally be set outside
of the page boundaries, it is not an error when |max_v| or |max_h| is
exceeded. But |max_s| should not be exceeded.

The postamble also specifies the total number of pages; \.{DVIDOC}
checks to see if this total is accurate.

@<Glob...@>=
@!max_v:integer; {the value of |abs(v)| should probably not exceed this}
@!max_h:integer; {the value of |abs(h)| should probably not exceed this}
@!max_s:integer; {the stack depth should not exceed this}
@!max_v_so_far,@!max_h_so_far,@!max_s_so_far:integer; {the record high levels}
@!total_pages:integer; {the stated total number of pages}
@!page_count:integer; {the total number of pages seen so far}

@ @<Set init...@>=
max_v:=@'17777777777; max_h:=@'17777777777; max_s:=stack_size+1;@/
max_v_so_far:=0; max_h_so_far:=0; max_s_so_far:=0; page_count:=0;

@ Before we get into the details of |do_page|, it is convenient to
consider a simpler routine that computes the first parameter of each
opcode.

@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 function first_par(o:eight_bits):integer;
begin case o of
sixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64):
  first_par:=o-set_char_0;
set1,put1,fnt1,xxx1,fnt_def1: first_par:=get_byte;
set1+1,put1+1,fnt1+1,xxx1+1,fnt_def1+1: first_par:=get_two_bytes;
set1+2,put1+2,fnt1+2,xxx1+2,fnt_def1+2: first_par:=get_three_bytes;
right1,w1,x1,down1,y1,z1: first_par:=signed_byte;
right1+1,w1+1,x1+1,down1+1,y1+1,z1+1: first_par:=signed_pair;
right1+2,w1+2,x1+2,down1+2,y1+2,z1+2: first_par:=signed_trio;
set1+3,set_rule,put1+3,put_rule,right1+3,w1+3,x1+3,down1+3,y1+3,z1+3,
  fnt1+3,xxx1+3,fnt_def1+3: first_par:=signed_quad;
nop,bop,eop,push,pop,pre,post,post_post,undefined_commands: first_par:=0;
w0: first_par:=w;
x0: first_par:=x;
y0: first_par:=y;
z0: first_par:=z;
sixty_four_cases(fnt_num_0): first_par:=o-fnt_num_0;
end;
end;

@ Here are two other subroutines that we need: They compute the number of
pixels in the height or width of a rule. Characters and rules will line up
properly if the sizes are computed precisely as specified here.  (Since
|horiz_conv| and |vert_conv| 
are computed with some floating-point roundoff error, in a
machine-dependent way, format designers who are tailoring something for a
particular resolution should not plan their measurements to come out to an
exact integer number of pixels; they should compute things so that the
rule dimensions are a little less than an integer number of pixels, e.g.,
4.99 instead of 5.00.)

@p function horiz_rule_pixels(x:integer):integer;
  {computes $\lceil|horiz_conv|\cdot x\rceil$}
var n:integer;
begin n:=trunc(horiz_conv*x);
if n<horiz_conv*x then horiz_rule_pixels:=n+1 @+ else horiz_rule_pixels:=n;
end;

function vert_rule_pixels(x:integer):integer;
  {computes $\lceil|vert_conv|\cdot x\rceil$}
var n:integer;
begin n:=trunc(vert_conv*x);
if n<vert_conv*x then vert_rule_pixels:=n+1 @+ else vert_rule_pixels:=n;
end;

@ Strictly speaking, the |do_page| procedure is really a function with
side effects, not a `\&{procedure}'; it returns the value |false| if
\.{DVIDOC} should be aborted because of some unusual happening. The
subroutine is organized as a typical interpreter, with a multiway branch
on the command code followed by |goto| statements leading to routines that
finish up the activities common to different commands. We will use the
following labels:

@d fin_set=41 {label for commands that set or put a character}
@d fin_rule=42 {label for commands that set or put a rule}
@d move_right=43 {label for commands that change |h|}
@d move_down=44 {label for commands that change |v|}
@d show_state=45 {label for commands that change |s|}
@d change_font=46 {label for commands that change |cur_font|}

@ Some \PASCAL\ compilers severely restrict the length of procedure bodies,
so we shall split |do_page| into two parts, one of which is
called |special_cases|. The different parts communicate with each other
via the global variables mentioned above, together with the following ones:

@<Glob...@>=
@!s:integer; {current stack size}
@!ss:integer; {stack size to print}
@!cur_font:integer; {current internal font number}

@ Here is the overall setup.

@p @<Declare the function called |special_cases|@>@;
function do_page:boolean;
label fin_set,fin_rule,move_right,show_state,done,9998,9999;
var o:eight_bits; {operation code of the current command}
@!p,@!q:integer; {parameters of the current command}
@!a:integer; {byte number of the current command}
i,j:integer; {for loop indices for setting rules}
begin empty_page; cur_font:=nf; {set current font undefined}
s:=0; h:=0; v:=0; w:=0; x:=0; y:=0; z:=0; hh:=0; vv:=0;
  {initialize the state variables}
while true do @<Translate the next command in the \.{DVI} file;
    |goto 9999| with |do_page=true| if it was |eop|;
    |goto 9998| if premature termination is needed@>;
9998: write_ln(term_out,'!'); do_page:=false;
9999: end;

@ 
@d error(#)==write(term_out,' ',#)

@<Translate the next command...@>=
begin a:=cur_loc; 
o:=get_byte; p:=first_par(o);
if eof(dvi_file) then abort('the file ended prematurely!');
@.the file ended prematurely@>
@<Start translation of command |o| and |goto| the appropriate label to
  finish the job@>;
fin_set: @<Finish a command that either sets or puts a character, then
    |goto move_right| or |done|@>;
fin_rule: @<Finish a command that either sets or puts a rule, then
    |goto move_right| or |done|@>;
move_right: @<Finish a command that sets |h:=h+q|, then |goto done|@>;
show_state: ;
done: ;
end

@ The multiway switch in |first_par|, above, was organized by the length
of each command; the one in |do_page| is organized by the semantics.

@<Start translation...@>=
if o<set_char_0+128 then @<Translate a |set_char| command@>
else case o of
  four_cases(set1): begin out_char(p,hh,vv); goto fin_set;
    end;
  set_rule: begin goto fin_rule;
    end;
  put_rule: begin goto fin_rule;
    end;
  @t\4@>@<Cases for commands |nop|, |bop|, $\ldotss$, |pop|@>@;
  @t\4@>@<Cases for horizontal motion@>@;
  othercases if special_cases(o,p,a) then goto done@+else goto 9998
  endcases

@ @<Declare the function called |special_cases|@>=
function special_cases(@!o:eight_bits;@!p,@!a:integer):boolean;
label change_font,move_down,done,9998;
var q:integer; {parameter of the current command}
@!k:integer; {loop index}
@!bad_char:boolean; {has a non-ascii character code appeared in this \\{xxx}?}
@!pure:boolean; {is the command error-free?}
begin pure:=true;
case o of
four_cases(put1): begin goto done;
  end;
@t\4@>@<Cases for vertical motion@>@;
@t\4@>@<Cases for fonts@>@;
four_cases(xxx1): @<Translate an |xxx| command and |goto done|@>;
pre: begin error('preamble command within a page!'); goto 9998;
  end;
@.preamble command within a page@>
post,post_post: begin error('postamble command within a page!'); goto 9998;
@.postamble command within a page@>
  end;
othercases begin error('undefined command ',o:0,'!');
  goto done;
@.undefined command@>
  end
endcases;
move_down: @<Finish a command that sets |v:=v+p|, then |goto done|@>;
change_font: @<Finish a command that changes the current font,
  then |goto done|@>;
9998: pure:=false;
done: special_cases:=pure;
end;

@ @<Cases for commands |nop|, |bop|, $\ldotss$, |pop|@>=
nop: begin goto done;
  end;
bop: begin error('bop occurred before eop'); goto 9998;
@.bop occurred before eop@>
  end;
eop: begin 
  if s<>0 then error('stack not empty at end of page (level ',
    s:0,')!');
@.stack not empty...@>
  do_page:=true; flush_page; goto 9999;
  end;
push: begin 
  if s=max_s_so_far then
    begin max_s_so_far:=s+1;
    if s=max_s then error('deeper than claimed in postamble!');
@.deeper than claimed...@>
@.push deeper than claimed...@>
    if s=stack_size then
      begin error('DVIDOC capacity exceeded (stack size=',
        stack_size:0,')'); goto 9998;
      end;
    end;
  hstack[s]:=h; vstack[s]:=v; wstack[s]:=w;
  xstack[s]:=x; ystack[s]:=y; zstack[s]:=z;
  hhstack[s]:=hh; vvstack[s]:=vv; incr(s); ss:=s-1; goto show_state;
  end;
pop: begin 
  if s=0 then error('Pop illegal at level zero!')
  else  begin decr(s); hh:=hhstack[s]; vv:=vvstack[s];
    h:=hstack[s]; v:=vstack[s]; w:=wstack[s];
    x:=xstack[s]; y:=ystack[s]; z:=zstack[s];
    end;
  ss:=s; goto show_state;
  end;

@ Rounding to the nearest pixel is best done in the manner shown here, so as
to be inoffensive to the eye: When the horizontal motion is small, like a
kern, |hh| changes by rounding the kern; but when the motion is large, |hh|
changes by rounding the true position |h| so that accumulated rounding errors
disappear.


@d out_space==if abs(p)>=font_space[cur_font] then
    begin hh:=horiz_pixel_round(h+p);
    end
  else hh:=hh+horiz_pixel_round(p);
  q:=p; goto move_right

@<Cases for horizontal motion@>=
four_cases(right1):begin out_space;
  end;
w0,four_cases(w1):begin w:=p; out_space;
  end;
x0,four_cases(x1):begin x:=p; out_space;
  end;

@ Vertical motion is done similarly, but with the threshold between
``small'' and ``large'' increased by a factor of five. The idea is to make
fractions like ``$1\over2$'' round consistently, but to absorb accumulated
rounding errors in the baseline-skip moves.

@d out_vmove==if abs(p)>=5*font_space[cur_font] then vv:=vert_pixel_round(v+p)
  else vv:=vv+vert_pixel_round(p);
  goto move_down

@<Cases for vertical motion@>=
four_cases(down1):begin out_vmove;
  end;
y0,four_cases(y1):begin y:=p; out_vmove;
  end;
z0,four_cases(z1):begin z:=p; out_vmove;
  end;

@ @<Cases for fonts@>=
sixty_four_cases(fnt_num_0): begin 
  goto change_font;
  end;
four_cases(fnt1): begin 
  goto change_font;
  end;
four_cases(fnt_def1): begin 
  define_font(p); goto done;
  end;

@ @<Translate an |xxx| command and |goto done|@>=
begin write(term_out,'xxx'''); bad_char:=false;
for k:=1 to p do
  begin 
    q:=get_byte;
    if 
      (q>="!")and(q<="~") then write(term_out,xchr[q])
      else bad_char:=true
  end;
write(term_out,'''');
if bad_char then error('non-ascii character in xxx command!');
@.non-ascii character...@>
goto done;
end

@ @<Translate a |set_char|...@>=
begin 
      out_char(p,hh,vv)  
end

@ @<Finish a command that either sets or puts a character...@>=
if font_ec[cur_font]=256 then p:=256; {width computation for oriental fonts}
if (p<font_bc[cur_font])or(p>font_ec[cur_font]) then q:=invalid_width
else q:=char_width(cur_font)(p);
if q=invalid_width then
  begin error('character ',p:0,' invalid in font ');
@.character $c$ invalid...@>
  print_font(cur_font);
  if cur_font<>nf then write(term_out,'!');
  end;
if o>=put1 then goto done;
if q=invalid_width then q:=0
else hh:=hh+char_pixel_width(cur_font)(p);
goto move_right

@ @<Finish a command that either sets or puts a rule...@>=
q:=signed_quad;
if (p>0) and (q>0) then
  for i:=hh to hh+horiz_rule_pixels(q)-1 do
    for j:=vv downto vv-vert_rule_pixels(p)+1 do
      out_char(xord['-'],i,j);
if o=put_rule then goto done;
hh:=hh+horiz_rule_pixels(q); goto move_right

@ Since \.{DVIDOC} is intended to diagnose strange errors, it checks
carefully to make sure that |h| and |v| do not get out of range.
Normal \.{DVI}-reading programs need not do this.

@d infinity==@'17777777777 {$\infty$ (approximately)}

@<Finish a command that sets |h:=h+q|, then |goto done|@>=
if (h>0)and(q>0) then if h>infinity-q then
  begin error('arithmetic overflow! parameter changed from ',
@.arithmetic overflow...@>
    q:0,' to ',infinity-h:0);
  q:=infinity-h;
  end;
if (h<0)and(q<0) then if -h>q+infinity then
  begin error('arithmetic overflow! parameter changed from ',
    q:0, ' to ',(-h)-infinity:0);
  q:=(-h)-infinity;
  end;
h:=h+q;
if abs(h)>max_h_so_far then
  begin max_h_so_far:=abs(h);
  if abs(h)>max_h then error('warning: |h|>',max_h_so_far:0,'!');
@.warning: |h|...@>
  end;
goto done

@ @<Finish a command that sets |v:=v+p|, then |goto done|@>=
if (v>0)and(p>0) then if v>infinity-p then
  begin error('arithmetic overflow! parameter changed from ',
@.arithmetic overflow...@>
    p:0,' to ',infinity-v:0);
  p:=infinity-v;
  end;
if (v<0)and(p<0) then if -v>p+infinity then
  begin error('arithmetic overflow! parameter changed from ',
    p:0, ' to ',(-v)-infinity:0);
  p:=(-v)-infinity;
  end;
v:=v+p;
if abs(v)>max_v_so_far then
  begin max_v_so_far:=abs(v);
  if abs(v)>max_v then error('warning: |v|>',max_v_so_far:0,'!');
@.warning: |v|...@>
  end;
goto done

@ @<Finish a command that changes the current font...@>=
font_num[nf]:=p; cur_font:=0;
while font_num[cur_font]<>p do incr(cur_font);
goto done

@* Skipping pages.
@ 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.

@<Glob...@>=
@!old_backpointer:integer; {the previous |bop| command location}
@!new_backpointer:integer; {the current |bop| command location}
@!started:boolean; {has the starting page been found?}

@ @<Set init...@>=
old_backpointer:=-1; started:=false;

@ @<Pass a |bop| command, setting up the |count| array@>=
new_backpointer:=cur_loc-1; incr(page_count);
for k:=0 to 9 do count[k]:=signed_quad;
if signed_quad<>old_backpointer
  then write_ln(term_out,'backpointer in byte ',cur_loc-4:0,
    ' should be ',old_backpointer:0,'!');
@.backpointer...should be p@>
old_backpointer:=new_backpointer

@* Using the backpointers.
First comes a routine that illustrates how to find the postamble quickly.

@<Find the postamble, working back from the end@>=
n:=dvi_length;
if n<57 then bad_dvi('only ',n:0,' bytes long');
@.only n bytes long@>
m:=n-4;
repeat if m=0 then bad_dvi('all 223s');
@.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:0);
@.ID byte is wrong@>
move_to_byte(m-3); q:=signed_quad;
if (q<0)or(q>m-36) then bad_dvi('post pointer ',q:0,' at byte ',m-3:0);
@.post pointer is wrong@>
move_to_byte(q); k:=get_byte;
if k<>post then bad_dvi('byte ',q:0,' is not post');
@.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.

@<Glob...@>=
@!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.

@<Count the pages and move to the starting page@>=
q:=post_loc; p:=first_backpointer; start_loc:=-1;
if p>=0 then
  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:0,' after byte ',q:0);
@.page link wrong...@>
  q:=p; move_to_byte(q); k:=get_byte;
  if k=bop then incr(page_count)
  else bad_dvi('byte ',q:0,' is not bop');
@.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!');
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

@* 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 \.{DVIDOC} 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 
move_to_byte(cur_loc+12); {skip over numerator, denominator, and magnification}
max_v:=signed_quad; max_h:=signed_quad;@/
max_s:=get_two_bytes; total_pages:=get_two_bytes;@/
@<Process the font definitions of the postamble@>;
@<Make sure that the end of the file is well-formed@>;
end;

@ When we get to the present code, the |post_post| command has
just been read.

@<Make sure that the end of the file is well-formed@>=
q:=signed_quad;
m:=get_byte;
k:=cur_loc; m:=223;
while (m=223)and not eof(dvi_file) do m:=get_byte;
if not eof(dvi_file) then abort('signature in byte ',cur_loc-1:0,
@.signature...should be...@>
    ' should be 223!')
else if cur_loc<k+4 then
  write_ln(term_out,'not enough signature bytes at end of file (',
@.not enough signature bytes...@>
    cur_loc-k:0,')');

@ @<Process the font definitions...@>=
repeat k:=get_byte;
if (k>=fnt_def1)and(k<fnt_def1+4) then
  begin p:=first_par(k); define_font(p); write_ln(term_out,' '); k:=nop;
  end;
until k<>nop

@* The main program.
Now we are ready to put it all together. This is where \.{DVIDOC} starts,
and where it ends.

@p begin initialize; {get all variables initialized}
dialog; {set up all the options}
@<Process the preamble@>;
@<Find the postamble, working back from the end@>;
in_postamble:=true; read_postamble; in_postamble:=false;
@<Count the pages and move to the starting page@>;
if not in_postamble then @<Translate up to |max_pages| pages@>;
final_end:end.

@ The main program needs a few global variables in order to do its work.

@<Glob...@>=
@!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 \.{DVIDOC} looks at the preamble in order to do error
checking, and to display the introductory comment.

@<Process the preamble@>=
open_dvi_file;
p:=get_byte; {fetch the first byte}
if p<>pre then bad_dvi('First byte isn''t start of preamble!');
@.First byte isn't...@>
p:=get_byte; {fetch the identification byte}
@<Compute the conversion factor@>;
p:=get_byte; {fetch the length of the introductory comment}
write(term_out,'''');
while p>0 do
  begin decr(p); write(term_out,xchr[get_byte]);
  end;
write_ln(term_out,'''')

@ The conversion factors |horiz_conv| and 
|vert_conv| are figured as follows: There are exactly
|n/d| \.{DVI} units per decimicron, and 254000 decimicrons per inch,
and |horiz_resolution| or |vert_resolution| characters per inch. Then we have to adjust this
by the stated amount of magnification.

@<Compute the conversion factor@>=
numerator:=signed_quad; denominator:=signed_quad;
if numerator<=0 then bad_dvi('numerator is ',numerator:0);
@.numerator is wrong@>
if denominator<=0 then bad_dvi('denominator is ',denominator:0);
@.denominator is wrong@>
horiz_conv:=(numerator/254000.0)*(horiz_resolution/denominator);
vert_conv:=(numerator/254000.0)*(vert_resolution/denominator);
mag:=signed_quad;
if new_mag>0 then mag:=new_mag
else if mag<=0 then bad_dvi('magnification is ',mag:0);
@.magnification is wrong@>
true_horiz_conv:=horiz_conv; horiz_conv:=true_horiz_conv*(mag/1000.0);
true_vert_conv:=vert_conv; vert_conv:=true_vert_conv*(mag/1000.0);

@ 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. Such numbers can, for example,
be displayed on the console of a printer when it is working on that
page.

@<Translate up to...@>=
begin while max_pages>0 do
  begin decr(max_pages);
  write_ln(term_out); write(term_out,'Beginning of page ');
  for k:=0 to start_vals do
    begin write(term_out,count[k]:0);
    if k<start_vals then write(term_out,'.')
    else write_ln(term_out);
    end;
  if not do_page then abort('page ended unexpectedly!');
@.page ended unexpectedly@>
  repeat k:=get_byte;
  if (k>=fnt_def1)and(k<fnt_def1+4) then
    begin p:=first_par(k); define_font(p); k:=nop;
    end;
  until k<>nop;
  if k=post then
    begin in_postamble:=true; goto done;
    end;
  if k<>bop then bad_dvi('byte ',cur_loc-1:0,' is not bop');
@.byte n is not bop@>
  @<Pass a |bop|...@>;
  end;
done:end

@* System-dependent changes.
This module should be replaced, if necessary, by changes to the program
that are necessary to make \.{DVIDOC} work at a particular installation.
It is usually best to design your change file so that all changes to
previous modules preserve the module numbering; then everybody's version
will be consistent with the printed program. More extensive changes,
which introduce new modules, can be inserted here; then only the index
itself will get a new module number.
@^system dependencies@>

@* Index.
Pointers to error messages appear here together with the section numbers
where each ident\-i\-fier is used.
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0

-- 
----------------------------------------------------------------------
Computers will never replace the			Clayton Elwell
wastebasket when it comes to			Elwell@Ohio-State.ARPA
streamlining office work.		   ...!cbosgd!osu-eddie!elwell
----------------------------------------------------------------------