% hpgf.ch -*-mode: change; webfile: crudetype.web version 3.01;-*- % HPGF.CH Provisional change file for the HP Laserjet... % NOTE the system change file must normally be inserted above this point. % SEE HPGF.DOC for users document--which must be rewritten for any new % system. % BUGFIX 19-dec-1990 Bug spoilt Landscape mode. % @x Module 41; Lines 822 -- 839 When this module starts, the \.{DVI} file should be positioned at or before a BOP. @= read_BOP; if (counter[0] >= first_page) then start := true ; if start and (count_pages > 0 ) then begin @ decr(count_pages); if not quiet then display('[', counter[0]:1 ); {Progress report} Read_one_page ; @ Send_page ; if not quiet then display( ']' ); end else if ( count_pages > 0) then Skip_page else time_to_stop := true; @y When this module starts, the \.{DVI} file should be positioned at or before a BOP. This is where the printer change file proper begins. This change file goes with \.{Crudetype} version 2. First, it should be explained that the HP is not at all a "crude" printer, and the mechanisms of \.{Crudetype} are not really suitable for it. It is really stretching the program a very long way from its intended purpose. In particular, some changes have to be spliced into the middle of the program, instead of going at the end as printer changes ought to. It seems that the only reasonable way to drive a HP is by downloading all the required characters. As stated in \.{Crudetype}, the problems of downloading are extremely difficult and I have not solved them in any satisfactory manner. The code given below manages downloading in the simplest and crudest way possible. First, I have added flags to print either even or odd pages only. In principle, this will allow double sided printing. Also, we do not sort the page as the HP can jump about. @= read_BOP; if (counter[0] >= first_page) then start := true ; if start and (count_pages > 0 ) and ( ( odd( counter[0]) = odds) or (( not evens) and ( not odds)) ) then begin decr(count_pages); if not quiet then display('[', counter[0]:1 ); {Progress report} Read_one_page ; @ Send_page ; @ if not quiet then display( ']' ); end else if ( count_pages > 0) then Skip_page else time_to_stop := true; @z % Next, the HP has its own rule-setting commands. @x Module 57; Lines 1126 -- 1136 procedure set_rule; var D_p,D_q: integer; begin D_p:=get_integer(dvi) (-4); D_q:=get_integer(dvi)(-4); if (D_p<=0)or(D_q<=0) then {an invisible rule! Dont ask me why \TeX\ wants to do this} else if (D_p*v_conv <= post_height/2) then do_rail(D_p, D_q) else do_post(D_p, D_q); end; @y procedure set_rule; var D_p,D_q: integer; rule_h, rule_v, rule_ht, rule_wid: integer ; {all in pixels} begin D_p:=get_integer(dvi) (-4); D_q:=get_integer(dvi)(-4); if (D_p<=0)or(D_q<=0) then {an invisible rule! Dont ask me why \TeX\ wants to do this} else begin @ @ end; end; @z % Dont shunt on a fine printer @x Module 154; Lines 2678 -- 2679 if H_shunt > (Set_h - 3) then H_shunt := (Set_h - 3) ; @y do_nothing ; @z @x Module 174; Lines 2958 -- 2959 @d out_of_sequence == ( ( Old_v > Set_v) or ( ( Old_v = Set_v) and ( Old_h > Set_h))) @y Since we do not sort, we will not separate the characters into runs. @d out_of_sequence == false @z % Again, shunting is only appropriate for line printers. @x Module 186; Lines 3157 -- 3157 PR_h_next := hpos - H_shunt ; @y PR_h_next := hpos ; @z @x Module 207; Lines 3467 -- 3484 @ So here are their default values. We believe they are all appropriate for lineprinters on VMS machines. Note that the program makes no attempt to check these values for consistency. @= device_ID := 'Lineprinter '; {Pad to 12 chars} list := false ; fortran := false ; b_feed_absolute := false ; b_feed_by_string := false ; feed_absolute := false ; b_feed_scream := true ; b_space_absolute := false ; b_space_by_string := false ; space_absolute := false ; abs_is_incr := false ; wl_does_cr := false ; want_split := true ; @y @ The first lot of data describes the HP's overall style of carriage control. Many of them are completely irrelevant to the HP, but still needed in order for the program to compile. @= device_ID := 'Laserjet + '; list := false ; fortran := false ; b_feed_absolute := true ; b_feed_by_string := false ; feed_absolute := true ; b_feed_scream := true ; b_space_absolute := true ; b_space_by_string :=false ; space_absolute := true ; abs_is_incr := false ; wl_does_cr := false ; want_split := true ; is_header := false ; {each page needs a header} @z @x Module 208; Lines 3518 -- 3527 @ The general run of \TeX\ characters are narrower than line-printer chars. So we spread them out to make them fit. @= l_margin := 1.0 ; {Normal left margin, in inches} top_margin := 1.0 ; {Top ditto} h_fudge := 7.227 {number of points per |h_step|} / 5.25 ; {A typical design width} v_fudge := 2.0 ; { Force double-spacing, in hope that suffixes will come out right} @y @ The general run of \TeX\ characters are narrower than line-printer chars. But the HP prints them at their proper widths. @= l_margin := 1.0 ; {Normal left margin, in inches} top_margin := 1.0 ; {Top ditto} h_fudge := 1.0 ; v_fudge := 1.0 ; @z @x Module 213; Lines 3584 -- 3594 @ This batch is concerned with distances and resolutions. @= h_resolution = 10 ; {|h_steps| per inch} v_resolution = 6 ; {|v_steps| per inch} fixed_width = true ; {printers characters are fixed width} char_width = 1 ; {all printer characters are this width, in units of |h_step|. Normally, |space_dist| will be equal to this, but some printers are not normal!} gap_width = 1 ; {Intended minimum space between words} char_ht = 1 ; @y @ This batch is concerned with distances and resolutions. @= h_resolution = 300 ; {|h_steps| per inch} v_resolution = 300 ; {|v_steps| per inch} fixed_width = false ; char_width = 30 ; {default char. sizes in |h_steps| -- a guess} gap_width = 5 ; {Intended minimum space between words} char_ht = 42 ; @z @x Module 216; Lines 3616 -- 3620 max_font = 1 ; only_one_font = true ; can_dl_font = false ; min_dl_font = 0 ; max_dl_font = 0 ; {printers down-loadable fonts} @y max_font = 40 ; only_one_font = false ; can_dl_font = true ; min_dl_font = 8 ; max_dl_font = 40 ; {printers down-loadable fonts. The HP allows up to 32} @z @x Module 234; Lines 4022 -- 4024 @= @ @ @y @= do_nothing @z @x Module 235; Lines 4028 -- 4028 *** Attach printer change file here *** @y @ The remaining changes can all go at the end of the program. Before getting onto the hardest task (namely, downloading) lets clear up the loose ends that were left lying about in the body of the program. First, there are a number of extra command options: @= else if ( key = "O") then odds := true {Print odd-numbered pages only} else if ( key = "E") then evens := true {Even ditto} else if ( key = "L") then begin land := true ; {Print Landscape} start_stuff.data[ 8] := '1' ; {bugfix, 19-dec-1990, previously 6} end @ @= land, odds, evens: boolean ; @ @= land := false ; odds := false ; evens := false ; @ Where will the printed file go to? @= be_string( '.HPL') ; print_ex := buffer ; @ @= L_reset( run) ; Add_run ; L_reset( mid) ; cur_pge_ptr := son( next( mid) ) ; @ Now lets dispose of rule-setting. \TeX\ puts the reference point of a rule at bottom left, the HP at top left. Sizes must be rounded up. @= rule_ht := round(v_conv*D_p + 0.5) ; rule_wid := round(h_conv*D_q + 0.5) ; D_dis := D_q ; IM_dis := rule_wid ; round_IM_h ( 0); rule_h := IM_h ; rule_v := IM_v - rule_ht ; @ @= set_v_abs(rule_v) ; set_h_abs(rule_h) ; print(chr(27), '*c', rule_ht:1, 'B') ; print(chr(27), '*c', rule_wid:1, 'A') ; print(chr(27), '*c0P') ; print_ln; @ Consider command strings. @= be_string ( '^[E^[&l0O' ) ; start_stuff := buffer ; {Reset everything to default state} be_string ( '^[(&DX' ) ; font_command := buffer ; be_string ( '^[*p&DY' ) ; v_abs_com := buffer ; be_string ( '^[*p&DX ' ) ; h_abs_com := buffer ; stop_stuff := start_stuff ; page_top := blank ; pause_after := blank ; @ On the HP, we must explicitly start a new page at a set position. Also since rules get set before any characters, we must then reset the position. @= set_v_abs(0) ; set_h_abs(0) ; @ @= set_v_abs(0) ; set_h_abs(0) ; @* Downloading, 1: reading the font file. The simplest and crudest way this could possibly be done is: read the raster file and load the entire font, as soon as the |font_def| command is read from the \.{DVI} file. On VAX/VMS, this turned out to be unbearably slow. So it is here changed as follows: When a |font_def| command is read, we read the whole raster file into an array. Then download each character before trying to print it. This `lazy downloading' makes the program run much faster, at the price of a large use of memory. @^\.{TUG}boat@> @= begin @ repeat @ until GF_com = 248; close_binary( raster_file) ; @ end @ First we have to determine the file name. @= raster_mag := round(300 * font_mag * magnification ) ; if not hunt_for_size( font_name, raster_mag) then font_error('cannot load this font') ; @.Error: cannot load@> @ @= function open_font( name: var_string; mag: integer; ask: boolean ): boolean; begin splice( raster_name, raster_def, mag) ; open_font := open_and_ask( raster_file, raster_indx, name, raster_name, ask) ; end; @ Frequently the {\.DVI} file calls for a font at a magnification that is almost but not quite one of the standard sizes. So we try a few steps up or down before giving up. |range| is the maximum percentage that we allow the magnification to vary. @= function hunt_for_size ( name: var_string; mag: integer): boolean; forward ; @ @= function hunt_for_size ; label exit ; const range = 5 ; var try_mag, n , max : integer; hh: boolean; begin max := round( raster_mag* range / 100); n := 0 ; while ( n <= max) do begin try_mag := mag + n ; hh := open_font( name, try_mag, false) ; if hh then return else if ( n>0) then n:= -n else n := 1 - n ; end; hh := open_font( name, mag, true) ; exit: hunt_for_size := hh; end; @ Then read the file; a horrible mess. @= GF_com := get_byte( raster) ; if GF_com <= 63 then paint( GF_com) else if ( GF_com >= 74) and ( GF_com <= 238) then new_row( GF_com - 74 ) else @ Then we have the usual messy |case| statement: @d three_cases(#)== #,#+1,#+2 : begin GF_par := get_integer( raster)( GF_com - # + 1 ); four_case_end @= case GF_com of three_cases( 64)( paint( GF_par)); 67, 68: boc; 69: eoc; 70: miss_row( 0); three_cases( 71)( miss_row( GF_par)); three_cases( 239)( skip( raster)( GF_par)); 242: skip( raster)( get_integer( raster)( -4)) ; 243: skip( raster)( 4); 244:; 247: preamble; 248: postamble ; 245,246,249,250,251,252,253,254,255: warn( 'illegal GF command, will try to continue'); end; @.illegal GF command@> @ Now there follow lots of procedures to deal with the commands. @= procedure preamble; var p: byte; begin p:=get_byte( raster); p:=get_byte( raster); skip( raster)( p) ; {the introductory comment} with Font_box do begin L := pixel_R ; R := pixel_L ; T := pixel_B ; B := pixel_T ; end; end; @# procedure postamble; var GF_check: integer ; begin skip( raster)( 8); GF_check := get_integer( raster)( -4) ; if (D_check<>0)and(GF_check<>0)and(D_check<>GF_check) then begin warn('check sums do not agree!'); @.error: check sums...@> display_ln('DVI check was: ', D_check, ' GF check was: ', GF_check); display(' '); end; end; @# procedure boc; var q : byte; mm, nn: i_word; begin if GF_com = 67 then begin GF_char := get_integer( raster)( 4) mod 256 ; skip( raster) ( 4); Boc_box.L:=get_integer( raster)( -4); Boc_box.R:=get_integer( raster)( -4); Boc_box.B:=get_integer( raster)( -4); Boc_box.T:=get_integer( raster)( -4); end else begin GF_char:=get_byte( raster); q:=get_byte( raster); Boc_box.R:=get_byte( raster); Boc_box.L:=Boc_box.R-q; q:=get_byte( raster); Boc_box.T:=get_byte( raster); Boc_box.B:=Boc_box.T-q; end; @ end; @ Now lets assign names to variables. In order to try to clear up the muddle of boundaries for character cells, I introduce a concept of a ``box'', not the same as a \TeX\ box. The fields represent the boundaries that must contain all the black pixels of any character. @= pixel_L = -300; pixel_R = 700; pixel_B = -300; pixel_T = 700; @ @= @!x_coord=pixel_L..pixel_R; @!y_coord=pixel_B..pixel_T; box = packed record L,R: x_coord; T,B: y_coord; end; dir_entry = packed record point: integer; h_offset, v_offset, wid, ht : i_word; end; @ @= Boc_box, {Limits of char. cell declared in |BOC| command} char_box, {actual boundaries of char. cell} Font_box: box; {The font cell. This is the smallest cell that contains the reference point and any char. cell in the font} GF_com, GF_par, GF_char : byte; glyphs: packed array[1..max_glyph] of byte ; glyph_ptr: integer; raster_mag: integer; directory : array[D_font_ptr, D_char_ptr ] of dir_entry ; C_width : integer; @ @= glyph_ptr := 1 ; for in_i := 0 to max_D_fonts do for in_j := 0 to max_D_char do directory[ in_i, in_j].point := sentry ; {Mark everything as unprintable} @ The definition of \.{GF} files refers to two registers, $(G_m,G_n)$, which hold row and column numbers. We also need to remember |paint_switch|, which is either |black| or |white|. @= @!pixels: packed array [y_coord,x_coord] of pixel; @!G_m: x_coord; @!G_n: y_coord; {current state values} @!paint_switch: pixel; @ We'll need a big array of pixels to hold the character image. Each pixel should be represented as a single bit in order to save space. Different systems may prefer the following definitions, while others may do better using the |boolean| type and constants. @^system dependencies@> @d white==false @d black==true @d swop == paint_switch:=not paint_switch {could also be |if paint_switch=black then paint_switch:=white else paint_switch:=black|} @= @!pixel=boolean ; {could also be |white..black|} @ Maybe there's a faster way to do this on your system. Note that the only part of the |image_array| that we clear is the part that the current character may use. Thus, the rest of this program may not look outside the area delimited by |Boc_box| and expect to see anything but junk. @^system dependencies@> @= begin for nn := Boc_box.B to Boc_box.T do for mm := Boc_box.L to Boc_box.R do pixels[nn,mm] := white; G_n := Boc_box.T ; G_m := Boc_box.L ; char_box.L := pixel_R ; char_box.R := pixel_L ; char_box.B := pixel_T ; char_box.T := pixel_B ; directory[ nf, GF_char].point := 0 ; {Indicates a blank character} end @ @= procedure paint( p: integer); var m, k: integer; begin if G_m+p> pixel_R then warn('character extends too far to the right') @.character extends...@> else if paint_switch = white then else begin m := G_m + p -1 ; if char_box.T < G_n then char_box.T := G_n ; if char_box.B > G_n then char_box.B := G_n ; if char_box.L > G_m then char_box.L := G_m ; for k:= G_m to m do pixels[G_n, k] := black ; if char_box.R < m then char_box.R := m ; end; swop; G_m := G_m +p ; end; @# procedure new_row( p:integer); begin decr(G_n); G_m:=Boc_box.L + p; paint_switch:=black; end; @# procedure miss_row( p: integer); begin G_n:=G_n - p ; G_m:= Boc_box.L; paint_switch:=white; end; @* Downloading, 2: Transfer characters into memory. After we have read the character, we must transfer it into memory. First, stretch the Font box to include the current characters box. @= procedure eoc; var x, y : integer; cur_byt: byte ; q: i_word ; begin if char_box.R >= char_box.L {If not, the character is unprintable} then begin if char_box.L < Font_box.L then Font_box.L := char_box.L ; if char_box.B < Font_box.B then Font_box.B := char_box.B ; if char_box.R > Font_box.R then Font_box.R := char_box.R ; if char_box.T > Font_box.T then Font_box.T := char_box.T ; @ @ end; end; @ In landscape mode, the character must be rotated. The |directory| will contain the dimensions to be downloaded; these do depend on orientation. |char_box| describes the logical character which does not depend on orientation. @= with directory[nf, GF_char] do begin point := glyph_ptr; if land then begin h_offset := - char_box.T ; v_offset := char_box.R ; ht := char_box.R - char_box.L +1 ; wid := char_box.T - char_box.B +1 ; end else begin h_offset := char_box.L ; {\TeX\ and the HP measure this in opposite directions} v_offset := char_box.T ; wid := char_box.R - char_box.L +1 ; ht := char_box.T - char_box.B +1 ; end; end; @ Likewise the |glyphs| array will contain the pixels to be downloaded; these also depend on orientation. Looking at the logical character, |x| is the horizontal coordinate and |y| the vertical. In portrait mode the pixels must be sent starting at the top left corner and going left to right along the top row. In Landscape mode you must start at the top right corner and go down the right hand column. Each row or column must be padded to |8n| bits. @= if land then begin for x := char_box.R downto char_box.L do begin cur_byt := 0 ; q := 0 ; for y := char_box.T downto char_box.B do begin if pixels[ y, x] then cur_byt := cur_byt + powers[q] ; if q < 7 then incr( q) else begin glyphs[glyph_ptr] := cur_byt ; incr( glyph_ptr) ; cur_byt := 0; q := 0 ; end; end; if q > 0 then begin glyphs[glyph_ptr] := cur_byt ; incr( glyph_ptr) ; cur_byt := 0; end; end; end else @ @= begin for y := char_box.T downto char_box.B do begin cur_byt := 0 ; q := 0 ; for x := char_box.L to char_box.R do begin if pixels[ y, x] then cur_byt := cur_byt + powers[q] ; if q < 7 then incr( q) else begin glyphs[glyph_ptr] := cur_byt ; incr( glyph_ptr) ; cur_byt := 0; q := 0 ; end; end; if q > 0 then begin glyphs[glyph_ptr] := cur_byt ; incr( glyph_ptr) ; cur_byt := 0; end; end; end ; @ @= powers[0] := 128 ; for in_i := 1 to 7 do powers[in_i] := powers[in_i-1] div 2 ; @ @= powers: array[0..7] of byte ; @ @= max_glyph = 1000000 ; @ @= display_ln ('Used ', glyph_ptr:1, ' bytes of font memory out of ', max_glyph:1); @ Finally, does the printer have enough room for the font? The HP allows 32 fonts per job and 395 KB memory. I have not checked the restriction of only 16 fonts per page. @= incr(PR_dl_font ) ; if PR_dl_font > max_dl_font then font_error('tried to load too many fonts') ; @.Error: tried to load@> @ @= PR_dl_font := min_dl_font ; PR_mem_used := 0 ; PR_max_mem := 395000; be_string( '^[*c&DD' ) ; font_start := buffer ; @ @= PR_dl_font, PR_max_mem, PR_mem_used: integer ; font_start: var_string ; @ If the error tests succeed, then we come here. Before we can load any characters, we have to send a command to the printer to declare the new font. This section assembles the necessary information. |dir_start[ nf]| should be pointing to the start of the font directory. The main task is that the printer must be given the size of a character cell; this must be large enough to contain all the characters. First we specify the font ID. This is a number by which the printer will refer to the font after loading it. @= print_command( font_start, PR_dl_font, '^') ; print(chr(27), ')s26W' ); {A create font command} prw(26); prw(1) ; { 8 bit chars} prw(0) ; with Font_box do begin if L > 0 then L := 0 ; if B > 0 then B := 0 ; if R < 0 then R := 0 ; if T < 0 then T := 0 ; {Stretch the Font box to include the ref. point} prw( T) ; prw( R - L + 1) ; {width} prw( T - B + 1) ; {height} end; if land then prw( 257) else prw(1) ; {proportional spaced} prw(277) ; for font_i := 1 to 5 do prw(0) ; {The HP needs these parameters, but they serve no purpose known to me} @ Finally, we must establish the map from \TeX\ characters to printers characters in the new font. @= incr(top_code) ; {get a new coding scheme} scheme[ nf] := top_code ; alphabet(0, 33, top_code, PR_dl_font, 190); alphabet(33, 95, top_code, PR_dl_font, 33); for tex_chr := 0 to max_D_char do codes[ top_code, tex_chr].breadth := down_loaded ; @ @= top_code: integer ; @ @<|font_def| vars@>= tex_chr, font_i: integer; @ @= top_code := 1 ; @* Downloading, 3: Lazy downloading. The idea is to load only those characters in each font that actually will be printed. It is obviously essential to ensure that each character gets loaded before being printed, and only once. This is done in the procedure |set_character|. The \TeX\ character is number |c_num| in font |D_font|, but the printer character is addressed by |cod|. The first job is to update the directory info. for the character, and assemble its size parameters. @= with directory [ D_font, c_num] do begin if point < 0 then begin warn( 'tried to print a non-existent character, number: ' , c_num:1) ; codes[ cur_scheme, c_num].breadth := bad_char ; end else begin C_length := ht * (( wid + 7) div 8) ; {length of data} C_width := D_width[ D_font, c_num] ; C_delta := round(C_width * h_conv) ; codes[ cur_scheme, c_num].breadth := C_delta ; cod.breadth := C_delta ; PR_mem_used := PR_mem_used + C_length + 64 ; {approximate} if PR_mem_used > PR_max_mem then warn('overflowed printer memory, will try to proceed regardless') ; @.Error: overflowed printer memory@> @.Error: tried to print...@> @ @= display_ln ('Used ', PR_mem_used:1, ' bytes of printers memory out of ', PR_max_mem:1) ; @ @= C_length, C_delta : integer; char_start, char_head: var_string ; @ @= be_string( '^[*c&DE' ) ; char_start := buffer ; be_string( '^[(s&DW' ) ; char_head := buffer ; @ Now we must not let the character get downloaded twice, so we put the correct value into its |breadth|; we must also update the current |cod|. @ Now we send the character header. First, tell the printer which character will be downloaded. \TeX\ fonts usually have 128 characters and HP fonts have either 96 or 192. The permitted values for HP characters are 33..127 and 160..255 according to the manual but appendix B says 160 and some others are undefined. So we map \TeX\ characters 0..32 onto 190..222 . @= { N.B. still |with directory [ D_font, c_num]| } print_command( font_start, cod.IM_font, '^') ; {Specify printers font identifier} print_command( char_start, cod.IM_char, '^') ; print_command( char_head, (C_length + 16), '^') ; {and the character} prw(1024); prw(14*256 + 1); if land then prw( 256 ) else prw(0); prw(h_offset) ; prw(v_offset); prw(wid) ; prw(ht) ; prw(4 * C_delta) ; @ And at long last we can send the pixels!! @= for d_i := point to point+C_length-1 do print(chr(glyphs[ d_i] )) ; print_ln ; end; end; @ Nearly all the HP's arguments come as signed 16-bit words, to be printed in two-complement notation. This procedure prints them. @= procedure prw( n: i_word); var nn: integer ; begin if (n>= 0) then nn := n else nn := n + 65536 ; print( zchr(nn div 256)); print( zchr(nn mod 256)); end ; @z