Introduction

Sending Control Codes to the Printer

by Kenneth G. Getz, Larry Abare, and Karen Robinson

One of the frequently asked questions of the Software Support Center concerns
how to send control codes to the printer from within dBASE III PLUS.  We
usually advise callers to use the ? or @...SAY command to send the codes to
the printer.  If you change printing fonts often (condensed to extra wide to
back to condensed), repeatedly typing in long strings of control codes can be
a tedious and sometimes confusing process.  The two programs presented here,
SetPrint.ASM and PatchPrt.PRG, allow you to store up to ten sets of printer
control codes in a data table and send these codes to your printer from dBASE
III, dBASE III PLUS, or DOS.  

The main routine, SetPrint.ASM, is an assembly language program that stores
the codes in a data table and sends them to your printer.  You can RUN it as a
.COM file or LOAD and CALL it as a .BIN file.  The supporting program,
PatchPrt.PRG, is a dBASE III PLUS program that provides a way for you to add,
edit, and change codes stored in the data table without having to reassemble
and link SetPrint.ASM.   


SetPrint.ASM 

Setprint.ASM

SetPrint stores up to ten different printer control strings in a data table. 
Each string can have as many as 26 codes.  You can enter these codes directly
into the table or through the dBASE III PLUS program, PatchPrt.PRG.  

To use SetPrint from within dBASE III or dBASE III PLUS, your computer must
have at least 320K of RAM.  To assemble SetPrint.ASM as a .COM or .BIN file,
you need the IBM or Microsoft Macro Assembler (MASM.EXE), version 2.0 or
higher.  The smaller assembler, ASM.EXE, will not work since it does not
support macros or conditional assembly, both of which are used in
SetPrint.ASM.  You will also need the programs LINK.EXE and EXE2BIN.EXE,
included on the DOS Supplemental Programs disk with version 2.0 and higher.  

SetPrint accepts one parameter, either a ? or a number between 0 and 9
inclusive.  If you are executing it as a .BIN file, the parameter is a
character string.  If you are executing it as a .COM file from DOS or dBASE,
the parameter is a literal.  For example,

   LOAD SetPrint
   CALL SetPrint with "3" 

or

   RUN SetPrint 3

or

   A> SetPrint 3

When you enter a number (0 through 9), SetPrint sends the control codes
corresponding to that table position to the printer.  The example above sends
the fourth set of control codes. There are no messages displayed to indicate
whether or not the printer accepted the codes.  So, if your printer is off
when you execute SetPrint, the codes are simply ignored.  Furthermore, when
the codes are sent to the printer, you will see the changed print font only
when you print a document.

You can use the ? symbol as a parameter to display a list of the codes stored
in the table.  If you are displaying this list from within dBASE III PLUS, SET
STATUS OFF first in order to prevent the status bar from moving up the
screen.  In addition, to make sure that all of the control codes display, do
not use ANSI.SYS as a device driver.  If you use ANSI.SYS, the list may be
incomplete since non-printable characters, such as ASCII 27, do not display. 

In addition to updating the table through PatchPrt, you can also change the
data table directly.  The table of codes is stored in SetPrint and by default
is null-filled to 26 characters.  In the program code, the table looks like
this, 

   table    db       26 dup(0)
            db       26 dup(0)
            db       26 dup(0)
            db       26 dup(0)
            db       26 dup(0)
            db       26 dup(0)
            db       26 dup(0)
            db       26 dup(0)
            db       26 dup(0)
            db       26 dup(0)

The table contains ten rows, one row per string of control codes.  When you
enter the printer control codes, use ASCII decimal value for each character
separating them by commas.  If your code sequence is less than 26 decimal
codes long, pad the string with nulls (00) out to 26 places.  Option 0 in the
table below is the Epson condensed print codes, Esc 15.  The command 24 dup(0)
tells the program to attach 24 null characters to the end of the sequence.  Be
sure to use this command to pad the printer control codes strings that you
enter.  

   db       27,15,24 dup(0)
   db       26 dup(0)
   db       26 dup(0)
   db       26 dup(0)
   db       26 dup(0)
   db       26 dup(0)
   db       26 dup(0)
   db       26 dup(0)
   db       26 dup(0)
   db       26 dup(0)

Each time you change the table of printer codes, SetPrint must be reassembled
and linked. 


PatchPrt.PRG

PatchPrt.PRG

PatchPrt is a dBASE III PLUS program that provides an easy method for entering
and updating the table of printer control codes without having to edit,
reassemble, and link SetPrint.ASM.  (PatchPrt runs only in dBASE III PLUS
because it uses commands and functions that are not available in dBASE III.) 
When you use this program, make sure that the files PatchPrt.PRG and SetPrint
(.BIN or .COM) are in the same subdirectory.  The DOS file DEBUG.COM must also
be available.  Either copy it into the same subdirectory as the other files or
set a DOS path to its location.

Enter the printer control codes as literal characters or as ASCII decimal
values.  ASCII decimal values must be entered as a three-digit numbers
delimited with braces, such as {027} for Esc.  Enter each code from left to
right without separating them with spaces or commas.  For non-printable
characters, such as Esc, always use the ASCII decimal value.  For example, on
an Epson printer, the code sequence for double-strike is Esc G.  You can enter
this in either of the following ways:

   {027}G
 
or

   {027}{071} 

Each sequence is limited to 26 codes.  If you enter more, PatchPrt truncates
them.  This limitation refers to the actual number of ASCII codes sent, not to
the number of characters used to send the control codes.  The letter "A" is
considered a single code, for example; its decimal equivalent, {065}, is also
considered a single code.  

When you execute PatchPrt, it displays a description of the printer control
codes previously entered through PatchPrt.  If you enter the control codes
directly into the table, a description does not display.  PatchPrt prompts you
to select a sequence to edit or to type Q (quit).  Once the printer control
codes and descriptions are entered, PatchPrt translates the control codes to
their hexadecimal values and writes these values to a text file using SET
ALTERNATE.  The text file is then used by DEBUG.COM to update the data table
in SetPrint.  The program finishes by reloading the newly updated module using
the LOAD command.


Setup

Setup

To set up the programs, enter the code for both PatchPrt.PRG and SetPrint.ASM
using MODIFY COMMAND or any text editor that generates standard ASCII text
files.  If you plan to execute SetPrint from DOS or RUN it from dBASE III,
assemble it as a .COM file.  If you are going to use the LOAD/CALL command in
dBASE III PLUS, assemble it as a .BIN file.  

To use SetPrint as a .COM file, change the second line of the program code to
read as follows:

   COM    EQU	1

To use it as a .BIN file, the second command line should read:

   COM    EQU	0

To assemble SetPrint.ASM as either a .COM or .BIN file, the following files
must be available, either in the same subdirectory as SetPrint.ASM or through
the DOS PATH command: MASM.EXE, LINK.EXE, and EXE2BIN.EXE.  Follow the steps
below to assemble SetPrint.ASM as either a .COM or .BIN file.  

1. From the DOS prompt, type: 

      MASM SetPrint.ASM;
   
   This creates an object file (SetPrint.OBJ).

2. Then, type: 

     LINK SetPrint;
   
   This creates SetPrint.EXE.

3. To create a .COM file, type:

      EXE2BIN SetPrint.EXE SetPrint.COM
    
   To create a .BIN file, type:

      EXE2BIN SetPrint.EXE

4. Last, delete SetPrint.EXE:

      ERASE SetPrint.EXE

The programs presented here will save you time and increase your efficiency by
allowing you to store and call your ten most-often-used printer control codes
from any of three places: dBASE III, dBASE III PLUS, and DOS.  If your office
has several different kinds of printers, you can create a custom version of
SetPrint for each printer type.  

The usefulness of SetPrint is not limited to dBASE applications.  Because you
can also run it from the DOS level, it's actually a generic utility that can
be used alone or with other software programs.


Code

SetPrint.ASM

Code

; Program ....: Setprint.ASM
; Author .....: Ken Getz
; Date .......: December 1, 1986
; Version ....: dBASE III, Developer's Release, dBASE III PLUS
; Note(s) ....: Sends preplanned printer control strings to the
;               printer either from DOS or from within dBASE.
;
CODE     SEGMENT  BYTE PUBLIC       'CODE'

COM      EQU      0             ; Should read 'COM EQU 1' for COM file.
MAX      EQU      26
TOTAL    EQU      10
CR       EQU      13
LF       EQU      10

write1   MACRO    char          ; Macro to write a single character.
         PUSH     AX
         PUSH     DX
         MOV      DL,char
         MOV      AH,2
         INT      21h
         POP      DX
         POP      AX
         ENDM

SETPRINT PROC     FAR
         ASSUME   CS:code,DS:code

IF      COM                     ; Indicates where to look
        ORG     05Dh            ; for param if COM file.

param   LABEL   byte
        ORG     100h
ENDIF

START:  JMP     ENTRY

table   db      26 dup(0)       ; Empty table for printer codes.
        db      26 dup(0)
        db      26 dup(0)
        db      26 dup(0)
        db      26 dup(0)
        db      26 dup(0)
        db      26 dup(0)
        db      26 dup(0)
        db      26 dup(0)
        db      26 dup(0)

ENTRY:   
        PUSH    CX               ; Save working environment.
        PUSH    SI
        PUSH    DX
        PUSH    BX
        PUSH    AX
        PUSH    DS

IF      COM                     ; If a .COM file,
        MOV     AL,param        ;  get the parameter.
        CMP     AL,32           ; Check for a parameter.
        JG      PARAM_OK1       ; If it's there, continue.
        JMP     EXIT            ; If not, quit.
ELSE
        MOV     AX,[BX]         ; If a .BIN file, get the
ENDIF                           ; parameter from dBASE.

PARAM_OK1:
        PUSH    CS
        POP     DS              ; Force DS to be the same as CS.
        CMP     AL,'?'          ; If the parameter is '?', 
        JNE     NOT_HELP        ; display all options, otherwise, 
        JMP     DISPLAY_ALL     ; check for the specific parameter.

NOT_HELP:
        CMP     AL,'0'          ; Is parameter greater than '0'?
        JGE     RANGE_CHECK
        JMP     EXIT            ; If not, quit.
RANGE_CHECK:
        CMP     AL,'9'          ; Is it less than '9'?
        JLE     PARAM_OK3
        JMP     EXIT            ; If not, quit.
PARAM_OK3:                      ; Convert parameter to an integer value
        SUB     AL,48           ; by subtracting the ASCII value of '0'.
        XOR     AH,AH           ; Zero out top byte of AX.
PARAM_OK2:
        MOV     CX,MAX          ; Store length of printer code to CX.
        MUL     CX              ; Get offset into table by
                                ; multiplying the offset (AX)
                                ; by the length of the code (CX).
        MOV     SI,OFFSET table ; Get the offset of the table of printer
codes.
        ADD     SI,AX           ; Add that to the original offset.
LOOPER:                         ; Send the codes to the printer.
        MOV     DL,[SI]         ; Store a single character in DL.
        MOV     AH,5            ; Call service 5 of
        INT     21h             ; INT 21h.
        INC     SI              ; Go to next character
        LOOP    LOOPER          ; until CX = 0.
        JMP     EXIT
DISPLAY_ALL:                    ; Loops to display all codes.
        MOV     DI,OFFSET table   
                                ; Store the location of the 
                                ; table in DI.
        MOV     CX,TOTAL        ; Store the total number of 
                                ; strings in CX.
        write1  CR              ; Call the macro 'write1' to 
                                ; write a single character.
        write1  LF
OUTER_LOOP:
        XOR     BX,BX           ; Zero out the BX register.
        MOV     DX,TOTAL        ; Store the total number of
                                ; strings in DX.
        SUB     DX,CX           ; Compute the string number (0 - 9).
        ADD     DX,48           ; Convert to character
                                ; by adding ASCII '0'.
        write1  DL              ; Write the string number.
        write1  ':'
        write1  ' '      
INNER_LOOP:
        CMP     BYTE PTR [DI],0   
                                ; Search for ASCII NULL to see
                                ; if at end of string.
        JE      INC_DI          ; If so, go on to the next string.
        MOV     DL,BYTE PTR [DI]
                                ; Otherwise, move the code into DL
        write1  DL              ; and write it out.
INC_DI:
        INC     BX              ; Go to next string.
        INC     DI
        CMP     BX,MAX          ; See if we're at the last one.
        JL      INNER_LOOP      ; If not, go back for more.
        write1  CR
        write1  LF
        LOOP    OUTER_LOOP
EXIT:    
        POP     DS              ; Restore the environment.
        POP     AX
        POP     BX
        POP     DX
        POP     SI
        POP     CX

IF      COM                     ; If this is a COM program, quit to DOS.
        MOV     AX,4C00h
        INT     21h      
ELSE
        RET                     ;  Otherwise, return to dBASE.
ENDIF    
SETPRINT ENDP
CODE     ENDS
         END    START 


PatchPrt.PRG

* Program ....: Patchprt.PRG
* Author .....: Larry Abare
* Date .......: December 1, 1986
* Version ....: dBASE III Plus, Developer's Release
* Note(s) ....: Utility to modify printer codes in assembled
*               .BIN file.  Asks user for string to modify,
*               then characters to replace.  Does NO error
*               checking on validity of printer codes.
*               Assumes that DEBUG.COM is available (in same
*               directory or PATH).
*
SET STATUS OFF
SET SAFETY OFF
SET TALK OFF
SET BELL OFF
SET ALTERNATE TO Prtcodes.txt
* ---Modifiable parameters
infile = "Setprint.BIN"
choice = SPACE(1)
* ---Length of all the printer codes
codelen = 26
STORE SPACE(35) TO s0,s1,s2,s3,s4,s5,s6,s7,s8,s9,;
                   sc0,sc1,sc2,sc3,sc4,sc5,sc6,sc7,sc8,sc9
IF FILE("Codes.MEM")
   RESTORE FROM Codes ADDITIVE
ENDIF
* ---Offset of printer table in .BIN file
table = 3
full_pad = REPLICATE("00 ",codelen)
CLEAR
? "Each sequence may be up to " + LTRIM(STR(codelen,2,0))
?? " characters long if needed."
? "You must use the ASCII representation or decimal equivalent of the byte "
? "to be sent to the printer."
? "Non-displayable characters such as CHR(15) or Escape must be encased in "
? "soft brackets, and consist of 3 digits. "
? "EXAMPLE: An asterisk may be entered as * or {042}, Escape as {027}, etc.. "
@ 7,0 to 7,79
@ 13,0 to 13,79
@ 19,0 to 19,79
DO WHILE .T.
   @ 8, 1 CLEAR TO 12,79
   @ 8, 1 SAY "0: "+S0+" 5: "+S5
   @ 9, 1 SAY "1: "+S1+" 6: "+S6
   @ 10,1 SAY "2: "+S2+" 7: "+S7
   @ 11,1 SAY "3: "+S3+" 8: "+S8
   @ 12,1 SAY "4: "+S4+" 9: "+S9
   @ 14,0 CLEAR TO 18,79
   @ 20,0 CLEAR TO 24,79
   code_num = SPACE(1)
   @ 20,0 SAY "Enter the number (0-9) to assign to this sequence."
   @ 21,0 SAY "Enter Q to quit...  "
   DO WHILE .NOT. code_num $ "0123456789Q"
      code_num = SPACE(1)
      @ 21,20 GET code_num;
              PICTURE "!"
      READ
   ENDDO
   IF code_num = "Q"
      EXIT
   ENDIF
   STORE s&code_num TO change
   @ 14,20 SAY "String Description:  " + s&code_num
   @ 16,1  SAY sc&code_num
   @ 21,0  SAY "Enter String Description"
   @ 22,0  GET s&code_num
   READ
   @ 23,0 SAY "Do you want to change this control codes sequence [Y/N]";  
          GET choice;
          PICTURE "Y"
   READ
   IF UPPER(choice) = "N"
      LOOP
   ENDIF
   @ 21,0 CLEAR
   @ 21,0 SAY "Enter Printer Code Sequence " + REPLICATE(space(1),30)
   ACCEPT "->  " TO p_string
   sc&code_num = p_string
   pstring_len = LEN(p_string)
   counter     = 1
   xtra_count  = 0
   hex         = "0123456789ABCDEF"
   max_count   = 0
   endstring   = ""
   DO WHILE counter <= pstring_len
      nextchar = STR(ASC(SUBSTR(p_string, counter, 1)), 3, 0)
      IF nextchar = "123"
         IF STR(ASC(SUBSTR(p_string, counter + 4, 1)), 3, 0) = "125"
            nextchar   = SUBSTR(p_string, counter + 1, 3)
            counter    = counter + 4
            xtra_count = xtra_count + 4
         ENDIF
      ENDIF
      temp_c = SUBSTR(hex, INT(VAL(nextchar) / 16) + 1, 1) +;
               SUBSTR(hex, MOD(VAL(nextchar),  16) + 1, 1)
      endstring = endstring + " " + temp_c
      counter   = counter + 1
      max_count = max_count + 1
      IF max_count > 26
         ? CHR(7)
         @ 20,0 CLEAR TO 23,79
         @ 21,8 SAY "The string has exceeded the 26 byte " + "limit... Please Re-Enter"
         ?
         WAIT
         LOOP
      ENDIF
      @ 22 + IIF(counter > 77, 1, 0), (counter - IIF(counter > 77, 80, 0)) + 2;
             SAY "."
   ENDDO
   pad        = " " + SUBSTR(full_pad,1,3 * (codelen +  xtra_count - pstring_len))
   m_location = STR(VAL(code_num) * codelen + table, 3, 0)
   mlocation1 = SUBSTR(hex, INT(VAL(m_location) / 16) + 1, 1) +;
                SUBSTR(hex, MOD(VAL(m_location), 16) + 1, 1)
   m_location = STR(VAL(code_num) * codelen + table + 15, 3, 0)
   mlocation2 = SUBSTR(hex, INT(VAL(m_location) / 16) + 1, 1) +;
                SUBSTR(hex, MOD(VAL(m_location), 16) + 1, 1)
   newstring1 = SUBSTR(endstring + pad,  1, 45)
   newstring2 = SUBSTR(endstring + pad, 46, 45)
   SET CONSOLE OFF
   SET ALTERNATE ON
   ? "E1" + mlocation1 + newstring1
   ? "E1" + mlocation2 + newstring2
   SET ALTERNATE OFF
   SET CONSOLE ON
ENDDO
SAVE ALL LIKE S* TO Codes
SET CONSOLE OFF
SET ALTERNATE ON
? "W"
? "Q"
?
CLOSE ALTERNATE
SET CONSOLE ON
CLEAR
verify = "N"
? "NOTE: This program assumes that Escape Sequences were entered correctly!"
?
? "   Proceeding will write only new sequences to "+UPPER("&infile")+" Module"
? "   and leave non-selected sequences intact."
?
@ 8,0 SAY "Enter [Y/N] to continue... "
@ 8,32 GET verify;
       PICTURE "Y"
READ
IF UPPER(verify)="Y"
   CLEAR
   @ 1,1 SAY "Updating " + UPPER("&infile")
   SET CONSOLE OFF
   RUN DEBUG &infile < Prtcodes.TXT
   SET CONSOLE ON
   LOAD &infile
   DELETE FILE Prtcodes.TXT
ENDIF
CLEAR
SET STATUS ON
SET SAFETY ON
SET TALK ON
SET BELL ON
* EOP Patchprt.PRG

 