Creating dBASE III PLUS Printer Drivers BY KENT IRWIN One of the keys to effective application development is modularity. Any system that is built from smaller, interchangeable modules is easier to assemble, modify, and maintain. Additionally, some modules can be used without modification in other applications, saving you programming time. Some common tasks among application systems are printer and screen setups, file maintenance routines, and menu structures. To increase your programming efficiency, you can create and use these commonly used modules as standard procedures in your development library. The printer setup routine in this article is one such module. It creates printer drivers for dBASE III PLUS containing the printer codes needed to access special print attributes such as bold, underline, and compressed print. Each driver is really a memory file (.MEM file) consisting of one memory variable for each print attribute setting. The program, PrtSetup.PRG, prompts you for the print attribute settings as a series of numeric code lists. After you enter them, PrtSetup then saves the settings to the .MEM file. Figure 1 below shows the dictionary of variable names and their descriptions. Variable Name Description Variable Name Description p_name Print driver filename p_ital_on Italic On p_six Six Lines/Inch p_ital_off Italic Off p_eight Eight Lines/Inch p_emph_on Emphasized On p_10cpi Ten cpi p_emph_off Emphasized Off p_12cpi Twelve cpi p_dbl_on Double-Strike On p_compr_on Compressed On p_dbl_off Double-Strike Off p_compr_off Compressed Off p_expn_on Expanded On p_nlq_on Letter Quality On p_expn_off Expanded Off p_nlq_off Letter Quality Off p_supr_on Superscript On p_bold_on Bold On p_supr_off Superscript Off p_bold_off Bold Off p_sub_on Subscript On p_und_on Underline On p_sub_off Subscript Off p_und_off Underline Off Figure 1. Whenever your programs need to print, load the print attribute .MEM file and use the printer variables to set special print attributes in your output. If you use several different printers, you can create a driver for each one. Since all drivers use the same variable names, you can print on several different printers and get the same print attributes by simply loading a different .MEM file. Usage ----- The printer driver system consists of the setup program, PrtSetup.PRG, and .MEM files created by PrtSetup. With PrtSetup, you create new or edit existing printer driver files. When you run PrtSetup, it prompts you for a .MEM filename and either loads an existing .MEM file or initializes variables to create one. If you are creating a new .MEM file, PrtSetup prompts you to enter the printer control codes as ASCII decimal numbers separated by commas. Figure 2 shows the entry form that PrtSetup uses with values entered for a Toshiba P351 printer. Once the codes are entered, the procedure Convert changes the decimal numbers to their literal characters and saves the .MEM file to disk. If the .MEM file you specify exists, PrtSetup calls the procedure Dconvert to change the ASCII characters back to their decimal values. Once you make your edits, PrtSetup runs Convert again to change the decimal values back to literal characters before saving them to disk. Figure 2. (screen shot of PrtSetup form) PrtSetup.PRG calls itself as a procedure file incorporating two procedures: Convert and Dconvert. This is done to make the operation of the program run a little faster. The program, PrtTest.PRG, prints a test pattern of the print attributes in order to verify the codes you entered. It also demonstrates how to embed the print variables in your programs. As the program code shows, the print variables are concatenated to the string being output. For example, to print a bolded string with an @...SAY command you would syntax like the following: @ , SAY p_bold_on + "This is bold print" +; p_bold_off You can of course use these print variables with @...SAY, ?, REPORT FORM, and LABEL FORM commands. Setup ----- To set up the program, enter the code for both PrtSetup.PRG and PrtTest.PRG using MODIFY COMMAND or any text editor that generates standard ASCII text files. To execute the program, type, DO PrtSetup from the dBASE III PLUS dot prompt. Special Considerations ---------------------- When you use this system, there are several considerations to be aware of. First, as a general principle, when you turn on an attribute, be sure also to turn it off. Second, dBASE III PLUS always counts printer control codes the same as all other characters but your printer does not print them. This can cause alignment problems with @...SAY and REPORT FORM. If, for example, you print a line with several @...SAYs and change the print attribute of any of the display values, all the values to the right of the print attribute change are offset to the left by the number of characters that constitute your print attribute string. In normal circumstances, you would likely compensate by hardcoding a correcting offset. This is not, however, an appropriate solution for a system which seeks to make your print attributes device independent. You must account for variable circumstance. The answer is in fact quite easy. Add to the tail of any @...SAY command that turns a print attribute on and/or off the following expression: SPACE(LEN( + )) This pads the @...SAY display value with a number of spaces lost by the nonprintable characters dBASE III PLUS treated as normal. The following command line shows the example above with the space padding expression: @ , SAY p_bold_on + "This is bold print" +; p_bold_off + SPACE(LEN(p_bold_on +; p_bold_off)) Another problem to be aware of is the inability of dBASE III PLUS to send an ASCII 0 (CHR(0)) to the printer. Many printers use a command string ending with a CHR(0) to toggle a setting on or off. So, you can only send this character by first telling the printer to ignore the eighth bit and then sending a CHR(128) instead. Some printers allow the eighth bit to be ignored, other do not. If you must send a CHR(0) to your printer and your printer supports ignoring the eighth bit (called MSB, for Most Significant Bit, by Epson), then fill in the correct codes to enable and disable this in the "No 8th Bit" fields. If PrtSetup finds codes in both "No 8th Bit" fields, then it looks for CHR(0) in your codes and rewrites that code string for you. It will precede your character string with the codes to disregard the eighth bit, then replace any CHR(0) with CHR(128), then follow the string with the codes to reset noticing the eighth bit. Please note that many printers support both the code or character (CHR(0) or "0") interchangeably. If your printer does not include a code to ignore the MSB, try using the character zero (CHR(48)) rather than CHR(0). It may still work. Program Code ------------ * Program...: PrtSetup.PRG * Author....: Kent Irwin * Date......: May 1, 1987 * Version...: dBASE III PLUS * Note(s)...: Program to store printer control codes to a memory file. * * SET TALK OFF SET BELL OFF SET PROCEDURE TO PrtSetup blanks = SPACE(30) row = 24 STORE blanks TO p_six, p_eight, p_10cpi, p_12cpi, p_name, p_cmpr_on,; p_cmpr_off, p_nlq_on, p_nlq_off, p_bold_on, p_bold_off,; p_und_on, p_und_off, p_ital_on, p_ital_off, p_emph_on STORE blanks TO p_emph_off, p_dbl_on, p_dbl_off, p_expn_on, p_expn_off,; p_supr_on, p_supr_off, p_sub_on, p_sub_off, p_msb_on,p_msb_off CLEAR f_name = SPACE(8) @ 10, 15 SAY "Name of Setup .MEM file"; GET f_name; PICTURE "@!" READ IF AT(".MEM", f_name) = 0 IF AT(".", f_name) <> 0 f_name = LEFT(f_name, AT(".", f_name) - 1) ENDIF f_name = LEFT(f_name, MIN(8, LEN(TRIM(f_name)))) + ".MEM" ENDIF IF FILE(f_name) CLEAR @ row, 20 SAY "Loading, please wait..." column = COL() @ row, column + 1 SAY REPLICATE(".", 20) offset = 0 RESTORE FROM &f_name ADDITIVE DO Dconvert WITH p_six DO Dconvert WITH p_cmpr_on DO Dconvert WITH p_eight DO Dconvert WITH p_10cpi DO Dconvert WITH p_12cpi DO Dconvert WITH p_cmpr_off DO Dconvert WITH p_nlq_ON DO Dconvert WITH p_nlq_OFF DO Dconvert WITH p_bold_on DO Dconvert WITH p_bold_off DO Dconvert WITH p_und_on DO Dconvert WITH p_und_off DO Dconvert WITH p_ital_on DO Dconvert WITH p_ital_off DO Dconvert WITH p_emph_on DO Dconvert WITH p_emph_off DO Dconvert WITH p_dbl_on DO Dconvert WITH p_dbl_off DO Dconvert WITH p_expn_on DO Dconvert WITH p_expn_off DO Dconvert WITH p_supr_ON DO Dconvert WITH p_supr_off DO Dconvert WITH p_sub_on DO Dconvert WITH p_sub_off DO Dconvert WITH p_msb_on DO Dconvert WITH p_msb_off @ 24, 0 ENDIF CLEAR @ 1, 28 SAY "Printer Setup Program" @ 3, 12 SAY "Printer Name" @ 4, 10 SAY "Six Lines/Inch" @ 5, 8 SAY "Eight Lines/Inch" @ 6, 17 SAY "Ten CPI" @ 7, 14 SAY "Twelve CPI" @ 9, 28 SAY "ON" @ 9, 62 SAY "OFF" @ 10, 4 SAY "Compressed" @ 11, 0 SAY "Letter Quality" @ 12, 10 SAY "Bold" @ 13, 5 SAY "Underline" @ 14, 8 SAY "Italic" @ 15, 4 SAY "Emphasized" @ 16, 1 SAY "Double-Strike" @ 17, 6 SAY "Expanded" @ 18, 3 SAY "Superscript" @ 19, 5 SAY "Subscript" @ 20, 4 SAY "No 8th bit" @ 24, 15 SAY "Press Ctrl-Q to quit, or Ctrl-W to Save." * @ 3, 25 GET p_name @ 4, 25 GET p_six @ 5, 25 GET p_eight @ 6, 25 GET p_10cpi @ 7, 25 GET p_12cpi @ 10, 15 GET p_cmpr_on @ 10, 49 GET p_cmpr_off @ 11, 15 GET p_nlq_ON @ 11, 49 GET p_nlq_OFF @ 12, 15 GET p_bold_on @ 12, 49 GET p_bold_off @ 13, 15 GET p_und_on @ 13, 49 GET p_und_off @ 14, 15 GET p_ital_on @ 14, 49 GET p_ital_off @ 15, 15 GET p_emph_on @ 15, 49 GET p_emph_off @ 16, 15 GET p_dbl_on @ 16, 49 GET p_dbl_off @ 17, 15 GET p_expn_on @ 17, 49 GET p_expn_off @ 18, 15 GET p_supr_ON @ 18, 49 GET p_supr_off @ 19, 15 GET p_sub_on @ 19, 49 GET p_sub_off @ 20, 15 GET p_msb_on @ 20, 49 GET p_msb_off READ CLEAR choice = " " @ 12, 15 SAY "Save This Setup File?"; GET choice; PICTURE "@!" READ CLEAR IF UPPER(choice) <> "Y" CLOSE PROCEDURE SET TALK ON RETURN ENDIF @ row, 20 SAY "Now processing " column = COL() offset = 0 x_msb_flag = .F. @ row, column + 1 SAY REPLICATE(".", 20) DO Convert WITH p_msb_on DO Convert WITH p_msb_off x_msb_flag = (LEN(TRIM(p_msb_on)) <> 0 .AND. LEN(TRIM(p_msb_off)) <> 0) DO Convert WITH p_six DO Convert WITH p_cmpr_on DO Convert WITH p_eight DO Convert WITH p_10cpi DO Convert WITH p_12cpi DO Convert WITH p_cmpr_off DO Convert WITH p_nlq_ON DO Convert WITH p_nlq_OFF DO Convert WITH p_bold_on DO Convert WITH p_bold_off DO Convert WITH p_und_on DO Convert WITH p_und_off DO Convert WITH p_ital_on DO Convert WITH p_ital_off DO Convert WITH p_emph_on DO Convert WITH p_emph_off DO Convert WITH p_dbl_on DO Convert WITH p_dbl_off DO Convert WITH p_expn_on DO Convert WITH p_expn_off DO Convert WITH p_supr_ON DO Convert WITH p_supr_off DO Convert WITH p_sub_on DO Convert WITH p_sub_off @ 24, 0 * SET SAFETY OFF SAVE TO &f_name ALL LIKE p_* SET SAFETY ON CLOSE PROCEDURE SET TALK ON RETURN * EOP PrtSetup.PRG PROCEDURE Dconvert PARAMETERS string * temp = 1 newstring = "" * * ---Comment out the next line to disable "walking arrow" and * ---speed up the operation. DO Walk WITH row, column, offset macro = "LTRIM(STR(ASC(SUBSTR(string, temp, 1)), 3))" DO WHILE temp <= LEN(string) newstring = IIF(LEN(newstring) <> 0, newstring + "," + ¯o, ¯o) temp = temp + 1 ENDDO newstring = LEFT(newstring + blanks, 30) string = newstring RETURN * EOP DConvert PROCEDURE Convert PARAMETERS string * string = TRIM(string) newstring = "" * * ---Comment out the next line to disable "walking arrow" and * ---speed up the operation. DO Walk WITH row, column, offset x_msb_ok = .F. pos = AT(",", SUBSTR(string, 1, LEN(string))) DO WHILE pos <> 0 char = SUBSTR(string, 1, pos - 1) x_msb_ok = x_msb_ok .OR. (x_msb_flag .AND. (char = "0")) newstring = newstring + IIF(x_msb_ok, CHR(128), CHR(VAL(char))) string = SUBSTR(string, pos + 1, LEN(string) - pos + 1) pos = AT("," , string) ENDDO x_msb_ok = x_msb_ok .OR. (x_msb_flag .AND. (string = "0")) newstring = newstring + IIF(x_msb_ok, CHR(128), CHR(VAL(string))) IF x_msb_ok string = p_msb_on + newstring + p_msb_off ELSE string = newstring ENDIF RETURN * EOP Convert PROCEDURE Walk PARAMETERS row, column, offset * @ row, column + offset SAY "." offset = MOD(offset + 1, 20) @ row, column + offset SAY CHR(16) @ row, column + offset SAY SPACE(0) RETURN * EOP Walk * Program...: PrtTest.PRG * Author....: Kent Irwin * Date......: May 1, 1987 * Version...: dBASE III PLUS * Note(s)...: Printer driver demo program. * SET TALK OFF CLEAR f_name = SPACE(8) @ 10, 15 SAY "Name of Setup .MEM file"; GET f_name; PICTURE "@!" READ IF AT(".MEM", f_name) = 0 IF AT(".", f_name) <> 0 f_name = LEFT(f_name, AT(".", f_name) - 1) ENDIF f_name = LEFT(f_name, MIN(8, LEN(TRIM(f_name)))) + ".MEM" ENDIF RESTORE FROM &f_name @ 22, 0 SAY "Ready printer, then press any key to begin printing..." @ 23, 0 WAIT CLEAR SET PRINT ON ? "Printer driver filename: " + p_name ? p_cmpr_on + "Compressed" + p_cmpr_off ? p_nlq_on + "Letter Quality" + p_nlq_off ? p_bold_on + "Bold" + p_bold_off ? p_und_on + "Underline" + p_und_off ? p_ital_on + "Italic" + p_ital_off ? p_emph_on + "Emphasized" + p_emph_off ? p_dbl_on + "Double-Strike" + p_dbl_off ? p_expn_on + "Expanded" + p_expn_off ? p_supr_on + "Superscript" + p_supr_off ? p_sub_on + "Subscript" + p_sub_off SET PRINT OFF EJECT CLEAR ALL SET TALK ON * EOP PrtTest.PRG Summary ------- By using the printer driver memory files, you can enhance your reports with special print attributes without adding to programming time or sacrificing portability. Because you are using the same variable names for each print attribute, all you have to do is load the correct printer driver when you move from one printer to another. You can also use PrtSetup.PRG as a menu option within your applications, allowing users to create and edit printer drivers as needed.