[mod.sources] v06i015: TeX DVI driver for LaserJet+

sources-request@mirror.UUCP (06/22/86)

Submitted by: Tor Lillqvist <talcott!seismo!mcvax!santra!tml>
Mod.sources: Volume 6, Issue 15
Archive-name: texdvi2lj/Part3

[ I have split this submission into three pieces named LJ.1.3,
  LJ.2.3, and LJ.2.3.  Unpack each piece and do
	  "cat LJ.[123].3 >dvi2lj.web"
  This is not great, but is the best way I could think of to
  distribute a large single-file source; if you have a better
  way, please let me know.  --r$]

Here is a DVI (TeX output) driver for the HP LaserJet+.  This version
is for the Pascal/1000 compiler on HP1000 machines running RTE-A (and
my TeX implementation), but it should be fairly easy to convert to
other TeX implementations, compilers and operating systems.

DVIplus is based on the DVItype program. It downloads only those
characters actually used. Pages are printed in reverse.

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
# Contents:  LJ.3.3
 
echo x - LJ.3.3
sed 's/^XX//' > "LJ.3.3" <<'@//E*O*F LJ.3.3//'
XX@* Interpreting the {\tentex DVI} file.
XXThe main work of \.{DVIplus} is accomplished by the |do_page| procedure,
XXwhich produces the output for an entire page, assuming that the |bop|
XXcommand for that page has already been processed. This procedure is
XXessentially an interpretive routine that reads and acts on the \.{DVI}
XXcommands.
XX  
XX@ The definition of \.{DVI} files refers to six registers,
XX$(h,v,w,x,y,z)$, which hold integer values in \.{DVI} units.  In practice,
XXwe also need registers |hh| and |vv|, the pixel analogs of $h$ and $v$,
XXsince it is not always true that |hh=pixel_round(h)| or
XX|vv=pixel_round(v)|.
XXThe |lj_h| and |lj_v| registers hold the current actual cursor position
XXof the printer.
XX  
XXThe stack of $(h,v,w,x,y,z)$ values is represented by eight arrays
XXcalled |hstack|, \dots, |zstack|, |hhstack|, and |vvstack|.
XX  
XX@<Glob...@>=
XX@!h,@!v,@!w,@!x,@!y,@!z,@!hh,@!vv:integer; {current state values}
XX@!lj_h,@!lj_v:integer; {current cursor position}
XX@!hstack,@!vstack,@!wstack,@!xstack,@!ystack,@!zstack:
XX  array [0..stack_size] of integer; {pushed down values in \.{DVI} units}
XX@!hhstack,@!vvstack:
XX  array [0..stack_size] of integer; {pushed down values in pixels}
XX  
XX@ Three characteristics of the pages (their |max_v|, |max_h|, and
XX|max_s|) are specified in the postamble, and a warning message
XXis printed if these limits are exceeded. Actually |max_v| is set to
XXthe maximum height plus depth of a page, and |max_h| to the maximum width,
XXfor purposes of page layout. Since characters can legally be set outside
XXof the page boundaries, it is not an error when |max_v| or |max_h| is
XXexceeded. But |max_s| should not be exceeded.

XXThe postamble also specifies the total number of pages; \.{DVIplus}
XXchecks to see if this total is accurate.

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

XX@ @<Set init...@>=
XXmax_v:=@'17777777777-99; max_h:=@'17777777777-99; max_s:=stack_size+1;@/
XXmax_v_so_far:=0; max_h_so_far:=0; max_s_so_far:=0; page_count:=0;

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

XX@d four_cases(#)==#,#+1,#+2,#+3
XX@d eight_cases(#)==four_cases(#),four_cases(#+4)
XX@d sixteen_cases(#)==eight_cases(#),eight_cases(#+8)
XX@d thirty_two_cases(#)==sixteen_cases(#),sixteen_cases(#+16)
XX@d sixty_four_cases(#)==thirty_two_cases(#),thirty_two_cases(#+32)

XX@p function first_par(o:eight_bits):integer;
XXbegin case o of
XXsixty_four_cases(set_char_0),sixty_four_cases(set_char_0+64):
XX  first_par:=o-set_char_0;
XXset1,put1,fnt1,xxx1,fnt_def1: first_par:=get_byte;
XXset1+1,put1+1,fnt1+1,xxx1+1,fnt_def1+1: first_par:=get_two_bytes;
XXset1+2,put1+2,fnt1+2,xxx1+2,fnt_def1+2: first_par:=get_three_bytes;
XXright1,w1,x1,down1,y1,z1: first_par:=signed_byte;
XXright1+1,w1+1,x1+1,down1+1,y1+1,z1+1: first_par:=signed_pair;
XXright1+2,w1+2,x1+2,down1+2,y1+2,z1+2: first_par:=signed_trio;
XXset1+3,set_rule,put1+3,put_rule,right1+3,w1+3,x1+3,down1+3,y1+3,z1+3,
XX  fnt1+3,xxx1+3,fnt_def1+3: first_par:=signed_quad;
XXnop,bop,eop,push,pop,pre,post,post_post,undefined_commands: first_par:=0;
XXw0: first_par:=w;
XXx0: first_par:=x;
XXy0: first_par:=y;
XXz0: first_par:=z;
XXsixty_four_cases(fnt_num_0): first_par:=o-fnt_num_0;
XXend;
XXend;

XX@ Here is another subroutine that we need: It computes the number of
XXpixels in the height or width of a rule. Characters and rules will line up
XXproperly if the sizes are computed precisely as specified here.  (Since
XX|conv| is computed with some floating-point roundoff error, in a
XXmachine-dependent way, format designers who are tailoring something for a
XXparticular resolution should not plan their measurements to come out to an
XXexact integer number of pixels; they should compute things so that the
XXrule dimensions are a little less than an integer number of pixels, e.g.,
XX4.99 instead of 5.00.)

XX@p function rule_pixels(x:integer):integer;
XX  {computes $\lceil|conv|\cdot x\rceil$}
XXvar n:integer;
XXbegin n:=trunc(conv*x);
XXif n<conv*x then rule_pixels:=n+1 @+ else rule_pixels:=n;
XXend;

XX@ Strictly speaking, the |do_page| procedure is really a function with
XXside effects, not a `\&{procedure}'; it returns the value |false| if
XX\.{DVIplus} should be aborted because of some unusual happening. The
XXsubroutine is organized as a typical interpreter, with a multiway branch
XXon the command code followed by |goto| statements leading to routines that
XXfinish up the activities common to different commands. We will use the
XXfollowing labels:
XX 
XX@d fin_set=41 {label for commands that set or put a character}
XX@d fin_rule=42 {label for commands that set or put a rule}
XX@d move_right=43 {label for commands that change |h|}
XX@d move_down=44 {label for commands that change |v|}
XX@d change_font=46 {label for commands that change |cur_font|}
XX  
XX@ Some \PASCAL\ compilers severely restrict the length of procedure bodies,
XXso we shall split |do_page| into two parts, one of which is
XXcalled |special_cases|. The different parts communicate with each other
XXvia the global variables mentioned above, together with the following ones:
XX  
XX@<Glob...@>=
XX@!s:integer; {current stack size}
XX@!ss:integer; {stack size to print}
XX@!cur_font:integer; {current internal font number}
XX@!prev_font:integer;
XX@!fonts_in_use:integer; {how many are currently loaded}
XX@!fonts_on_page:integer; {how many fonts used on current page}
XX  
XX@ @<Set init...@>=
XXfonts_in_use:=0;
XX  
XX@ Here is the overall setup.
XX  
XX@p @t\4@>@<Declare the function called |special_cases|@>@;
XXprocedure do_page;
XXlabel fin_set,fin_rule,move_right,done,exit;
XXvar o:eight_bits; {operation code of the current command}
XX@!p,@!q:integer; {parameters of the current command}
XXpp,qq:integer;
XXi,j,k:integer;
XX@!a:integer; {byte number of the current command}
XX@!hhh:integer; {|h|, rounded to the nearest pixel}
XX@!height,pitch:i2c;
XXbegin cur_font:=nf; {set current font undefined}
XXprev_font:=-1;
XXs:=0; h:=round(h_offset/conv); v:=round(v_offset/conv);
XXw:=0; x:=0; y:=0; z:=0; hh:=pixel_round(h); vv:=pixel_round(v);
XXlj_h:=-10000; lj_v:=-10000;
XX  {initialize the state variables}
XXwhile true do @<Translate the next command in the \.{DVI} file@>;
XXexit:
XXend;
XX  
XX@
XX  
XX@d error(#)==print_ln('% ',#)
XX  
XX@<Translate the next command...@>=
XXbegin a:=cur_loc;
XXo:=get_byte; p:=first_par(o);
XXif eof_dvi_file then bad_dvi('file ended prematurely');
XX@.the file ended prematurely@>
XX@<Start translation of command |o| and |goto| the appropriate label to
XX  finish the job@>;
XXfin_set: @<Finish a command that either sets or puts a character, then
XX    |goto move_right| or |done|@>;
XXfin_rule: @<Finish a command that either sets or puts a rule, then
XX    |goto move_right| or |done|@>;
XXmove_right: @<Finish a command that sets |h:=h+q|, then |goto done|@>;
XXdone:
XXend
XX  
XX@ The multiway switch in |first_par|, above, was organized by the length
XXof each command; the one in |do_page| is organized by the semantics.
XX  
XX@<Start translation...@>=
XXif o<set_char_0+128 then @<Translate a |set_char| command@>
XXelse case o of
XX  four_cases(set1),
XX  four_cases(put1): bad_dvi('illegal character (', p:1, ')');
XX  set_rule,
XX  put_rule: goto fin_rule;
XX  @t\4@>@<Cases for commands |nop|, |bop|, \dots, |pop|@>@;
XX  @t\4@>@<Cases for horizontal motion@>@;
XX  othercases if special_cases(o,p,a) then goto done
XX             else bad_dvi(' ')
XX  endcases
XX  
XX@ @<Declare the function called |special_cases|@>=
XXfunction special_cases(@!o:eight_bits;@!p,@!a:integer):boolean;
XXlabel change_font,move_down,done,9998;
XXvar q:integer; {parameter of the current command}
XXq1,q2:integer; {dummies for |fnt_def|}
XX@!k:integer; {loop index}
XX@!bad_char:boolean; {has a non-ASCII character code appeared in this \\{xxx}?}
XX@!pure:boolean; {is the command error-free?}
XX@!vvv:integer; {|v|, rounded to the nearest pixel}
XXbegin pure:=true;
XXcase o of
XX@t\4@>@<Cases for vertical motion@>@;
XX@t\4@>@<Cases for fonts@>@;
XXfour_cases(xxx1): @<Translate an |xxx| command and |goto done|@>;
XXpre: begin error('preamble command within a page!'); goto 9998;
XX  end;
XX@.preamble command within a page@>
XXpost,post_post: begin error('postamble command within a page!'); goto 9998;
XX@.postamble command within a page@>
XX  end;
XXothercases begin error('undefined command ',o:1,'!');
XX  goto done;
XX@.undefined command@>
XX  end
XXendcases;
XXmove_down: @<Finish a command that sets |v:=v+p|, then |goto done|@>;
XXchange_font: @<Finish a command that changes the current font,
XX  then |goto done|@>;
XX9998: pure:=false;
XXdone: special_cases:=pure;
XXend;
XX  
XX@ @<Cases for commands |nop|, |bop|, \dots, |pop|@>=
XXnop: goto done;
XXbop: begin error('bop occurred before eop'); bad_dvi(' ');
XX@.bop occurred before eop@>
XX  end;
XXeop: begin
XX  write_lj(#12);
XX  if s<>0 then error('stack not empty at end of page (level ',
XX    s:1,')!');
XX@.stack not empty...@>
XX  return;
XX  end;
XXpush: begin
XX  if s=max_s_so_far then
XX    begin max_s_so_far:=s+1;
XX    if s=max_s then error('deeper than claimed in postamble!');
XX@.deeper than claimed...@>
XX@.push deeper than claimed...@>
XX    if s=stack_size then
XX      abort('DVIplus capacity exceeded (stack size=',
XX        stack_size:1,')');
XX    end;
XX  hstack[s]:=h; vstack[s]:=v; wstack[s]:=w;
XX  xstack[s]:=x; ystack[s]:=y; zstack[s]:=z;
XX  hhstack[s]:=hh; vvstack[s]:=vv; incr(s); ss:=s-1; goto done;
XX  end;
XXpop: begin
XX  if s=0 then error('(illegal at level zero)!')
XX  else  begin decr(s); hh:=hhstack[s]; vv:=vvstack[s];
XX    h:=hstack[s]; v:=vstack[s]; w:=wstack[s];
XX    x:=xstack[s]; y:=ystack[s]; z:=zstack[s];
XX    end;
XX  ss:=s; goto done;
XX  end;
XX  
XX@ Rounding to the nearest pixel is best done in the manner shown here, so as
XXto be inoffensive to the eye: When the horizontal motion is small, like a
XXkern, |hh| changes by rounding the kern; but when the motion is large, |hh|
XXchanges by rounding the true position |h| so that accumulated rounding errors
XXdisappear. We allow a larger space in the negative direction than in
XXthe positive one, because \TeX\ makes comparatively
XXlarge backspaces when it positions accents.
XX  
XX@d out_space==begin
XX  if (p>=font_space[cur_font])or(p<=-4*font_space[cur_font]) then
XX    hh:=pixel_round(h+p)
XX  else hh:=hh+pixel_round(p);
XX  q:=p; goto move_right;
XXend
XX  
XX@<Cases for horizontal motion@>=
XXfour_cases(right1):out_space;
XXw0,four_cases(w1):begin w:=p; out_space;
XX  end;
XXx0,four_cases(x1):begin x:=p; out_space;
XX  end;
XX  
XX@ Vertical motion is done similarly, but with the threshold between
XX``small'' and ``large'' increased by a factor of five. The idea is to make
XXfractions like ``$1\over2$'' round consistently, but to absorb accumulated
XXrounding errors in the baseline-skip moves.
XX  
XX@d out_vmove==begin
XX  if abs(p)>=5*font_space[cur_font] then vv:=pixel_round(v+p)
XX  else vv:=vv+pixel_round(p);
XX  goto move_down;
XXend
XX  
XX@<Cases for vertical motion@>=
XXfour_cases(down1):out_vmove;
XXy0,four_cases(y1):begin y:=p; out_vmove;
XX  end;
XXz0,four_cases(z1):begin z:=p; out_vmove;
XX  end;
XX  
XX@ @<Cases for fonts@>=
XXsixty_four_cases(fnt_num_0),
XXfour_cases(fnt1):
XX  goto change_font;
XXfour_cases(fnt_def1): begin
XX  @<Skip a |fnt_def| command@>;
XX  goto done;
XX  end;
XX  
XX@ @<Skip a |fnt_def| command@>=
XXq:=signed_quad; q:=signed_quad; q:=signed_quad;
XXq1:=get_byte; q2:=get_byte;
XXfor k:=1 to q1+q2 do q:=get_byte;
XX  
XX@ @<Translate an |xxx| command and |goto done|@>=
XXbegin bad_char:=false;
XXfor k:=1 to p do
XX  begin q:=get_byte;
XX  if not ((q>=" ")and(q<="~")) then
XX    bad_char:=true;
XX  end;
XXif bad_char then error('non-ASCII character in xxx command!');
XX@.non-ASCII character...@>
XXgoto done;
XXend
XX  
XX@ @<Translate a |set_char|...@>=
XXgoto fin_set
XX  
XX@ This is the code that checks whether the next character to be
XXprinted in |cur_font| has occurred previously.
XXIf not, the character data is downloaded to the printer. If the character
XXis too large, the data is sent as a raster image.
XXIf |cur_font| is also new, the font header is first downloaded.
XXThe number of fonts stored in the printer is kept at |max_printer_fonts|
XXmaximum. When more fonts are needed, the least recently used one is
XXdeleted.
XX  
XXThe value of |max_printer_fonts| was choosed by waving a magig rod;
XXit would be more appropriate to calculate exactly how many bytes of
XXthe user-available memory in the LaserJet+ is in use, and base the
XXdecisions whether we must delete some fonts on that.
XX(Why can't you ask the printer how much memory it has left?).
XX  
XXThere should also be a test whether the number of fonts on a page
XXexceeds the maximum 16.
XX  
XX@<Finish a command that either sets or puts a character...@>=
XXif p<0 then p:=255-((-1-p) mod 256)
XXelse if p>=256 then p:=p mod 256; {width computation for oriental fonts}
XX@^oriental characters@>@^Chinese characters@>@^Japanese characters@>
XXif (p>127) then q:=invalid_width
XXelse q:=char_width(cur_font)(p);
XXif q=invalid_width then
XX  begin error('character ',p:1,' invalid in font ');
XX@.character $c$ invalid...@>
XX  print_font(cur_font);
XX  if cur_font<>nf then print('!'); {font |nf| has `\.!' in its name}
XX  end;
XXif font_used_on[cur_font]=0 then begin
XX  if fonts_in_use=max_printer_fonts then
XX    @<Delete least recently used font@>;
XX  incr(fonts_on_page);
XX  @<Download font header@> end
XXelse if font_used_on[cur_font]<page_count then
XX  incr(fonts_on_page);
XXif fonts_on_page>max_fonts_on_page then
XX  error('too many fonts on this page');
XXfont_used_on[cur_font]:=page_count;
XXif cur_font <> prev_font then
XX  write_lj(@=#27'('@>,font_num[cur_font]:1,'X');
XXprev_font:=cur_font;
XXif char_status(cur_font)(p) = too_large then
XX  raster_char(p)
XXelse begin
XX  if char_status(cur_font)(p) = not_loaded then
XX    download_char(p);
XX  if char_status(cur_font)(p) > 0 then begin
XX    vv:=vv+char_status(cur_font)(p);
XX    update_pos;
XX    write_lj(vis_chr(p));
XX    vv:=vv-char_status(cur_font)(p); end
XX  else begin
XX    update_pos;
XX    write_lj(vis_chr(p));
XX  end;
XX  lj_h:=lj_h+char_pixel_width(cur_font)(p);
XXend;
XXif o>=put1 then begin
XX  hh:=hh-char_pixel_width(cur_font)(p);
XX  goto done;
XXend;
XXif q=invalid_width then q:=0
XXelse hh:=hh+char_pixel_width(cur_font)(p);
XXgoto move_right
XX 
XX@ @<Download font header@>=
XXbegin
XX  height.i0:=4*round(font_design_size[cur_font]*
XX                     font_mag[cur_font]/1000.0*conv);
XX  if (height.i0 < 0) or (height.i0>10922) then
XX    height.i0:= 10922;
XX  pitch.i0:=height.i0-20; {is this value used for anything in the printer??}
XX  
XX  write_lj(@=#27'*c'@>,font_num[cur_font]:1,
XX  {char cell 255*255 pixels max}
XX  {baseline distance set to $255-$|baseline| }
XX    @='D'#27')s26W'#0#26#0#1#0#0#0@>, chr(255-baseline),
XX    @=#0#255#0#255#0#1#1#21@>,
XX    pitch.c[0], pitch.c[1], height.c[0], height.c[1],
XX    @=#0#0#0#0#0#0#27'*c4F'@>);
XX  incr(fonts_in_use);
XXend
XX  
XX@ @<Delete least recently...@>=
XXbegin
XX  j:=9999;
XX  k:=nf;
XX  for i:=0 to nf-1 do
XX    if font_used_on[i] < j then begin
XX      j:=font_used_on[i];
XX      k:=i;
XX    end;
XX  write_lj(@=#27'*c'@>,font_num[k]:1,'d2F');
XX  font_used_on[k]:=0;
XX  for i:=0 to 127 do
XX    if (char_status(k)(i) > 0) or (char_status(k)(i) = loaded_ok) then
XX      char_status(k)(i):=not_loaded;
XX  decr(fonts_in_use);
XXend
XX  
XX@ @<Finish a command that either sets or puts a rule...@>=
XXq:=signed_quad;
XXqq:=rule_pixels(q);
XXpp:=rule_pixels(p);
XXif (p>0) and (q>0) then begin
XX  vv:=vv-pp;
XX  update_pos;
XX  write_lj(@=#27'*c'@>,qq:1,'a',pp:1,'b0P');
XX  vv:=vv+pp;
XXend;
XXif o=put_rule then goto done;
XXhh:=hh+qq;
XXgoto move_right
XX  
XX@ A sequence of consecutive rules, or consecutive characters in a fixed-width
XXfont whose width is not an integer number of pixels, can cause |hh| to drift
XXfar away from a correctly rounded value. \.{DVIplus} ensures that the
XXamount of drift will never exceed |max_drift| pixels.
XX  
XXSince \.{DVIplus} is intended to diagnose strange errors, it checks
XXcarefully to make sure that |h| and |v| do not get out of range.
XXNormal \.{DVI}-reading programs need not do this.
XX  
XX@d infinity==@'17777777777 {$\infty$ (approximately)}
XX@d max_drift=2 {we insist that abs|(hh-pixel_round(h))<=max_drift|}
XX  
XX@<Finish a command that sets |h:=h+q|, then |goto done|@>=
XXif (h>0)and(q>0) then if h>infinity-q then
XX  begin error('arithmetic overflow! parameter changed from ',
XX@.arithmetic overflow...@>
XX    q:1,' to ',infinity-h:1);
XX  q:=infinity-h;
XX  end;
XXif (h<0)and(q<0) then if -h>q+infinity then
XX  begin error('arithmetic overflow! parameter changed from ',
XX    q:1, ' to ',(-h)-infinity:1);
XX  q:=(-h)-infinity;
XX  end;
XXhhh:=pixel_round(h+q);
XXif abs(hhh-hh)>max_drift then begin
XX  if hhh>hh then hh:=hhh-max_drift
XX  else hh:=hhh+max_drift;
XXend;
XXh:=h+q;
XXif abs(h)>max_h_so_far then
XX  begin if abs(h)-round(h_offset/conv)>max_h+99 then
XX    begin error('warning: h > ',max_h:1,'!');
XX@.warning: |h|...@>
XX    max_h:=abs(h);
XX    end;
XX  max_h_so_far:=abs(h);
XX  end;
XXgoto done
XX  
XX@ @<Finish a command that sets |v:=v+p|, then |goto done|@>=
XXif (v>0)and(p>0) then if v>infinity-p then
XX  begin error('arithmetic overflow! parameter changed from ',
XX@.arithmetic overflow...@>
XX    p:1,' to ',infinity-v:1);
XX  p:=infinity-v;
XX  end;
XXif (v<0)and(p<0) then if -v>p+infinity then
XX  begin error('arithmetic overflow! parameter changed from ',
XX    p:1, ' to ',(-v)-infinity:1);
XX  p:=(-v)-infinity;
XX  end;
XXvvv:=pixel_round(v+p);
XXif abs(vvv-vv)>max_drift then begin
XX  if vvv>vv then vv:=vvv-max_drift
XX  else vv:=vvv+max_drift;
XXend;
XXv:=v+p;
XXif abs(v)>max_v_so_far then
XX  begin if abs(v)-round(v_offset/conv)>max_v+99 then
XX    begin error('warning: v > ',max_v:1,'!');
XX@.warning: |v|...@>
XX    max_v:=abs(v);
XX    end;
XX  max_v_so_far:=abs(v);
XX  end;
XXgoto done
XX  
XX@ @<Finish a command that changes the current font...@>=
XXbegin
XX  font_num[nf]:=p; cur_font:=0;
XX  while font_num[cur_font]<>p do incr(cur_font);
XX  goto done
XXend
XX  
XX@* Using the backpointers.
XXFirst comes a routine that illustrates how to find the postamble quickly.
XX  
XX@<Find the postamble, working back from the end@>=
XXn:=dvi_length;
XXif n<53 then bad_dvi('only ',n:1,' bytes long');
XX@.only n bytes long@>
XXm:=n-4;
XXrepeat if m=0 then bad_dvi('all 223s');
XX@.all 223s@>
XXmove_to_byte(m); k:=get_byte; decr(m);
XXuntil k<>223;
XXif k<>dvi_id then bad_dvi('ID byte is ',k:1);
XX@.ID byte is wrong@>
XXmove_to_byte(m-3); q:=signed_quad;
XXif (q<0)or(q>m-33) then bad_dvi('post pointer ',q:1,' at byte ',m-3:1);
XX@.post pointer is wrong@>
XXmove_to_byte(q); k:=get_byte;
XXif k<>post then bad_dvi('byte ',q:1,' is not post');
XX@.byte n is not post@>
XXpost_loc:=q; first_backpointer:=signed_quad
XX  
XX@ Note that the last steps of the above code save the locations of the
XXthe |post| byte and the final |bop|.  We had better declare these global
XXvariables, together with another one that we will need shortly.
XX  
XX@<Glob...@>=
XX@!post_loc:integer; {byte location where the postamble begins}
XX@!first_backpointer:integer; {the pointer following |post|}
XX@!start_loc:integer; {byte location of the first page to process}
XX@!start_inx:integer; {index into |page_start| for first page}
XX@!last_loc:integer; {byte localtion of last page to process}
XX@!page_start:array[1..max_bops] of integer; {pointers to |bop|s}
XX  
XX@ The next routines follow the backpointers
XXto move through a \.{DVI} file in reverse order. \.{DVIplus} does this because
XXit wants to print the pages backwards as the LaserJet stacks
XXthem with the printed side up.
XX 
XXFirst we search for the starting page and the last page.
XX  
XX@<Scan for page range to print@>=
XXq:=post_loc; p:=first_backpointer; start_loc:=-1;
XXrepeat
XX  {now |q| points to a |post| or |bop| command; |p>=0| is prev pointer}
XX  if p>q-46 then
XX    bad_dvi('page link ',p:1,' after byte ',q:1);
XX@.page link wrong...@>
XX  q:=p; move_to_byte(q);
XX  k:=get_byte;
XX  if k=bop then incr(page_count)
XX  else bad_dvi('byte ',q:1,' is not bop');
XX@.byte n is not bop@>
XX  if page_count>max_bops then bad_dvi('there are too many pages');
XX@.there are too many pages@>
XX  page_start[page_count]:=q;
XX  for k:=0 to 9 do count[k]:=signed_quad;
XX  if start_match then begin start_loc:=q; start_inx:=page_count; end;
XX  p:=signed_quad;
XXuntil p<0;
XXif start_loc<0 then abort('starting page number could not be found!');
XX@.starting page number...@>
XXif (start_inx > max_pages) then
XX  last_loc:=page_start[start_inx-max_pages+1]
XXelse
XX  last_loc:=page_start[1];
XXif page_count<>total_pages then
XX  print_ln('there are really ',page_count:1,
XX    ' pages, not ',total_pages:1,'!');
XX@.there are really n pages@>
XX 
XX@ This is the code that really goes through the pages to be printed
XX(in reverse order). It starts from the page pointed to by |last_loc| and
XXproceeds up to |start_loc|.
XX  
XXThe code shown here uses a convention that has proved to be useful:
XXIf the starting page was specified as, e.g., `\.{1.*.-5}', then
XXall page numbers in the file are displayed by showing the values of
XXcounts 0, 1, and~2, separated by dots. Such numbers can, for example,
XXbe displayed on the console of a printer when it is working on that
XXpage.
XX  
XX@<Translate the page range in reverse order@>=
XXpage_count:=0;
XXq:=last_loc;
XXrepeat
XX  move_to_byte(q); k:=get_byte;
XX  incr(page_count);
XX  fonts_on_page:=0;
XX  for k:=0 to 9 do count[k]:=signed_quad;
XX  q:=signed_quad;
XX  print('[');
XX  for k:=0 to start_vals do
XX    begin print(count[k]:1);
XX      if k<start_vals then print('.');
XX    end;
XX  update_terminal;
XX  do_page;
XX  print('] ');
XX  update_terminal;
XXuntil q<start_loc;
XX  
XX@* Reading the postamble.
XXNow imagine that we are reading the \.{DVI} file and positioned just
XXfour bytes after the |post| command. That, in fact, is the situation,
XXwhen the following part of \.{DVIplus} is called upon to read, translate,
XXand check the rest of the postamble.
XX  
XX@p procedure read_postamble;
XXvar k:integer; {loop index}
XX@!p,@!q,@!m:integer; {general purpose registers}
XXbegin post_loc:=cur_loc-5;
XX@.Postamble starts at byte n@>
XXif signed_quad<>numerator then
XX  print_ln('numerator doesn''t match the preamble!');
XX@.numerator doesn't match@>
XXif signed_quad<>denominator then
XX  print_ln('denominator doesn''t match the preamble!');
XX@.denominator doesn't match@>
XXif signed_quad<>mag then if new_mag=0 then
XX  print_ln('magnification doesn''t match the preamble!');
XX@.magnification doesn't match@>
XXmax_v:=signed_quad; max_h:=signed_quad;@/
XXmax_s:=get_two_bytes; total_pages:=get_two_bytes;@/
XXif total_pages>max_bops then
XX  bad_dvi('enormous number of pages (', total_pages:1, ')');
XX@.enormous number of pages@>
XX@<Process the font definitions of the postamble@>;
XX@<Make sure that the end of the file is well-formed@>;
XXend;
XX  
XX@ No warning is given when |max_h_so_far| exceeds |max_h| by less than~100,
XXsince 100 units is invisibly small; it's approximately the wavelength of
XXvisible light, in the case of \TeX\ output. Rounding errors can be expected
XXto make |h| and |v| slightly more than |max_h| and |max_v|, every once in
XXa~while; hence small discrepancies are not cause for alarm.
XX  
XX@ When we get to the present code, the |post_post| command has
XXjust been read.

XX@<Make sure that the end of the file is well-formed@>=
XXq:=signed_quad;
XXif q<>post_loc then
XX  print_ln('bad postamble pointer in byte ',cur_loc-4:1,'!');
XX@.bad postamble pointer@>
XXm:=get_byte;
XXif m<>dvi_id then print_ln('identification in byte ',cur_loc-1:1,
XX@.identification...should be n@>
XX    ' should be ',dvi_id:1,'!');
XXk:=cur_loc; m:=223;
XXwhile (m=223)and not eof_dvi_file do m:=get_byte;
XXif not eof_dvi_file then bad_dvi('signature in byte ',cur_loc-1:1,
XX@.signature...should be...@>
XX    ' should be 223')
XXelse if cur_loc<k+4 then
XX  print_ln('not enough signature bytes at end of file (',
XX@.not enough signature bytes...@>
XX    cur_loc-k:1,')');

XX@ @<Process the font definitions...@>=
XXrepeat k:=get_byte;
XXif (k>=fnt_def1)and(k<fnt_def1+4) then
XX  begin p:=first_par(k); define_font(p); print_nl; k:=nop;
XX  end;
XXuntil k<>nop;
XXif k<>post_post then
XX  print_ln('byte ',cur_loc-1:1,' is not postpost!')
XX@.byte n is not postpost@>
XX  
XX@* The main program.
XXNow we are ready to put it all together. This is where \.{DVIplus} starts,
XXand where it ends.
XX  
XX@p begin dont_catch_errors; initialize; {get all variables initialized}
XXdialog; {set up all the options}
XX@<Process the preamble@>;
XX@<Find the postamble, working back from the end@>;
XXread_postamble;
XXprint_ln('Total of ', nf:1, ' fonts.');
XX@<Scan for page range to print@>;
XXwrite_lj(@=#27'E'#27'&l'@>, copies:1, @='X'#27'*t300R'@>);
XX@<Translate the page range...@>;
XXwrite_lj(@=#27'&l1X'@>);
XXwrite_ln(laser_file,'_');
XXprint_nl
XXend.
XX  
XX@ The main program needs a few global variables in order to do its work.
XX  
XX@<Glob...@>=
XX@!k,@!m,@!n,@!p,@!q:integer; {general purpose registers}
XX  
XX@ A \.{DVI}-reading program that reads the postamble first need not look at the
XXpreamble; but \.{DVIplus} looks at the preamble in order to do error
XXchecking, and to display the introductory comment.
XX  
XX@<Process the preamble@>=
XXif not open_dvi_file then
XX  bad_dvi('cannot open file');
XX@<Open and lock |laser_file|@>;
XXp:=get_byte; {fetch the first byte}
XXif p<>pre then bad_dvi('first byte isn''t start of preamble!');
XX@.First byte isn't...@>
XXp:=get_byte; {fetch the identification byte}
XXif p<>dvi_id then
XX  print_ln('identification in byte 1 should be ',dvi_id:1,'!');
XX@.identification...should be n@>
XX@<Compute the conversion factor@>;
XXp:=get_byte; {fetch the length of the introductory comment}
XXprint('''');
XXwhile p>0 do
XX  begin decr(p); print(xchr[get_byte]);
XX  end;
XXprint_ln('''')

XX@ The conversion factor |conv| is figured as follows: There are exactly
XX|n/d| \.{DVI} units per decimicron, and 254000 decimicrons per inch,
XXand |resolution| pixels per inch. Then we have to adjust this
XXby the stated amount of magnification.
XX  
XX@<Compute the conversion factor@>=
XXnumerator:=signed_quad; denominator:=signed_quad;
XXif numerator<=0 then bad_dvi('numerator is ',numerator:1);
XX@.numerator is wrong@>
XXif denominator<=0 then bad_dvi('denominator is ',denominator:1);
XX@.denominator is wrong@>
XXconv:=(numerator/254000.0)*(resolution/denominator);
XXmag:=signed_quad;
XXif new_mag>0 then mag:=new_mag
XXelse if mag<=0 then bad_dvi('magnification is ',mag:1);
XX@.magnification is wrong@>
XXtrue_conv:=conv; conv:=true_conv*(mag/1000.0);
XX  
XX@* System-dependent changes.
XXThis section should be replaced, if necessary, by changes to the program
XXthat are necessary to make \.{DVIplus} work at a particular installation.
XXIt is usually best to design your change file so that all changes to
XXprevious sections preserve the section numbering; then everybody's version
XXwill be consistent with the printed program. More extensive changes,
XXwhich introduce new sections, can be inserted here; then only the index
XXitself will get a new section number.
XX@^system dependencies@>

XX@* Index.
XXPointers to error messages appear here together with the section numbers
XXwhere each ident\-i\-fier is used.
XX-------------------------------------- end of last part ----------------


@//E*O*F LJ.3.3//
chmod u=rw,g=rw,o=rw LJ.3.3
 
exit 0