% hp.ch -*-mode: change; webfile: crudetype.web version 3.01;-*- % HP.CH Provisional change file for the HP Laserjet... % NOTE the system change file must normally be inserted above this point. % This file reads PXL files, so cannot be tested at our site % @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 @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 @x Module 206; Lines 3419 -- 3457 @* Default declarations for printer. This section defines masses of data to describe how the printer behaves. Previously this was all in the lineprinter change file. However most Change files are for lineprinters, so I moved this stuff into the main program. In V3 I changed most of these constants into variables. The assumed characteristics of a lineprinter are as follows: 1. A lineprinter can print all the printable ASCII characters, and no others. 2. Each character is one |step| high and one |step| wide. 3. Printer will act correctly if it receives the following ASCII controls: line feed, carriage return, space, and form feed. More precisely, the Standard specifies that the \PASCAL\ procedure |page| does something that advances the printer by one page. 4. Backspacing and backfeeding are assumed impossible ; also we do not use tabs. The first lot of data describes the printer's overall style of carriage control. |fortran| means that the carriage control character gets put at the start of the line, and it is here assumed that it must be inserted explicitly. |feed| means a vertical movement and |space| horizontal. Each |thing_char| is the character needed to make the printer do the named action. Owing to the rules of \.{TANGLE}, the words |back| and |tiny| have to be abbreviated (to avoid identifier clashes). @.ASCII@> @= @!device_ID : packed array[1..12] of char ; {Name of device} @# list, fortran, b_feed_absolute, b_feed_by_string, feed_absolute, b_feed_scream, b_space_absolute, b_space_by_string, space_absolute, abs_is_incr, wl_does_cr, want_split, is_header, do_pause, inspection: boolean ; {These say whether the printer can do the named action} @# wl_feed_dist, cr_feed_dist, feed_dist, t_feed_dist, b_feed_dist, tiny_drop, big_drop, space_dist, t_space_dist, b_space_dist, @y @* Printer dependent data. This section defines masses of data to describe how the printer behaves. Previously this was all in the Line printer change file. However most Change files are for lineprinters, so I moved this stuff into the main program. The assumed characteristics of a lineprinter are as follows: 1. A lineprinter can print all the printable ASCII characters, and no others. 2. Each character is one |step| high and one |step| wide. 3. Printer will act correctly if it receives the following ASCII controls: line feed, carriage return, space, and form feed. More precisely, the Standard specifies that the \PASCAL\ procedure |page| does something that advances the printer by one page. 4. Backspacing and backfeeding are assumed impossible ; also we do not use tabs. @ 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 + '; {Pad to 12 chars} list := 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 ; space_absolute := false ; abs_is_incr := false ; w_l_does_c_r := false ; want_split := true ; is_header := false ; {each page needs a header} @z @x Module 207; Lines 3472 -- 3472 device_ID := 'Lineprinter '; {Pad to 12 chars} @y device_ID := 'Laserjet + '; @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 3587 -- 3594 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 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 3611 -- 3623 @ The next batch are concerned with fonts. @= min_font = 1 ; {smallest and largest number of printers resident fonts} max_font = 1 ; only_one_font = true ; can_dl_font = false ; min_dl_font = 0 ; max_dl_font = 0 ; {printers down-loadable fonts} max_codes = 60 ; {no. of known \TeX\ coding schemes} max_plain = 4 ; {Max number of a plain text font} @y @ The next batch are concerned with fonts. @= min_font = 1 ; {smallest & largest number of printers resident fonts} 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} max_codes = 40 ; {no. of possible \TeX\ coding schemes} max_plain = 3 ; {Max number of a plain text font} @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[ 6] := '1' ; 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) ; page_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 a font. The simplest and crudest way this could possibly be done is: read the \.{PXL} 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 pixel file into a very large array. Then download each character before trying to print it. This `lazy downloading' makes the program run much faster, at the price of an enormous |pixels| array. See \.{TUG}boat (Vol.2, No.3) for a description of the \.{PXL} file format. @^\.{TUG}boat@> @= begin @ @ @ @ goto good_font; {Bypass the rest of the \.{TFM} file } end @ First we have to determine the file name. @= raster_mag := round( 1500 * 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; @ @= be_string( 'TEX$PXL:.&DPXL' ) ; raster_def := buffer ; @ 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 we actually read the file into an array called |pixels|. In VMS, the short block at the end of the file ought to be padded with zeroes, but actually seems to be full of garbage. So we must do a fudge to find the true end of the file. @= pxl_start [ nf] := pxl_end ; repeat pixels[ pxl_end] := get_byte( raster) ; incr( pxl_end); if pxl_end = max_pixels then font_error('overflowed pixel array'); @.error: font: overflowed pixel array@> until eof(raster_file) ; while (pixels[ pxl_end] <> pixels[ pxl_start[ nf]+3]) or (pixels[ pxl_end-1] <> pixels[ pxl_start[ nf]+2]) or (pixels[ pxl_end-2] <> pixels[ pxl_start[ nf]+1]) or (pixels[ pxl_end-3] <> pixels[ pxl_start[ nf]]) do decr(pxl_end) ; close_binary(raster_file) ; @.System dependencies@> @ |pxl_end| is the highest used point in |pixels|; the fonts will be piled in on top of one another and |pxl_start[n]| points to the start of font n . @= pixels: packed array[1..max_pixels] of byte ; pxl_mag, pxl_check, pxl_end, dir_ptr : integer; pxl_start, dir_start: array [D_font_ptr] of integer ; raster_mag: integer; @ @= pxl_end := 1 ; @ @= max_pixels = 1000000 ; @ @<|font_def| vars@>= font_i, tex_chr : byte ; @ This is of course a guess, and perhaps we may want to refine it. @= display_ln ('Used ', pxl_end:1, ' bytes of pixel memory out of ', max_pixels:1) ; @ Now |pxl_end| should be pointing to the very last byte of the file; so we will do some checking. |next_half| gets a 16-bit half word from the file, and |two_comp| negates it in twos-complement. @d next_half == pixels[dir_ptr] * 256 + pixels[dir_ptr+1] ; dir_ptr := dir_ptr +2 @d two_comp(#) == if # > 32767 then # := # - 65536 ; @= if pxl_end < 2000 + pxl_start[ nf] then font_error('Pixel file truncated!'); dir_ptr := pxl_end - 19 ; pxl_check := next_half ; two_comp(pxl_check) ; pxl_check := pxl_check * 65536 + next_half ; if pxl_check <> TFM_check then begin warn('pixel checksum disagrees with TFM checksum') ; display('pixel sum is', pxl_check) ; end; @.error: pixel checksum@> @.error: font: Pixel file truncated@> @ Next, look at the directory pointer. @= dir_ptr := pxl_end - 7 ; dir_start[ nf] := next_half ; dir_start[ nf] := dir_start[ nf] * 65536 + next_half ; dir_start[ nf] := dir_start[ nf] * 4 + pxl_start[ nf] ; {we count bytes, not words} if dir_start[ nf] + 2067 <> pxl_end then warn('pixel directory pointer disagrees with file size') ; @.Error: pixel directory pointer@> @ 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: font: tried to load@> @.Error: font: overflowed printer memory@> @ @= PR_dl_font := min_dl_font ; PR_mem_used := 0 ; PR_max_mem := 395000; @ @= PR_dl_font, PR_max_mem, PR_mem_used: integer ; @ 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. @= cell_bot := 0 ; cell_top := 0 ; cell_wid := 0 ; dir_ptr := dir_start[ nf] ; for tex_chr := 0 to 127 do begin @ end; cell_ht := cell_bot + cell_top ; {FUDGE} @ @= C_pxl_w := next_half ; C_pxl_h := next_half ; C_h_off := next_half ; C_v_off := next_half ; two_comp(C_h_off) ; two_comp(C_v_off) ; if C_h_off < 0 then C_pxl_w := C_pxl_w - C_h_off ; if C_pxl_w > cell_wid then cell_wid := C_pxl_w ; if C_v_off > cell_top then cell_top := C_v_off ; if C_v_off < 0 then C_pxl_h := C_pxl_h - C_v_off ; if C_pxl_h > cell_bot then cell_bot := C_pxl_h ; @ @= C_width, cell_ht, cell_top, cell_bot, cell_wid, C_pxl_w, C_pxl_h, C_h_off, C_v_off : integer; @ Next, consider the character's width. This must be calculated exactly as in \.{DVItype}, for the reasons given there. @= dir_ptr := dir_ptr + 4 ; b0 := pixels[ dir_ptr ]; b1 := pixels[ dir_ptr + 1]; b2 := pixels[ dir_ptr + 2]; b3 := pixels[ dir_ptr + 3]; dir_ptr := dir_ptr + 4 ; C_width:= (((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta; if b0 = 255 then C_width:=C_width - alpha else if b0 <> 0 then font_error('Out-of-bounds value for b0') ; @.error: font: Out-of-bounds |b0|@> D_width[ nf, tex_chr] := C_width ; @ We can now send stuff to declare the font. First we specify the font ID. This is a number by which the printer will refer to the font after loading it. @= print(chr(27), '*c', PR_dl_font:1, 'D'); {Specify printers font identifier} print(chr(27), ')s26W' ); {Start a create font command} prw(26); prw(1) ; { 8 bit chars} prw(0) ; prw(cell_top) ; prw(cell_wid) ; prw(cell_ht ) ; prw(1) ; { portrait, proportional spaced} prw(277) ; for font_i := 1 to 5 do prw(0) ; {The HP needs these parameters, but they dont serve any 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 ; @ @= top_code := 1 ; @* Downloading, part2: 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 assemble the size parameters for the character. @= begin dir_ptr := dir_start[D_font] + c_num * 16 ; C_pxl_w := next_half ; C_pxl_h := next_half ; C_h_off := next_half ; C_v_off := next_half ; two_comp(C_h_off) ; two_comp(C_v_off) ; pxl_ptr := next_half ; pxl_ptr := pxl_ptr * 65536 + next_half ; pxl_ptr := pxl_ptr * 4 + pxl_start[D_font] ; Tex_bytes := (C_pxl_w + 31) div 32 ; Tex_bytes := Tex_bytes * 4 ; {Bytes per row in PXL file} PR_bytes := (C_pxl_w + 7) div 8 ;{Ditto, in HP fonts} C_length := PR_bytes * C_pxl_h + 16 ; @ @= pxl_ptr, Tex_bytes, PR_bytes, C_length, C_delta : integer; @ 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|. @= 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@> @ 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 . @= print(chr(27), '*c', cod.IM_font:1, 'D'); {Specify printers font identifier} print(chr(27), '*c') ; print(cod.IM_char:1 ) ; print('E'); print(chr(27), '(s', C_length:1, 'W' ) ; prw(1024); prw(14*256 + 1); prw(0); prw(- C_h_off) ; {\TeX\ and the HP measure this in opposite directions} prw(C_v_off); prw(C_pxl_w) ; prw(C_pxl_h) ; prw(4 * C_delta) ; @ And at long last we can send the pixels!! This is the only pleasant part of the whole messy business. We need not shuffle bits because the H-P wants them in almost the exact order that they appear in the PXL file. Only difference is that H-P packs with zero bits to the next 8-bit byte, while PXL files pack to next 32 bits. That is the reason for the odd-loking calculation of |Tex_bytes| above. @= for d_i:= 1 to C_pxl_h do begin for d_j:= 1 to PR_bytes do begin print(chr(pixels[ pxl_ptr ] )) ; incr(pxl_ptr) ; end ; pxl_ptr := pxl_ptr + Tex_bytes - PR_bytes ; end; print_ln ; 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