;FORM3547.A86 - Print IPSS/FAX files on an HP PCL3-compliant printer.
; Print 3 to a page. Leave a 1-1/4" margin on the left, and a 7/16" margin
; at top, for the template data.
;By default, will print ALL files.IMG in the current directory.
;
;Copyright (C) 1995 USPS
;Contact the author for bug reports and suggestions at:
;jcomeau@world.std.com (email)
;1900 W. Oakland Park Blvd., Ft. Lauderdale, FL 33310-9998
;
;Modifications:
; Added template with bold letters USPS instead of eagle
; 5/16/95 Changed to print every 3 images instead of buffering all the
;  converted files first.
; 6/1/95 Leaves TEMP.TMP alone until ready to overwrite
; 6/1/95 Added conditionals for 2-per-page, rather than separate source
; 6/1/95 V063 Corrected bugs introduced in the above changes (I hope!)
; 6/1/95 Adjusting # lines to chop at the bottom of 3-per-page images
;        (V064) to allow printing of return-address barcodes
; 6/6/95 Now searches to one level of subdirectories for .IMG files...
;        Subdirectory names must also have .IMG extension (V065)
; 6/7/95 Modifying output routines in anticipation of switching to a
;        Genicom or similar high-speed dot matrix printer. V066
; 6/11/95 Set hidden bit after processing so the file won't be reprocessed,
;        2-per-page images will be in .IM2 subdirectories V067
;
debug=1 ;set to zero when debugged
debug1=0 ;for first-wave debugging
wide=1 ;set to zero for 3-per-page, this is for 2-per-page
genicom=0 ;set to one if GENICOM printer used, one image per form
hppcl=1 ;set to one if Hewlett-Packard PCL-5 printer used, 2 or 3 images/form
postscript=0 ;set to one if same format but Adobe Postscript used
ibmpro=0 ;set to one if IBM Proprinter or compatible used, one image/form
;printer types are mutually exclusive, so:
#if hppcl
	genicom=0
	ibmpro=0
	postscript=0
#endif
#if ibmpro
	hppcl=0
	genicom=0
	postscript=0
#endif
#if genicom
	hppcl=0
	ibmpro=0
	postscript=0
#endif
;compression modes for HP-PCL5
method0=1 ;uncompressed
method1=0 ;RLE
method2=0 ;TIFF packbits - these 3 methods should be sufficient
;note - don't enable more than one of the above
cr      equ     13
lf      equ     10
ff      equ     12
so      equ     14
s       equ     15
esc     equ     27
dos     equ     21h
inch    equ     300     ;dots per inch
pagewid equ     8*inch  ;can't use 1/2" of width
maxwid  equ     pagewid/8 ;max width in bytes
black   equ     2       ;for word offset into table
white   equ     0       ;black xor black
stderr  equ     2       ;error device
pagelen equ     11*inch ;dots/page (1/2" unusable)
#if wide
base    equ     pagelen/2 - inch/2 - inch/30 ;for 2 images/page
increment equ   pagelen/2
imgsPerPage equ 2
#else
base    equ     pagelen/3 - inch/30 ;for 3 images/page
increment equ   pagelen/3
imgsPerPage equ 3
#endif
;
print   macro
	call    txterr
	db      'FORM3547 -- ',#1,cr,lf,0
	#em
;
errchk  macro
	call    #1
	#em
;
dbgmsg  macro
	#if debug1
	print   #1
	#endif
	#em
;
prntxt  macro
;send specified text to PRN: device
	call    txtprn   ;embed the text here in the code
	db      #1,#2,#3,#4,#5,#6,#7,#8,#9,0
	#em
;
prnnum  macro
;send specified number in decimal ASCII to PRN: device
	push    ax      ;save current AX contents
	mov     ax,#1   ;load up the number
	call    numprn  ;do the conversion
	pop     ax      ;restore AX
	#em
;
stkvar  macro
ttt=ttt-2
#1=ttt
	#em
;
begin   macro
ttt=0
	enter (#1*2),0
	#em
;
	jmp     start
wildcards: 
#if !wide
	db      "*.IMG",0
#else
	db      "*.IM?",0
#endif ;!wide
wildlen equ     $-offset wildcards ;length of null-terminated string
basedir db      "..",0  ;for CHDIR back to base
fileptr dw      0       ;for use with DOS findfile services    
handle  dw      0       ;input file handle returned by DOS
filesize dw     0       ;size of file returned in DTR
width   dw      0       ;image width from header
pwidth  dw      0       ;width in pixels
pheight dw      0       ;height in pixels
color   dw      0       ;color of current pel in decode line
codsiz  dw      0       ;length of code currently using in table
codlen  dw      0       ;length of code extracted from input file
thiscode dw     0       ;code currently extracted
byteptr dw      0       ;pointer into input file
inbyte  dw      0       ;last byte from input file
bits    dw      0       ;number of bits left in input byte
linesdone dw    0       ;lines completed from current image
image   dw      0       ;image being worked on, 0 to 3 (per page)
thisbyte dw     0       ;current byte in decode line
thisbit dw      80h     ;current bit in decode line
thisline dw     offset lin1bf ;pointer to decode line
refline dw      offset lin1bf ;pointer to reference line
routine dw      readline,huffline ;routine for tag 0, tag 1
lin1bf  db      maxwid dup 0 ;300*8=2400 pixels
	db      01010101xb ;to force a match at end
lin2bf  db      maxwid dup 0 ;same size
	db      01010101xb ;to force a match at end
#if ibmpro OR genicom
bytebuf db      2400 dup 0 ;buffer for dot-matrix printers
#endif ;ibmpro OR genicom
#if hppcl AND method2 ;TIFF compression
tifbuf  db      (maxwid*2) dup 0 ;store counts of matches & non-matches
tifcount dw     0       ;count of bytes used in above buffer
#endif ;hppcl method 2
bufadr  dw      offset buffer
outfile dw      1       ;STDOUT until overwritten
outname db      'temp.tmp',0
loadexec dw     0       ;for segment address of environment string
	dw      offset cmdargs ;segmented pointer to command-line arguments
	dw      0       ;store segment here at runtime
	dw      offset cmdfcb,0   ;first FCB
	dw      offset cmdfcb,0   ;2nd FCB
command db      'c:\command.com',0
	#if debug1
cmdargs db      ' /c copy/b temp.tmp con:',0d,0
	#else
cmdargs db      ' /c copy/b temp.tmp prn:',0d,0
	#endif
cmdfcb  db      0,"           ",0
	db      25 dup (0)
altdta  db      64 dup (0) ;alternate Disk Transfer Area
altdta2 db      64 dup (0) ;another for subdirectory searches
curDTA  dw      0       ;current DTA, init to null
;
start:  print   "Print form 3547's from ISS-scanned images"
	print   "Version V067, Copyright (C) 1995 USPS"
	mov     bx,1000h ;number of paragraphs to keep
	mov     ah,4ah  ;dos function to modify memory size
	int     dos     ;assume OK
	mov     dx,offset altdta ;make a safe DTA
	mov     ah,1ah  ;SET DTA service
	int     dos     ;assume OK
	mov     curDTA,offset altdta ;save it for checking later
	jmp     near    >a1
a0:     mov     ah,3eh  ;file close function
	mov     bx,handle ;get file handle
	int     dos     ;close the file
a1:     call    nextfile ;locate next file and open it
	errchk  >e1     ;check on error
	jc      >a9     ;exit if done
	call    skiphdr ;skip IPSS header info
	errchk  >e2
	call    newcode  ;skip first EOL code, error if not found
	errchk  >e3
a2:     call    gettag  ;get tag bit, shift left to make offset
	errchk  >e5      ;quit on EOF, get another file
	call    routine[bx] ;call appropriate routine
	errchk  >e4     ;check for errors or done
	jmp     a2
a9:     print   'Info - Completed file processing'
	mov     ax,4c00h ;no errors
	int     dos     ;exit to DOS
e1:     jnc     ret     ;return if no carry
	mov     ax,image ;see if we already sent formfeed
	or      ax,ax   ;...
	stc     ;set carry in any case
	je      ret     ;return if all done
	mov     image,2 ;otherwise force FF
	call    imagedone ;eject page and return
	stc     ;force exit
	ret
e2:     jnc     ret     ;return if no carry
	print   'Error - Does not appear to be IPSS image'
	pop     ax      ;don't return
	jmp     near a0 ;get another file
	ret
e3:     je      ret     ;return if EOL found
	print   'Error - Did not find EOL code at top of image'
	pop     ax      ;don't return
	jmp     near a0 ;get another file
	ret
e4:     ja      ret     ;return if not EOF nor error
	pop     ax      ;don't return
	call    imagedone ;clean up and prepare for next
	jmp     near a0 ;get another file
e5:     jnc     ret     ;return if no error
	pop     ax      ;clean up stack
	jmp     near a0 ;get another file
;
skiphdr:
	dbgmsg  'Skipping IPSS file header'
	mov     bx,240  ;skip first 240 bytes
	mov     pheight,word buffer[bx-14] ;get width and height
	mov     pwidth,word buffer[bx-12]
	mov     byteptr,bx ;update the pointer
	sub     filesize,bx ;...and size word, set carry if borrow
;now we want to advance to the vertical and horizontal position that will
;place the image at the lower right of the 1/3 sheet that the form uses.
;There are 3150 dots per page vertically, 2400 horizontally. This leaves
;1050 dots per image vertically - max IPSS image height is 1024 dots. Max
;IPSS width is 2400, equal to the page size.
;Also note that though only 3150 dots can be printed vertically, calculations        
;must be based on the 11*300 = 3300 dots LENGTH per page.
	mov     bx,image ;3 images per page
	shl     bx,1    ;make word offset
	jnz      >a1     ;skip if 2nd or 3rd image
;now (re)create the output file        
	mov     cx,0    ;zero attributes
	mov     dx,offset outname ;point to output filename
	mov     ah,3ch  ;create file, truncating if already exists
	int     dos     ;do it now
	mov     outfile,ax ;store the returned handle
#if hppcl
;send the escape sequences to initialize the page...
	prntxt  esc,'%-12345X',esc,'E'
	prntxt  esc,'&l0E' ;sets top margin to zero vs. default of 1/2"
a1:     mov     ax,bottom[bx] ;get Y value from table
	sub     ax,pheight ;now move up the number of dots the image occupies
	prntxt  esc,'*p' ;send intro escape sequence
	prnnum  ax      ;send the number in decimal ASCII
	prntxt  'Y'     ;finally, end the escape sequence with "Y"
	prntxt  esc,'*t'
	prnnum  inch    ;resolution
	prntxt  'R'     ;establishes DPI
#endif ;hppcl
	mov     width,pwidth ;copy pixel width into byte width
	shr     width,1 ;now make width into byte count
	jc      >a9     ;problem if not an even number of bytes
	shr     width,1 ;...
	jc      >a9     ;...
	shr     width,1 ;...
	jc      >a9     ;...
	ret
a9:     print   'Error - Image is not on byte boundaries'
	ret
;
bottom  dw      base
	dw      base+increment
#if !wide
	dw      base+2*increment
#endif
;
tagfile:
;put the name of the file in an inconspicuous place on the image, in case we
;have to reprint the piece or, worse, locate the actual mailpiece in the
;tray - the clerks can run 1 tray at a time and write down the piece count
;at the end of each tray, then they will be able to locate the piece quite
;easily in the trays, since the image filename will be the same as the
;sequence number, possibly plus an offset as added by the IPSS-SIM program
	dbgmsg  'Placing filename in small letters on the image'
	prntxt  esc,'(s1p4v0s0b4148T',esc,'*p' ;select UNIVERS
	mov     bx,image ;get number of image, 0-2
	shl     bx,1    ;shift over to make word offset
	mov     ax,bottom[bx] ;then get the number
	sub     ax,50   ;not quite so low
	prnnum  ax      ;convert to decimal ASCII and send it
	prntxt  'Y',esc,'*p'
	prnnum  inch/3
	prntxt  'X'
	mov     ah,2fh  ;get DTA address
	int     dos     ;into BX
	add     bx,30   ;point to filename string
	prntxt  0       ;print it
	ret
;
template:
#if hppcl
	prntxt  esc,'(8U',esc,')8M',esc,'(s1p18v0s3b4148T'
	prntxt  esc,'*p'
	prnnum  inch/3
	prntxt  'Y'
	call    >t2     ;print RETURN TO SENDER notice
	prntxt  esc,'*p'
	prnnum  inch/3+increment
	prntxt  'Y'
	call    >t2
#if !wide        
	prntxt  esc,'*p'
	prnnum  inch/3+2*increment
	prntxt  'Y'
	call    >t2
#endif ;!wide
	prntxt  esc,'(s7V' ;switch to 7 pitch for fine print warning
	prntxt  esc,'*p'
	mov     ax,inch/6+increment/55
	prnnum  ax
	prntxt  'Y'
	call    >t1     ;print warning
	prntxt  esc,'*p'
	add     ax,increment
	prnnum  ax
	prntxt  'Y'
	call    >t1
#if !wide
	prntxt  esc,'*p'
	add     ax,increment
	prnnum  ax
	prntxt  'Y'
	call    >t1
#endif ;!wide
	prntxt  esc,'&a90P' ;rotate to print landscape
	prntxt  esc,'(s12v0B' ;switch to medium stroke, 12 pitch
	mov     ax,(increment/2)-inch-(inch/15)-(inch/30)
	call    >t3     ;show "US POSTAL SERVICE FORM 3547"
	add     ax,increment
	call    >t3
#if !wide
	add     ax,increment
	call    >t3
#endif ;!wide
	ret
t1:     
	prntxt  esc,'*p'
	prnnum  5*inch+inch/3
	prntxt  'X PENALTY FOR PRIVATE'
	prntxt  esc,'*p'
	prnnum  5*inch+inch/3
	prntxt  'x+'
	prnnum  inch/10
	prntxt  'YUSE TO AVOID PAYMENT'
	prntxt  esc,'*p'
	prnnum  5*inch+inch/3
	prntxt  'x+'
	prnnum  inch/10
	prntxt  'Y    OF POSTAGE, $300'
	ret
t2:     prntxt  esc,'&a10C',so,163,s,' return to sender ',so,163
	prntxt  s,'     fee due 50',191,'                      USPS'
	ret
t3:     prntxt  esc,'&a0R',esc,'*p'
	prnnum  ax
	prntxt  'XU.S. POSTAL SERVICE'
	add     ax,inch/30
	prntxt  esc,'&a1R',esc,'*p'
	prnnum  ax
	prntxt  'X    PS FORM 3547'
	ret
#endif ;hppcl
;
imagedone:
	dbgmsg  'Finishing up the image'
	xor     ax,ax   ;for clearing registers
	mov     linesdone,ax ;...
	mov     color,ax ;always white to start
	mov     thisbyte,ax ;clear byte pointer
	mov     thisbit,80h ;reinit bit pointer
	mov     bits,0  ;bit counter
	mov     bx,offset lin1bf ;clear line buffers
	call    clearline ;...
	mov     bx,offset lin2bf ;...
	call    clearline ;...
	call    tagfile ;print filename at bottom left of image
	inc     image   ;this image complete
	mov     ax,imgsPerPage ;see if done a page...
	cmp     ax,image ;...
	jne     ret     ;done if not
	call    template ;else overlay the template
	prntxt  ff      ;issue a formfeed
#if hppcl
;send the escape sequences to reinitialize the printer...
	prntxt  esc,'E',esc,'%-12345X' ;
#endif ;hppcl        
	mov     image,0 ;and clear the count
;now the new code to send the file to the printer (5/16/95)
	mov     ah,3fh  ;close file function
	mov     bx,outfile ;output handle
	int     dos     ;go ahead and close it
	mov     ax,[2ch] ;get paragraph address of environment block from PSP
	mov     word loadexec,ax ;store in in LOADEXEC block
	mov     word [loadexec+4],ds ;store segment address of command args
	mov     word [loadexec+8],ds ;and of FCBs
	mov     word [loadexec+12],ds ;...
	mov     dx,offset command
	mov     bx,offset loadexec
	mov     ax,4b00h ;EXEC, execute child program
	int     dos     ;do it
;now reset the DTA, since EXEC throws it into outer space
	mov     dx,curDTA ;make a safe DTA
	mov     ah,1ah  ;SET DTA service
	int     dos     ;assume OK
	ret
;
eolproc:
;finish up the current decode line and send to lineprinter
	dbgmsg  'EOLPROC - Entered routine'
;reduce output by reading back from the end of the buffer to first nonzero
; byte, and only sending that much to the output
;6/1/95 In order to leave room at the bottom of the mailpiece to spray the        
; return address barcode, we will stop printing the bottom 100 lines or so
; of the mailpiece... may have to adjust that number...
#if !wide ;doesn't matter when you've got half the darn sheet...       
	mov     ax,linesdone ;get number of lines already completed
	add     ax,150  ;add the offset desired from the bottom
	cmp     ax,pheight ;compare to number of lines in the piece
	jae     >a8     ;so skip all this if it's more
#endif
#if ibmpro OR genicom        
;lay out the data in bytes which represent columns, high bit at the top
; of the column. Remember that the high bit of the decoded fax data, and
; also for PCL, is the leftmost bit of the raster byte.
#endif
#if hppcl        
	mov     di,thisline ;get address of data
	add     di,width ;point to end of it
	dec     di      ;now pointing to final byte
	std     ;search backwards
	xor     al,al   ;for non-null
	mov     cx,width ;count word
	repz    scasb   ;search for first nonmatch (non-null)
	cld             ;restore ordinary direction
	inc     cx      ;adjust for overshoot
	mov     di,thisline ;now start from beginning and do the same thing
	repz    scasb   ;search for first non-null
	inc     cx      ;adjust for overshoot
	dec     di      ;...
	prntxt  esc,'*p' ;set up for horizontal positioning
	mov     ax,2400 ;max image width...
	sub     ax,pwidth   ;less what the image occupies
	mov     bx,di   ;now figure in the offset to first non-null
	sub     bx,thisline ;offset from start of buffer
	shl     bx,1    ;multiply by 8 to make bytes into pixels
	shl     bx,1    ;...
	shl     bx,1    ;...
	add     ax,bx   ;add offset just determined
	prnnum  ax      ;send to the PCL printer...
	prntxt  'X'     ;specify X coordinate
	prntxt  esc,'*r1A' ;start at current cursor position 
	prntxt  esc,'*b' ;send graphics data introducer
#if method2 ;TIFF packbits?
	jcxz    >a1     ;skip if nothing to pack
	call    tifcomp ;else compress it first and insert "2m"
#endif ;method2
a1:     prnnum  cx      ;send width in bytes
	prntxt  'W'     ;end of escape sequence
	mov     dx,di   ;set up registers for WRITE
;continues here with buffer address in DX and byte count in CX:
	mov     ah,40h  ;...
	mov     bx,outfile ;...
	int     dos     ;do it...
	jc      ret     ;quit on error
	prntxt  esc,'*rB' ;end raster graphics mode
#endif ;hppcl
a8:     inc     linesdone ;up the count of completed lines
#if debug2
	call    txterr ;let debugger know
	db      'FORM3547 -- Count of lines completed is ',0
	mov     ax,linesdone
	call    numerr ;show LINESDONE as decimal ASCII
	call    txterr ;issue a CRLF
	db      cr,lf,0
#endif ;debug2
	mov     ax,offset lin1bf ;get buffer addresses
	mov     bx,offset lin2bf ;...
	cmp     ax,thisline ;are we using the first one?
	jne     >a9     ;skip if not, we will now
	xchg    ax,bx   ;otherwise swap
a9:     mov     thisline,ax ;store new values for current line and ref line
	mov     refline,bx ;...
	mov     bx,ax   ;now clear the new current line
	call    clearline ;...
	xor     ax,ax   ;clear carry
	mov     color,ax ;color white for new line
	mov     thisbyte,ax ;clear pointer
	mov     thisbit,80h ;...
	or      ax,thisbit ;clear Z flag which is reserved for EOF
	ret
;
#if hppcl AND method2 ;TIFF packbits
;note from page 15-22 of PCL 5 Printer Language Reference Manual, Oct. 1992:
;"It is more efficient to code two consecutive identical bytes as a repeated
; byte. If these bytes are preceded and followed by literal bytes, however,
; it is more efficient to code the entire group as literal bytes."
tifcomp:        
	xor     ax,ax   ;we'll leave AH clear
	mov     dx,ax   ;copy zero into last-byte register
	mov     tifcount,ax ;clear count to start
	push    cx      ;save current count
	push    di      ;data pointer
	mov     si,di   ;copy current data pointer
	mov     bx,offset tifbuffer ;point to buffer
	mov     [bx],ah ;clear current location
	lodsb   ;load up a data byte
	dec     cx      ;decrement the count
	mov     dl,al   ;copy the byte for later comparison
;now, for the length of the data, count SAME bytes and NOT-SAME,
; storing the counts in the array set up for that purpose.
a1:     jcxz    >a5     ;pack it up when done counting        
	lodsb   ;get next byte and advance pointer
	dec     cx      ;dec the count
	cmp     al,dl   ;same as last time?
	je      >a3     ;skip ahead if so
;current byte is not the same as last time
	test    byte ptr [bx],80h ;is current count a repeat count?
	jne     >a2     ;skip if not
;current byte not same as last time, but previous byte was repeated:
	inc     bx      ;advance to next array element
	mov     [bx],ah ;clear it
	mov     dl,al   ;store the current byte as "last byte"
	jmp     short a1 ;loop
;current byte not same as last time, neither was last byte:
a2:     inc     byte ptr [bx] ;up count of literal bytes
	mov     dl,al   ;copy current byte as "last byte"
	jns     a1      ;loop if it didn't overflow into high bit
	dec     byte ptr [bx] ;else make it 127
	inc     bx      ;point to next array element
	mov     [bx],ah ;clear it
	jmp     short a1 ;loop
;current byte same as last time
a3:     test    byte ptr [bx],80h ;are we already counting repeat bytes?
	jne     >a4     ;skip ahead if so
;current byte same as last time, but previous bytes are literal
#endif ;hppcl&method2
;
nextfile:
	dbgmsg  'Looking for another file to process'
	mov     ah,4fh  ;assume DTA already initialized
	mov     bx,curDTA ;get current DTA
	mov     cx,10h  ;get subdirectories but not hidden files
	xor     dx,dx   ;for test
	or      dx,fileptr ;is a pointer defined?
	jne     >l0     ;continue if so
	mov     dx,offset wildcards ;else use *.IMG
	mov     ah,4eh  ;specify new search
l0:     cmp     bx,offset altdta2 ;are we on lower level?
	jne     >l1     ;skip if not
	push    ax      ;save DOS service number
	push    dx      ;save wildcard string
	mov     dx,offset altdta+30 ;else point to name of subdirectory
	mov     ah,3bh  ;CHDIR service
	int     dos     ;go for it, don't bother checking result
	pop     dx      ;restore wildcard pointer
	pop     ax      ;and service number
l1:     mov     fileptr,dx ;make sure to use correct function next time
	cmp     bx,offset altdta2 ;are we already one level deep?
	jne     >l2     ;continue if not
	mov     cx,0    ;otherwise don't search for subdirectories
l2:     int     dos     ;load DTA with file info
	errchk  >e1     ;if no more files, check for lower level
	test    byte [bx+21],10h ;is it a subdirectory?
	je      >l3     ;continue if not
	mov     dx,offset altdta2 ;else use the next DTA down
	mov     fileptr,0 ;clear filename string pointer
	mov     ah,1ah  ;SET DTA service
	int     dos     ;assume OK
	mov     curDTA,offset altdta2 ;save it for checking later
	jmp     short nextfile ;loop back to try again
l3:     
#if debug1
	test    byte [bx+21],2 ;is it hidden?
	je      >l4     ;skip if not
	print   'File has HIDDEN bit set'
l4:
#endif ;debug
	add     bx,30   ;point to filename string
	call    showfile ;let user know which file we're working on
	mov     dx,bx   ;move to appropriate register for call
	mov     ax,4301h ;set attributes function
	mov     cx,2    ;hidden bit
	int     dos     ;set the bit so the file is inaccessable next time
	mov     ax,3d00h ;open for read
	int     dos     ;call DOS
	errchk  >e2     ;check for error
	sub     bx,4    ;point to file size
	mov     handle,ax ;save the handle
	mov     cx,[bx] ;get size from DTA
	mov     filesize,cx ;store it for later
#if debug1
	call    txterr
	db      "File size is ",0
	mov     ax,cx
	call    numerr
	call    txterr
	db      13,10,0
#endif
	mov     dx,offset buffer ;point to buffer
	mov     bx,handle ;get the handle
	mov     ah,3fh  ;use READ function
	int     dos     ;do it...
	mov     ax,offset altdta2 ;check if we're on lower level
	cmp     ax,curDTA ;are we?
	jne     ret     ;done if not
	jmp     cd_up   ;else cd ..
	ret
e1:     jnc     ret     ;return if OK
	pop     ax      ;clean up stack
	mov     ax,curDTA ;for comparison
	cmp     ax,offset altdta2 ;are we on lower level?
	jc      ret     ;return carry set if not
;programmer's note: ALTDTA2 must be positioned *after* ALTDTA for jc to work
	mov     dx,offset altdta ;switch back to primary DTA
	mov     ah,1ah  ;set DTA service
	int     dos     ;assume OK
	mov     curDTA,offset altdta ;save the changed pointer
	call    cd_up   ;move back up to base directory
	jmp     nextfile ;loop back to try again
e2:     jnc     ret     ;return if OK
	pop     ax      ;clean up stack
	print   'Error opening file, skipping this one'
	call    cd_up   ;set default back with cd..
	stc     ;re-set the carry flag
	ret
;
cd_up:
	push    dx      ;save registers
	push    ax
	mov     dx,offset basedir ;point back up to starting point
	mov     ah,3bh  ;CHDIR service
	int     dos     ;go for it, don't bother checking result
	pop     ax      ;restore registers and return
	pop     dx
	ret
;
;The following code formats messages for the output devices:
;
txtprn:
;if followed by an ASCIZ string, prints the string to OUTFILE; if followed
; immediately by a null, uses string address in BX
	pushf           ;save flags
	push    ax      ;save registers we will be using
	push    bx      ;...
	push    cx      ;...
	push    dx      ;...
	push    di      ;to locate null
	push    bp      ;pointer
	mov     bp,sp   ;so we can use relative addressing
	mov     di,[bp+14] ;address of text
	xor     ax,ax   ;clear to indicate null follows call
	or      ax,[di] ;let's see if it's null
	jne     >a1     ;skip if not
	xchg    bx,di   ;else use address in BX
	add     bx,2    ;and point past null word to return address
a1:     mov     dx,di   ;copy into text pointer
	xchg    al,ah   ;in case string was only 1 byte, move it into msb's
	mov     cx,80   ;max string length we will allow
	xor     al,al   ;looking for null
	repnz   scasb   ;loop till we find the null
	mov     cx,di   ;get the pointer
	sub     cx,dx   ;subtract the start of the string
	dec     cx      ;don't print the null
	or      ax,ax   ;see if we are using string following call
	jne     >a2     ;skip if so
	mov     di,bx   ;else get return address from BX
a2:     mov     [bp+14],di ;update the return address
	mov     bx,outfile ;output file
	mov     ah,40h  ;specify write function
	int     dos     ;do it...
	pop     bp      ;restore registers...
	pop     di      ;...
	pop     dx      ;...
	pop     cx      ;...
	pop     bx      ;...
	pop     ax      ;...
	popf            ;and flags
	ret
;
txterr:
;if followed by an ASCIZ string, prints the string to STDERR; if followed
; immediately by a null, uses string address in BX
	pushf           ;save flags
	push    ax      ;save registers we will be using
	push    bx      ;...
	push    cx      ;...
	push    dx      ;...
	push    di      ;to locate null
	push    bp      ;pointer
	mov     bp,sp   ;so we can use relative addressing
	mov     di,[bp+14] ;address of text
	xor     ax,ax   ;clear to indicate null follows call
	or      ax,[di] ;let's see if it's null
	jne     >a1     ;skip if not
	xchg    bx,di   ;else use address in BX
	add     bx,2    ;and point past null word to return address
a1:     mov     dx,di   ;copy into text pointer
	xchg    al,ah   ;in case string was only 1 byte, move it into msb's
	mov     cx,80   ;max string length we will allow
	xor     al,al   ;looking for null
	repnz   scasb   ;loop till we find the null
	mov     cx,di   ;get the pointer
	sub     cx,dx   ;subtract the start of the string
	dec     cx      ;don't print the null
	or      ax,ax   ;see if we are using string following call
	jne     >a2     ;skip if so
	mov     di,bx   ;else get return address from BX
a2:     mov     [bp+14],di ;update the return address
	mov     bx,stderr ;standard handle for error device (screen)
	mov     ah,40h  ;specify write function
	int     dos     ;do it...
	pop     bp      ;restore registers...
	pop     di      ;...
	pop     dx      ;...
	pop     cx      ;...
	pop     bx      ;...
	pop     ax      ;...
	popf            ;and flags
	ret
;
numprn:
	push    bx      ;save registers
	push    cx      ;...
	push    dx      ;...
	push    si      ;...
	push    di      ;...
	push    bp      ;...
	sub     sp,6    ;need 5 spaces for number-to-string conversion
			;plus one extra space for leading zero
	mov     bp,sp   ;for relative addressing
	xor     si,si   ;for clearing registers
	mov     di,5    ;pointer into digits
	mov     cx,10   ;constant for division
	mov     byte [bp+di],'0' ;init with ASCII zero
	or      ax,ax   ;is number zero?
	je      >a2     ;if so, skip computation
a1:     mov     byte [bp+di],'0' ;init with ASCII zero
	mov     dx,si   ;clear high word
	div     cx      ;put remainder in DX
	or      [bp+di],dl ;and merge with '0' to make ASCII number
	dec     di      ;adjust the pointer
	or      ax,ax   ;see if we're done
	jne     a1      ;loop if not
	inc     di      ;adjust upwards for last DEC
a2:     mov     cx,6    ;max chars to send, plus 1
	sub     cx,di   ;subtract pointer to get actual number
	lea     dx,[bp+di] ;address of string
	mov     bx,outfile ;handle for output file
	mov     ah,40h  ;specify write function
	int     dos     ;do it...
	add     sp,6    ;clear off the buffer we made at entry
	pop     bp      ;...
	pop     di      ;...
	pop     si      ;...
	pop     dx      ;...
	pop     cx      ;...
	pop     bx      ;...
	ret
;
numerr:
	pushf
	push    bx      ;save registers
	push    cx      ;...
	push    dx      ;...
	push    si      ;...
	push    di      ;...
	push    bp      ;...
	sub     sp,6    ;need 5 spaces for number-to-string conversion
			;plus one extra space for leading zero
	mov     bp,sp   ;for relative addressing
	xor     si,si   ;for clearing registers
	mov     di,5    ;pointer into digits
	mov     cx,10   ;constant for division
	mov     byte [bp+di],'0' ;init with ASCII zero
	or      ax,ax   ;is number zero?
	je      >a2     ;if so, skip computation
a1:     mov     byte [bp+di],'0' ;init with ASCII zero
	mov     dx,si   ;clear high word
	div     cx      ;put remainder in DX
	or      [bp+di],dl ;and merge with '0' to make ASCII number
	dec     di      ;adjust the pointer
	or      ax,ax   ;see if we're done
	jne     a1      ;loop if not
	inc     di      ;adjust upwards for last DEC
a2:     mov     cx,6    ;max chars to send, plus 1
	sub     cx,di   ;subtract pointer to get actual number
	lea     dx,[bp+di] ;address of string
	mov     bx,stderr ;standard handle for error device
	mov     ah,40h  ;specify write function
	int     dos     ;do it...
	add     sp,6    ;clear off the buffer we made at entry
	pop     bp      ;...
	pop     di      ;...
	pop     si      ;...
	pop     dx      ;...
	pop     cx      ;...
	pop     bx      ;...
	popf    ;...
	ret
;
showfile:
;Display a message with the current filename, called with BX pointing to
; the ASCIZ name... Destroys DX and AX
	call    txterr  ;print header stuff
	db      'FORM3547 -- Working on file ',0
	call    txterr  ;now print the filename
	dw      0       ;word zero forces TXTERR to use addr in BX
	call    txterr  ;now the CRLF
	db      cr,lf,0 ;...
	ret
;
;Code from here on is independent of the output device:
;
clearline:
;clear 300-byte buffer pointed to by BX
	mov     di,bx   ;so we can use fast instructions
	mov     cx,150  ;words are faster too
	xor     ax,ax   ;store zeroes
	repz    stosw   ;do it...
	ret
;
readline:
;Process a READ-encoded line of input data
	dbgmsg  'READLINE - Entered routine'
a1:     call    readcode ;process next code from input stream
	errchk  >e1     ;abort on error
	jne     a1      ;loop till EOL code found
	mov     ax,thisline ;make current line the new reference line
	mov     refline,ax ;...
	jmp     eolproc ;send the line to output
e1:     jnc     ret     ;return if no error
	print   'Warning - Error translating READ-encoded line'
	pop     ax      ;don't return within routine
	ret     ;to previous caller
;
readcode:
	dbgmsg  'READCODE - Entered routine'
	call    newcode ;find first 1 bit
	errchk  >e1     ;check on EOF error
	mov     bx,codlen ;get length of zero-extended code
	cmp     bx,6    ;max length is 6 to first 1 bit
	errchk  >e2     ;error if more
	shl     bx,1    ;make word offset from length word
	jmp     >a1[bx-2]  ;jump to proper routine
a1      dw      offset >a2,offset >a3,offset >a4
	dw      offset >a5,offset >a6,offset >a7
a2:     xor     bx,bx   ;vertical offset 0
	jmp     vertmode ;go paint it
a3:     mov     bx,1    ;vertical offset + or -1
	jmp     near >a8 ;find out which, then paint it
a4:     jmp     tworuns ;two Huffman-encoded runs follow, go to it
a5:     jmp     passmode ;skip next two runs in reference line
a6:     mov     bx,2   ;vertical offset + or -2
	jmp     near >a8 ;find out which, then paint it
a7:     mov     bx,3   ;vertical offset + or -3
a8:     call    inccode ;get following bit to find direction
	and     thiscode,1 ;test for positive
	jne     >a9      ;skip if correct
	neg     bx      ;else adjust accordingly
a9:     jmp     vertmode
e1:     ja      ret     ;return if neither C nor Z set
	pop     ax      ;throw away return address
	jc      ret     ;return on EOF error
	mov     ax,thisbyte
	cmp     ax,width ;EOL, did we really finish the line?
	je      ret     ;return Z set if so
	print   'WARNING - Unexpected EOL code in READ data stream'
	xor     ax,ax   ;set Z flag
	ret
e2:     jbe     ret     ;return if code within range
	print   'ERROR - Code found was not valid READ code'
	pop     ax      ;throw return address
	stc     ;don't continue with this image
	ret
;
vertmode:
;process a run in vertical mode. BX has the offset.
	dbgmsg  'VERTMODE - Entered routine'
	mov     si,thisbyte ;get byte pointer into decode line
	mov     ax,thisbit ;and bit pointer...
	mov     cx,refline ;get pointer to reference line
	xchg    bx,cx   ;swap with offset
	mov     di,width ;and point DI to EOL
	mov     dx,color ;test current color value
	or      dx,dx   ;zero is white...
	je      >w1      ;skip if so
;if here, current color is black, so...
;look for first transition from black-to-white on reference line (b1)
	test    [bx+si],al ;check the bit just above a0
	jne     >s2     ;skip if black, just look for next white
s1:;otherwise we have to look for a black run first...
	shr     al,1      ;skip to next bit
	errchk  >e1     ;adjust if it fell out
	test    [bx+si],al ;is it still white?
	je      s1      ;keep looping if so
s2:     shr     al,1      ;skip to next bit
	errchk  >e1     ;adjust if it fell out
	test    [bx+si],al ;is it still black?
	jne     s2      ;loop if so
;Now we're pointing to the b1 pel on the reference line. The trick now is to
; paint the black pels from a0 up to the offset specified in CX.
	jmp     >p1     ;skip to paint routine
w1:;current color is white, so we're looking for a white-to-black transition
	test    [bx+si],al ;is the bit above a0 white?
	je      >w3     ;skip if so, we just need to look for black
;otherwise, we must first skip the current black run, it doesn't count
	or      si,si   ;see if we're at start of line
	jne     >w2     ;continue if not
	or      al,al   ;at start, AL is negative if taken as signed (80h)
	jns     >w2     ;so continue if not
;At the start of a line, a0 is the imaginary white pel to the left of the
; decode line; so since the first reference pel was black, we are already
; pointing to the first transition pel (b1).
	jmp     near >p1 ;go straight to paint routine
w2:     shr     al,1      ;next bit
	errchk  >e1     ;adjust on rollover
	test    [bx+si],al ;is it still black?
	jne     w2      ;loop if so
w3:     shr     al,1      ;next bit down
	errchk  >e1     ;adjust if necessary
	test    [bx+si],al ;still white?
	je      w3      ;loop if so
p1:     mov     bx,thisline ;no longer need reference line
	or      cx,cx   ;check if offset is positive or negative
	js      >p4     ;skip if negative
	je      >p6     ;skip further if zero
p2:     shr     al,1      ;shift right until offset is reached
	errchk  >e3     ;use similar adjust routine as above
	loop    p2      ;for count in CX
	jmp     near >p6 ;now paint it
p4:     neg     cx      ;make the count positive
p5:     shl     al,1      ;shift left for negative number
	errchk  >e2     ;different rollover routine needed
	loop    p5      ;for count in CX
p6:     or      dx,dx   ;is color to be painted white?
	je      >p10    ;just adjust pointers if so
	mov     di,si   ;else copy dest pointers
	mov     dx,ax   ;bit also
	mov     si,thisbyte ;restore original source values...
	mov     ax,thisbit ;...
p7:     or      [bx+si],al ;start at a0 and paint up to specified offset
	shr     al,1      ;skip to next bit
	errchk  >e3     ;adjust when necessary
	cmp     si,di   ;loop till pointers are equal
	jb      p7      ;...
	cmp     ax,dx   ;...
	jne     p7
p10:    mov     thisbyte,si ;update the pointers
	mov     thisbit,ax ;...
	mov     ax,black ;change color for next time
	xor     color,ax ;...
	or      ax,ax   ;clear Z flag
	ret     ;with carry clear from OR
e1:     jnc     ret     ;continue if bit still within bounds
	mov     al,80h  ;else reset to top of the byte
	inc     si      ;update byte pointer
	cmp     di,si   ;at end of the line?
	ja      ret     ;continue if not
	pop     bp      ;don't return to where called
	or      cx,cx   ;better be a negative or zero offset
	jle     p1      ;just go paint it if so
	print   'Error - Vertical mode pointer out of bounds'
	stc     ;set error flag
	ret     ;to previous caller
e2:     jnc     ret     ;continue if bit didn't fall off the top
	mov     al,1    ;else start it at the bottom
	dec     si      ;and move byte pointer back one
	ret     ;now continue where we left off
e3:     jnc     ret     ;continue if bit still within bounds
	mov     al,80h  ;else reset to top of the byte
	inc     si      ;update byte pointer
	ret
;
passmode:
;Locate pixel b2 as explained on p.168 of Hummel's _Data and Fax
; Communications_, then paint the a0 color to the pel on the decode line
; just before b2. Uses pretty much the same logic as VERTMODE, except
; we're skipping two transitions not just one. Also, color does not change
; afterwards as it does in VERTMODE.
	dbgmsg  'PASSMODE - Entered routine'
	mov     si,thisbyte ;get byte pointer into decode line
	mov     ax,thisbit ;and bit pointer...
	mov     bx,refline ;get pointer to reference line
	mov     di,width ;and point DI to EOL
	mov     dx,color ;test current color value
	or      dx,dx   ;zero is white...
	je      >w1      ;skip if so
;look for first transition from black-to-white on reference line (b1)
	test    [bx+si],al ;check the bit just above a0
	jne     >s2     ;skip if black, just look for next white
s1:;otherwise we have to look for a black run first...
	shr     al,1      ;skip to next bit
	errchk  >e1     ;adjust if it fell out
	test    [bx+si],al ;is it still white?
	je      s1      ;keep looping if so
s2:     shr     al,1      ;skip to next bit
	errchk  >e1     ;adjust if it fell out
	test    [bx+si],al ;is it still black?
	jne     s2      ;loop if so
s3:;now skip one more time, to b2 transition, white-to-black
	shr     al,1      ;next bit down
	errchk  >e1     ;adjust on rollover
	test    [bx+si],al ;still white?
	je      s3      ;loop if so
	jmp     >p1     ;skip to paint routine
w1:;current color is white, so we're looking for a white-to-black transition
	test    [bx+si],al ;is the bit above a0 white?
	je      >w3     ;skip if so, we just need to look for black
;otherwise, we must first skip the current black run, it doesn't count
	or      si,si   ;see if we're at start of line
	jne     >w2     ;continue if not
	or      al,al   ;at start, AL is negative if taken as signed (80h)
	jns     >w2     ;so continue if not
;At the start of a line, a0 is the imaginary white pel to the left of the
; decode line; so since the first reference pel was black, we are already
; pointing to the first transition pel (b1).
	jmp     near >w4 ;now go find b2
w2:     shr     al,1    ;next bit
	errchk  >e1     ;adjust on rollover
	test    [bx+si],al ;is it still black?
	jne     w2      ;loop if so
w3:     shr     al,1      ;next bit down
	errchk  >e1     ;adjust if necessary
	test    [bx+si],al ;still white?
	je      w3      ;loop if so
w4:;skip one more time to next transition, black-to-white, b2
	shr     al,1      ;next bit
	errchk  >e1     ;adjust on rollover
	test    [bx+si],al ;still black?
	jne     w4      ;loop if so
p1:     mov     bx,thisline ;no longer need reference line
p6:     or      dx,dx   ;is color to be painted white?
	je      >p10    ;just adjust pointers if so
	mov     di,si   ;else copy dest pointers
	mov     dx,ax   ;bit also
	mov     si,thisbyte ;restore original source values...
	mov     ax,thisbit ;...
p7:     or      [bx+si],al ;start at a0 and paint up to specified offset
	shr     al,1      ;skip to next bit
	errchk  >e3     ;adjust when necessary
	cmp     si,di   ;loop till pointers are equal
	jb      p7      ;...
	cmp     ax,dx   ;...
	jne     p7
p10:    mov     thisbyte,si ;update the pointers
	mov     thisbit,ax ;...
	or      ax,ax   ;clear error flag and Z flag
	ret
e1:     jnc     ret     ;continue if bit still within bounds
	mov     al,80h  ;else reset to top of the byte
	inc     si      ;update byte pointer
	cmp     di,si   ;at end of the line?
	ja      ret     ;continue if not
	pop     bp      ;don't return to where called
	jmp     p1      ;just paint it
e3:     jnc     ret     ;continue if still within bounds
	mov     al,80h  ;reset to top of byte
	inc     si      ;adjust pointer
	ret
;
tworuns:
;On READ-encoded line, extract and paint the next two Huffman-encoded runs
	dbgmsg  'TWORUNS - Entered routine'
	push    color   ;current color on stack
	mov     bp,sp   ;pointer to it
a1:     call    getcode ;get next code
	errchk  >e1     ;abort on error
	call    huffout ;paint the pixels
	errchk  >e2     ;abort on error
	mov     ax,[bp] ;get entry color back
	cmp     color,ax ;same color as before?
	je      a1      ;continue if so
a2:     call    getcode ;do the whole thing over for next color...
	errchk  >e1     ;...
	call    huffout ;...
	errchk  >e2     ;...
	mov     ax,[bp] ;get entry color back
	cmp     color,ax ;this time we want to check for original color
	jne     a2      ;so if different, keep looping
	or      ax,bp   ;clear carry and Z, and return
	pop     ax      ;get color off stack
	ret
e1:     ja      ret     ;no error nor EOL, continue
	pop     ax      ;else back to previous caller
	pop     ax      ;clean up stack
	ret
e2:     jnc     ret     ;return if no error
	pop     ax      ;else abort
	pop     ax      ;clean up stack
	ret
;
getbyte:
;load up a new byte from input
	push    bx
	mov     bx,byteptr ;get pointer address
	mov     bl,buffer[bx]  ;get byte at that address
	mov     byte inbyte,bl ;load up the byte
	inc     byteptr ;update the pointer
	mov     bx,1    ;'cause DEC doesn't set carry
	sub     filesize,bx ;...
	pop     bx
	ret
;
newcode:
;extract bits from input into THISCODE - return Z set on EOL, C on EOF
	push    ax      ;save register
	xor     ax,ax   ;clear any old register
	mov     thiscode,ax ;clear code
	mov     codlen,ax ;clear code length
	call    inccode ;then get first set bit from input
	pop     ax      ;restore AX
	ret
inccode:
	push    ax      ;save AX register
a1:     mov     ax,bits ;any bits left in current byte?
	or      ax,ax   ;we'll see...
	jne     >a2     ;skip if so
	call    getbyte ;else get a new byte from input
	errchk  >e1     ;check for error
	mov     bits,8 ;reflect new byte in BITS count
a2:     inc     codlen  ;update registers
	dec     bits    ;...
	shl     thiscode,1 ;move code up one bit
	shr     inbyte,1  ;move low bit into carry
	adc     thiscode,0 ;merge into low bit
	je      a1      ;loop till nonzero
	mov     ax,12   ;for comparison
	cmp     ax,codlen ;see if same or more
	ja      >a3     ;return if less than 12 bits
	mov     ax,1    ;return Z set if code is 1 (EOL)
	sub     ax,thiscode ;...
	or      ax,ax   ;but clear carry flag, used only for errors
a3:     pop     ax      ;restore AX
	ret
e1:;return if carry set from GETBYTE
	jnc     ret     ;continue if not
	pop     ax      ;throw away return address
	pop     ax      ;restore AX
	ret
gettag:
	xor     bx,bx   ;zero out BX
	mov     codlen,bx ;clear code length
	inc     bx      ;make it one
	mov     thiscode,bx ;make code nonzero
	call    inccode ;now get next bit
	mov     bx,1    ;for AND
	and     bx,thiscode ;will be 1 for Huffman-encoded line
	shl     bx,1    ;make word offset and return
	ret
;
huffline:
;translate and paint a line of huffman-encoded image data
;Returns C set on error, Z on EOF
	dbgmsg  'HUFFLINE - Entered routine'
a1:     call    getcode ;get the next code from input stream
	errchk  >e1     ;quit on error or done
	call    huffout ;build PCL output
	errchk  >e2     ;quit on error
	jmp     near a1 ;loop till ERRCHK pulls us out
a3:     jmp     eolproc ;finish up the line and send it
e1:     je      >e3     ;special check on EOL
	jnc     ret     ;return if no problem
	pop     ax      ;toss the return address
	ret
e2:     jnc     ret     ;return if no error
	print   'Error - Aborting Huffman decode process'
	pop     ax      ;trash the return address
	ret
e3:     pop     ax      ;don't return in any case
	mov     ax,thisbyte ;did we go anywhere?
	or      ax,ax   ;check...
	jne     a3      ;continue if so
	ret             ;else return with Z set to indicate EOF
;
getcode:
;The Huffman search engine, taken from my PDP-11 routine. Since MOVs in
; Intel-land don't set any bits, we'll have to test using OR.
	dbgmsg  'GETCODE -- Entered routine'
	call    newcode ;get first nonzero bit
	errchk  >e1     ;return if EOL or I/O error
	mov     bx,color ;get current color 0=white, 2=black
	mov     bx,hufftbl[bx] ;now point to table for that color
a1:     xor     ax,ax   ;clear before OR
	or      ax,[bx] ;get next code from table
	errchk  >e2     ;check if null (end of table)
	jns     >a2     ;continue if positive
	neg     ax      ;otherwise force it positive
	mov     codsiz,ax ;save it, it's the current codesize
	mov     cx,[bx+2] ;pointer to next size in CX
	add     bx,4    ;skip to next table entry
	jmp     near a1 ;loop
a2:     mov     si,[bx+2] ;store pelstring length
	add     bx,4    ;advance pointer to next entry
a3:     mov     dx,codlen
	cmp     codsiz,dx ;up to length extracted from file?
	je      >a4     ;continue if so
	ja      >a5     ;skip ahead if more, we need to extract more
	mov     bx,cx   ;else get pointer to next size
	jmp     near a1 ;loop back around
a4:     cmp     ax,thiscode ;does table entry match extracted code?
	jne     a1      ;loop if not
	mov     bx,color ;save color before updating
	xor     ax,ax   ;clear before OR
	or      ax,si   ;get pelstring length into AX to return
	errchk  >e3     ;adjust if negative
	ret     ;with size in AX and color in BX
a5:     call    inccode ;merge another bit onto the code
	errchk  >e4     ;abort on error
	jmp     near a3 ;else loop
e1:;test if NEWCODE returned Z set (for EOL) or C set (EOF)
	ja      ret     ;return if neither
	pop     ax      ;else junk the return address
	ret
e2:;see if end of table reached
	jne     ret     ;return if nonzero
	print   'Error - Code not found in Huffman table'
	pop     ax      ;junk the return address
	ret
e3:;adjust size word if negative, new color if not
	jl      >e9   ;skip if make-up code
	mov     cx,black ;else adjust color
	xor     color,cx ;toggles to opposite, clears carry
	or      cx,cx   ;clear Z flag
	ret
e4:     jnc     ret     ;continue if no error
	pop     ax      ;trash the return address
	ret
e9:     neg     ax      ;make pelstring length positive
	or      ax,ax   ;clear carry
	ret
;
huffout:
;produce PCL output for the run just decoded
;Length is in AX, color in BX, buffer is initialized to white (zeroes)
;PCL bit image data shows the MSB to the left of the page, bytes shown
; in right-to-left order
	dbgmsg  'HUFFOUT - Entered routine'
	or      ax,ax   ;is length zero?
	je      ret     ;if so, nothing to do
	mov     si,thisbyte ;get pointer into decode line
	mov     dx,thisbit ;get bit-in-byte currently being used
	mov     cx,ax   ;length becomes loop count
	mov     ax,thisline ;address of current decode line
	xchg    ax,bx   ;put that in BX, color in AX
	or      ax,ax   ;see if white...
	je      >a5     ;skip if so, just adjust pointers
a1:     cmp     si,width ;are we at the end of the buffer?
	jc      >a2     ;continue if not
	print   'ERROR - Black string from Huffman code overflows the buffer'
	stc     ;set carry flag
	ret
a2:     or      [bx+si],dl ;set the current bit in current byte
	shr     dl,1    ;rotate to next pixel position    
	errchk  >e3     ;adjust if it went off the deep end
	loop    a1      ;continue for count in CX
;here when CX is exhausted
	mov     thisbit,dx ;save bit pointer...
	mov     thisbyte,si ;byte pointer...
	or      dl,dl   ;clear carry and Z flags
	ret
a5:     
;skips directly to here if white, nothing to paint so just adjust pointers
	mov     ax,cx   ;get count back into AX
	mov     cx,8    ;divide by 8 to get byte count
	xor     dx,dx   ;remainder goes into DL
	div     cx      ;AX/CX
	add     thisbyte,ax ;add byte count to pointer
	mov     ax,width ;check for overflow
	cmp     ax,thisbyte ;...
	errchk  >e7     ;...
	mov     ax,thisbit ;get current bit
	mov     cx,dx   ;get remainder into count word
	or      cx,cx   ;see if we have to shift at all
	je      >a7     ;skip if not
a6:     shr     al,1      ;shift right one bit at a time...
	errchk  >e6     ;if it goes out of bit zero, adjust the pointer
	loop    a6      ;loop for count in CX
a7:     mov     thisbit,ax ;store new bit pointer
	or      al,al   ;clear carry and Z flags
	ret
e3:     jnc     ret     ;just continue if bit is still in bounds
	mov     dl,80h  ;refresh the bit pointer
	inc     si      ;update the byte pointer
	ret     ;continue processing till stopped by CX count
e6:     jnc     ret     ;continue if bit didn't drop off
	inc     thisbyte ;otherwise advance pointer
	mov     al,80h   ;refresh bit pointer
	ret
e7:     jnc     ret     ;return if within image width boundary
	print   'ERROR - White string from HUFFMAN code overflows the buffer'
	pop     ax      ;trash the return address
	ret     ;to previous caller
;
hufftbl dw      >w4,>b2
b2      dw      -2,>b3     ;first codelength for black is 2
	dw      11xb,2
	dw      10xb,3
b3      dw      -3,>b4
	dw      010xb,1
	dw      011xb,4
b4      dw      -4,>b5
	dw      0011xb,5
	dw      0010xb,6
b5      dw      -5,>b6
	dw      00011xb,7
b6      dw      -6,>b7
	dw      000101xb,8
	dw      000100xb,9
b7      dw      -7,>b8
	dw      0000100xb,10
	dw      0000101xb,11
	dw      0000111xb,12
b8      dw      -8,>b9
	dw      00000100xb,13
	dw      00000111xb,14
b9      dw      -9,>b10
	dw      000011000xb,15
b10     dw      -10,>b11
	dw      0000110111xb,0 ;special code for 0-length black
	dw      0000010111xb,16
	dw      0000011000xb,17
	dw      0000001000xb,18
	dw      0000001111xb,-64 ;negative length for make-up codes
b11     dw      -11,>b12
	dw      00001100111xb,19
	dw      00001101000xb,20
	dw      00001101100xb,21
	dw      00000110111xb,22
	dw      00000101000xb,23
	dw      00000010111xb,24
	dw      00000011000xb,25
	dw      00000001000xb,-1792
	dw      00000001100xb,-1856
	dw      00000001101xb,-1920
b12     dw      -12,>b13
	dw      000011001010xb,26
	dw      000011001011xb,27
	dw      000011001100xb,28
	dw      000011001101xb,29
	dw      000001101000xb,30
	dw      000001101001xb,31
	dw      000001101010xb,32
	dw      000001101011xb,33
	dw      000011010010xb,34
	dw      000011010011xb,35
	dw      000011010100xb,36
	dw      000011010101xb,37
	dw      000011010110xb,38
	dw      000011010111xb,39
	dw      000001101100xb,40
	dw      000001101101xb,41
	dw      000011011010xb,42
	dw      000011011011xb,43
	dw      000001010100xb,44
	dw      000001010101xb,45
	dw      000001010110xb,46
	dw      000001010111xb,47
	dw      000001100100xb,48
	dw      000001100101xb,49
	dw      000001010010xb,50
	dw      000001010011xb,51
	dw      000000100100xb,52
	dw      000000110111xb,53
	dw      000000111000xb,54
	dw      000000100111xb,55
	dw      000000101000xb,56
	dw      000001011000xb,57
	dw      000001011001xb,58
	dw      000000101011xb,59
	dw      000000101100xb,60
	dw      000001011010xb,61
	dw      000001100110xb,62
	dw      000001100111xb,63
	dw      000011001000xb,-128
	dw      000011001001xb,-192
	dw      000001011011xb,-256
	dw      000000110011xb,-320
	dw      000000110100xb,-384
	dw      000000110101xb,-448
	dw      000000010010xb,-1984
	dw      000000010011xb,-2048
	dw      000000010100xb,-2112
	dw      000000010101xb,-2176
	dw      000000010110xb,-2240
	dw      000000010111xb,-2304
	dw      000000011100xb,-2368
	dw      000000011101xb,-2432
	dw      000000011110xb,-2496
	dw      000000011111xb,-2560
	dw      000000000001xb,0 ;EOL code
b13     dw      -13,0
	dw      0000001101100xb,-512
	dw      0000001101101xb,-576
	dw      0000001001010xb,-640
	dw      0000001001011xb,-704
	dw      0000001001100xb,-768
	dw      0000001001101xb,-832
	dw      0000001110010xb,-896
	dw      0000001110011xb,-960
	dw      0000001110100xb,-1024
	dw      0000001110101xb,-1088
	dw      0000001110110xb,-1152
	dw      0000001110111xb,-1216
	dw      0000001010010xb,-1280
	dw      0000001010011xb,-1344
	dw      0000001010100xb,-1408
	dw      0000001010101xb,-1472
	dw      0000001011010xb,-1536
	dw      0000001011011xb,-1600
	dw      0000001100100xb,-1664
	dw      0000001100101xb,-1728
	dw      0,0     ;end of black codes
w4      dw      -4,>w5  ;white codes start with codesize of 4
	dw      0111xb,2
	dw      1000xb,3
	dw      1011xb,4
	dw      1100xb,5
	dw      1110xb,6
	dw      1111xb,7
w5      dw      -5,>w6
	dw      10011xb,8
	dw      10100xb,9
	dw      00111xb,10
	dw      01000xb,11
	dw      11011xb,-64
	dw      10010xb,-128
w6      dw      -6,>w7
	dw      000111xb,1
	dw      001000xb,12
	dw      000011xb,13
	dw      110100xb,14
	dw      110101xb,15
	dw      101010xb,16
	dw      101011xb,17
	dw      010111xb,-192
	dw      011000xb,-1664
w7      dw      -7,>w8
	dw      0100111xb,18
	dw      0001100xb,19
	dw      0001000xb,20
	dw      0010111xb,21
	dw      0000011xb,22
	dw      0000100xb,23
	dw      0101000xb,24
	dw      0101011xb,25
	dw      0010011xb,26
	dw      0100100xb,27
	dw      0011000xb,28
	dw      0110111xb,-256
w8      dw      -8,>w9
	dw      00110101xb,0
	dw      00000010xb,29
	dw      00000011xb,30
	dw      00011010xb,31
	dw      00011011xb,32
	dw      00010010xb,33
	dw      00010011xb,34
	dw      00010100xb,35
	dw      00010101xb,36
	dw      00010110xb,37
	dw      00010111xb,38
	dw      00101000xb,39
	dw      00101001xb,40
	dw      00101010xb,41
	dw      00101011xb,42
	dw      00101100xb,43
	dw      00101101xb,44
	dw      00000100xb,45
	dw      00000101xb,46
	dw      00001010xb,47
	dw      00001011xb,48
	dw      01010010xb,49
	dw      01010011xb,50
	dw      01010100xb,51
	dw      01010101xb,52
	dw      00100100xb,53
	dw      00100101xb,54
	dw      01011000xb,55
	dw      01011001xb,56
	dw      01011010xb,57
	dw      01011011xb,58
	dw      01001010xb,59
	dw      01001011xb,60
	dw      00110010xb,61
	dw      00110011xb,62
	dw      00110100xb,63
	dw      00110110xb,-320
	dw      00110111xb,-384
	dw      01100100xb,-448
	dw      01100101xb,-512
	dw      01101000xb,-576
	dw      01100111xb,-640
w9      dw      -9,>w11
	dw      011001100xb,-704
	dw      011001101xb,-768
	dw      011010010xb,-832
	dw      011010011xb,-896
	dw      011010100xb,-960
	dw      011010101xb,-1024
	dw      011010110xb,-1088
	dw      011010111xb,-1152
	dw      011011000xb,-1216
	dw      011011001xb,-1280
	dw      011011010xb,-1344
	dw      011011011xb,-1408
	dw      010011000xb,-1472
	dw      010011001xb,-1536
	dw      010011010xb,-1600
	dw      010011011xb,-1728
w11     dw      -11,>w12
	dw      00000001000xb,-1792
	dw      00000001100xb,-1856
	dw      00000001101xb,-1920
w12     dw      -12,0
	dw      000000010010xb,-1984
	dw      000000010011xb,-2048
	dw      000000010100xb,-2112
	dw      000000010101xb,-2176
	dw      000000010110xb,-2240
	dw      000000010111xb,-2304
	dw      000000011100xb,-2368
	dw      000000011101xb,-2432
	dw      000000011110xb,-2496
	dw      000000011111xb,-2560
	dw      000000000001xb,0 ;EOL code
	dw      0,0     ;end of table
buffer: end
