USING PRINTER DRIVERS
Many applications require the use of two printers attached to one
workstation. Commonly, preprinted forms or labels will be loaded in one
printer, and blank paper or letterhead will be loaded in the other. Since
DOS allows the setup of multiple printers as LPT1, LPT2, LPT3, etc., the
application developer can formulate a methodology to print the proper
information to the proper printer.
This has been a challenge for both Revelation and Advanced Revelation
application developers. The goal of this bulletin is to provide an easy and
efficient solution to this problem.
Using PDISK to Redirect Output
PDISK is a utility used in Advanced Revelation to redirect printer output to
a file. PDISK has two components:
1) PDISK, the R/BASIC user interface.
2) SETPTR, the assembly language code that does printer redirection.
The standard PDISK user interface can be used to select a printer device by
entering the following command at TCL or via a PERFORM or EXECUTE from an
PDISK will then check to see if LPT1 exists. Since LPT1 is a DOS device,
PDISK will find it and ask if LPT1 should be overwritten. Respond "Yes" and
PDISK will redirect printer output to LPT1, or the specified line printer.
This approach requires no R/BASIC code, but is cumbersome as it requires the
user to respond each time the printer is redirected.
Calling SETPTR Directly
Because SETPTR is stored in the VERBS file, it can be called directly,
avoiding the need for user interaction. The calling parameters are:
DEVICE the device name in an ASCIIZ string (terminated with an ASCII
ACTION.FLAG when set true, this flag tells SETPTR to overwrite the
The program fragment illustrated in Figure 1 will accomplish the same as the
previous PDISK command without user intervention.
A generic subroutine callable from any program is illustrated in Figure 2.
This routine could be called from any R/BASIC program by cataloging it in
the VOC file or copying it to the VERBS file.
DECLARE SUBROUTINE SETPTR
DEVICE = 'LPT1':\00\ ;* load device, terminate with char(0)
ACTION.FLAG = 1 ;* overwrite device handle
SETPTR(DEVICE,ACTION.FLAG) ;* redirect
DECLARE SUBROUTINE SETPTR,MSG
* DEVICE = name of printer: LPT1, LPT2
* FLAG = return status: 1 if good redirection, 0 if problem
FLAG = 0 ;* set flag to false
* check device to see if it is a print device
IF DEVICE[1,3] = 'LPT' OR DEVICE[1,3] = 'PRN' THEN
* check last char of device to see if it is char(0)
DEVICE = TRIMB(DEVICE)
IF DEVICE[-1,1] NE CHAR(0) THEN
* add char(0) if not there
DEVICE = TRIMB(DEVICE):CHAR(0)
* set action flag to overwrite file/device.
ACTION.FLAG = 1
* call routine to redirect printer
CASE ACTION.FLAG = 21
* good redirection tell calling program
FLAG = 1
CASE ACTION.FLAG < 20
MSG('DOS error ':ACTION.FLAG,'','','')
CASE ACTION.FLAG <> 21
MSG('Undefined Error ':ACTION.FLAG,'','','')
When writing programs that will direct output to a printer, it is desirable
to know whether or not the printer is ready. Until Version 1.1 of Advanced
Revelation, this was a difficult task from within a program. There is now a
system subroutine, PRNSTAT, that will return the printer's status.
PRNSTAT takes one argument in which the status of the printer is returned.
PRNSTAT can be used as follows:
DECLARE SUBROUTINE PRNSTAT
PRINTER.STATUS = ''
IF PRINTER.STATUS THEN
* there was a problem
* error handling goes here
WHILE PRINTER.STATUS REPEAT
PRINTER.STATUS will have one of four values:
0 Printer ok
1 Printer not selected (or not connected)
2 Printer out of paper (or not connected)
3 I/O or timeout error
In order to implement PRNSTAT, changes to the AREV.EXE were required.
PRNSTAT will not work in releases prior to 1.1. Further changes to AREV.EXE
are required to make PRNSTAT recognize PDISK. As of Version 1.13, PRNSTAT
will always look for a physical printer, even if the output has been
redirected to a print file via PDISK.
Advanced Revelation 2.1 introduces printer drivers into the product. This
Technical Bulletin gives you a technical overview of how the printer drivers
are implemented and provides documentation on system subroutines you may
Overview of Escape Sequences
Virtually all printers offer a variety of print styles and formats. For
example, most offer both a "normal" and a "bold" weight, and
different styles such as italic or double-width. Many printers also offer a
variety of fonts, whether in the form of "draft" and "near-letter
(for dot matrix printers) or recognized typefaces such as "serif"
form of Roman) and "sans serif" (popularly Helvetica or Swiss).
In almost all printers, you can control how and when a printer uses these
different styles and typefaces by issuing "escape sequences" to the
Escape sequences for printers work much the same as they do for controlling
screen attributes (see Technical Bulletin #4) certain sequences of
characters (often preceded by an "escape" (ASCII character 27), hence
name) are interpreted by the printer not as text to be printed, but as
commands. As a simple example, in the escape-sequence "language"
by many Hewlett-Packard printers, the command to turn on bold printing is:
To send an escape sequence to your printer, you simply print it as you would
any other string of characters. When the printer receives these characters,
it recognizes the command and reacts accordingly.
Printer Drivers in Advanced Revelation Advanced Revelation Version 2.1 lets
you create tables of these escape sequences and then use them automatically
in your reporting. You can take advantage of these printer drivers in three
1) Establishing a default printer; in this case, all output to the
printer will conform to the "normal" attributes of that printer.
2) Specifying character formatting flags in Merge reports.
3) Printing printer codes from within an R/BASIC program.
This technical bulletin concentrates on the last option, because
that method provides the developer with the most control over printed
Using Printer Drivers in Advanced Revelation
There are two issues in controlling your printer using escape sequences.
The first is simply knowing what commands your printer understands and
generating the appropriate escape sequences. For example, while you may
understand that your printer can print in boldface, you will need to
identify (and keep handy) the escape sequence required to turn boldface on
and back off again when you want to resume normal printing.
The other issue is that of making your printing generic, that is, applicable
to more than one printer. You may have multiple printers, or you may be
creating an application to run in environments where the users can choose
their own printers. In both cases, you need to be able to switch easily from
printer to printer, preferably without having to recode, or even recompile,
Advanced Revelation's printer drivers keep you from having to worry about
what kind of printer is being used. Rather than hard-coding printer control
characters into your program you use the @ function in R/BASIC:
When the program is executed, the system automatically identifies ans sends
the escape sequence required by the current printer. Your programs neither
need to include the escape sequence that turns bold on, nor need to know
which printer is active.
Printing Escape Sequences
There are two parts to getting the proper output on paper. The first is the
@ function. The second is the printer table.
The @ Function and Generic
Escape Codes The @ function is used to control special output characters.
When you pass a negative number to the function, the system generates a
so-called "generic escape code" (GEC). The GEC takes the form:
where [Esc] is a CHAR(27), G is the literal "G" and xx is a two byte
For example, the function @(-50) produces the GEC:
This GEC is not specific to any one printer; it is, as implied by its name,
a generic code. There is one GEC (and by implication, a different negative
value) for each of the printer controls that Advanced Revelation understands
(e.g. "start bold printing", "end bold printing", etc.). For
Value Code Meaning
-50 [Esc]G2:CHAR(1) start bold
-51 [Esc]G":CHAR(1) stop bold
-52 [Esc]G2:CHAR(2) start italics
-53 [Esc]G":CHAR(2) stop italics (etc.)
To make it easier to use the @ function, an insert record is provided in
the INCLUDE file. PRINT_CONSTANTS maintains equated values for each of the
negative values, so, for example, you don't have to remember that -50 is
the code to turn on bold.
If a printer driver is active, the R/BASIC command PRINT monitors the data
being printed for the first two GEC characters ([Esc]+G). When these
characters are encountered:
They are removed from the output stream, as are the next two characters.
The two characters following the [Esc-G] are used to look up the
appropriate printer control information in a table maintained in memory.
The control information is then sent to the output device in place of the
[Esc-G] sequence. In other words, the PRINT command swaps the GEC for the
corresponding escape sequence for the current printer. Note that this
substitution is done at the moment the data is being printed.
For example,if your printer driver defines [Esc](s0B (a Hewlett Packard
code) as the sequence to turn bold on, the following steps are taken.
First, you write your program:
$INSERT INCLUDE, PRINT_CONSTANTS
PRINT @(BOLDON$): "BOLD TEXT"
When the program is executed, the @ function, generates an escape sequence
based on the value of BOLDON$, -50:
Then when the PRINT statement sees this [Esc]Gxx string it substitutes:
which is what a Hewlett Packard printer needs to start printing bold.
Note: The PRINT command simply scans the data for [Esc-G]. It does not
know how the [Esc-G] sequence was generated. In most cases, the only way
[Esc-G] is generated is via the @ function. However, if you are printing
data that may contain [Esc-G], for example, graphical data, you should
turn off the printer driver before printing your data. See DRIVER_CONTROL
below to see how this is done.
You can access and modify the tables that store the printer control codes
through the Management-Hardware-Printer-Definitions menu.
Filling out the table, either to create a new printer definition or to
modify an existing one, is a matter of putting the proper escape sequences
in the proper locations. See your printer manual for the codes and format
Some of the prompts in the window bear special mention.
In the Printer Control section of the window is a prompt labelled
Initialize. The values entered here will be sent to the printer whenever
the PRNINIT$ code is encountered.
Initialize is used to return the printer to a known state. Most programs
that print to the printer start off by initializing the printer. The system
does not automatically send the initialize code to the printer when the
printer driver is activated. You must do this within your program or
through the Printer Processes Open code and command. The one exception is
the Merge processor, which will indeed issue a "printer initialize"
sequence before beginning a print run.
On the last page of the definition window are prompts for Printer
Processes. There is a code and command sequence for opening the print
process, and another for closing the printer process.
The Open print process is executed whenever the printer driver is activated.
* When that printer driver is selected.
* At login.
* When a PRINTPROC OPEN command is executed from TCL.
* From a TCL first-level reset.
The Close print process, on the other hand, is executed:
* When the printer driver is deselected (ie, when a new printer is selected)
* At logoff.
* When a PRINTPROC CLOSE is executed at TCL.
The processing that happens from the code and command can be anything you
need. For example, you may wish to put your printer in landscape mode or
you may want to execute the Initialize code automatically. Figure 1 is a
program that can be used with any printer driver to execute the initialize
Prelude and End of Job
These prompts are intended to be used with PostScript printer drivers. The
only printer driver shipped with Advanced Revelation that uses these prompts
is the PostScript driver.
To use these prompts, you need to either write supporting routines or call
PSSUBS and PSPRINT. PSSUBS controls the print process Open and Close
features. PSPRINT formats the page and sends it to the printer.
Note: Do not make changes to the Prelude provided with the Advanced
Revelation PostScript driver without first making a backup. For more
information about the use of PSPRINT, see Technical Bulletin 99.
Determining the Active Printer
There are several ways to determine which printer driver is active. The
preferred method is to use the system function GETCONFIGURE. Field 28 of
the returned record is the name of the active printer, while Field 29 is a
list of printers defined in the environment. The following code fragment
shows how GETCONFIGURE is used.
DECLARE FUNCTION GETCONFIGURE
ERROR = GETCONFIGURE(INF )
PRINTER_NAME = INFO<28>
PRINTERS = INFO<29>
This will return (by name) the currently active printer, in the form of the
record key to the table in the PRINTER_CONFIG file. The information returned
by GETCONFIGURE is in part based on information maintained in @ENVIRON.SET.
Field 92 of @ENVIRON.SET is a value mark-delimited list of printers. Field
93 is an associated list of printer ports.
The active printer can also be determined by calling SET_ACTIVE_PRINTER
with a mode of 3. See below for details.
Changing the Active Printer
The active printer is usually changed by using the TCL command SETPRINTER
or via the Environment menu options. Both methods present a popup of
available printers (those currently in @ENVIRON.SET<92>) and let you to
select the one to activate.
You may want to change the active printer driver from within a program. To
do so, you must update fields 92 and 93 of @ENVIRON.SET and then call
SET_ACTIVE_PRINTER with a mode of 1 and the ordinal number of the printer.
See SET_ACTIVE_PRINTER below for details.
You can also control whether a printer driver is currently active. For
example, if you are printing graphical data that may coincidentally contain
escape sequences, you'll want to deactivate the printer driver first.
DRIVER_CONTROL is a system subroutine that controls whether the printer
drivers are active. It takes one parameter, a code indicating what action
3 Disable Video
4 Disable Print
1 - Enable
A code of 1 indicates that the printer drivers should be enabled.
Enabling the drivers does not load the driver tables. It is assumed that
the tables are already in memory.
2 - Disable A code of 2 indicates that both the print and video drivers
should be disabled. Disabling a driver does not remove the associated
table from memory.
3 - Disable Video A code of 3 indicates that only the video driver should
be disabled. The printer driver remains active.
4 - Disable Print A code of 4 indicates that the printer driver, but not
the video, should be disabled. The printer driver should be disabled
when you want to print data (for example, graphics) that may contain
escape sequences that should not be interpreted by the driver.
5 - Initialize
A code of 5 indicates that the printer tables should be initialized.
Both the display and the printer drivers for the last active printer
are loaded from disk. To change which print driver gets loaded, use
6 - Flush Use a code of 6 to flush the driver tables from memory. This is
useful for freeing memory in tight memory conditions. You should
disable the printer drivers when you flush the tables. When you are
ready to use the drivers again, you must enable the drivers (using code
1) and load the driver tables (using code 5).
The following code fragments show how DRIVER_CONTROL can be used in your
DECLARE SUBROUTINE DRIVER_CONTROL
EQU ENABLE$ TO 1
EQU DISABLE$ TO 2
EQU DISABLE_VID$ TO 3
EQU DISABLE_PRINT$ TO 4
EQU INIT$ TO 5
EQU FLUSH$ TO 6
In this example the printer driver is turned off before printing a graphics
image and then turned on after the image is printed.
* (initialization as above)
/* do processing */
This example shows how the drivers are turned off, and the tables flushed
from memory and, after the memory intensive processing has completed, how
the drivers are again enabled and loaded.
There is also a system subroutine to set the currently active printer.
SET_ACTIVE_PRINTER lets you assign a new printer automatically or manually,
and can be used to determine which printer is active. SET_ACTIVE_PRINTER
takes three parameters: MODE, PRN_NUM, and STATUS.
MODE indicates the action to be performed. There are three modes:
1 Automatic Selection
2 Manual Selection
A MODE of 1 activates the printer specified in PRN_NUM.
A MODE of 2 displays a popup of available printer drivers. The value
returned by the popup indicates the printer number to activate.
A MODE of 3 indicates that you want to find out which printer driver is
When MODE is 1, PRN_NUM is an integer indicating which printer driver to
activate. The integer is the ordinal value of the printers in field 92 of
@ENVIRON.SET. For example, if you pass PRN_NUM as 1, the first printer
driver in field 92 of @ENVIRON.SET is activated.
When MODE is 3, PRN_NUM is returned as a two element dynamic array.
Field 1 of the array is an integer representing the ordinal value of the
current printer from those in field 92 of @ENVIRON.SET.
Note: If @ENVIRON.SET is changed after the printer driver is activated,
PRN_NUM may not point to the name of the active printer; its value will only
be updated during the next call to SET_ACTIVE_PRINTER. Do not rely on the
position of PRN_NUM after you update @ENVIRON.SET.
Field 2 is a flag indicating whether the current printer has an Open code
and command defined, and whether it has been executed.
For all modes, STATUS is returned true if the operation succeeded, false
Frequent Printer Driver Questions
The printer driver support in Advanced Revelation has spawned a
certain number of questions. A sampling from the more common ones in
Technical Support are addressed below.
How can I print in Landscape mode on a laser printer?
There are no explicit commands within the defined range of printer
controls (e.g. BOLDON$, etc.) for orientation. However, you might try
two strategies. The first is to use otherwise unused or little-used font
controls as orientation control sequences. For example, you might define
"Font 15" not to be a font at all, but to send to the printer the
sequence required to switch to landscape, and "Font 16" as the
required for portrait mode.
Alternatively, you can define two different printer tables, one for
portrait and another for landscape. In that case, to print in landscape
mode, for example, you would choose the landscape version of the printer
You would need to be sure to download the appropriate orientation sequences
to the printer when the printer driver is initialized, by either explicitly
coding them into the "Open" code and command, or by embedding them as
Initialization code and making sure that this is downloaded to the printer
when the printer is selected. (See "Printer Processes" under
Can I control fonts in R/LIST, etc.?
As noted earlier, the most direct control over printers is via R/BASIC
and to a lesser extent, from Merge. For other processes such as R/LIST you
can establish a current printer, and all output will be sent in the default
font characteristics of that printer. For example, if you choose a printer
and then print an R/LIST report, the report will be sent appropriately to
that printer, but will not otherwise (by default) allow any formatting of
the data on the report.
Within certain limits, you can create symbolic dictionary items whose
definition is simply the appropriate escape sequence, and then intersperse
these "fields" in your report as required. For example, you might
(in the DICT of VOC) the fields BOLDON and BOLDOFF, whose definitions are,
/* BOLDON definition */
$INSERT INCLUDE, PRINT_CONSTANTS
@ANS = @(BOLDON$)
/* BOLDOFF definition */
$INSERT INCLUDE, PRINT_CONSTANTS
@ANS = @(BOLDOFF$)
This would let you to execute an R/LIST statement such as this one:
LIST SAMPLE_CUSTOMERS COMPANY_NAME BOLDON CITY BOLDOFF
The problem with this approach is the space required to "print" thes
two fields in the report. You must make the field at least four characters
wide. When R/LIST formats the field, it will truncate it at the display
length, not realizing that the contents of these particular fields are
not meant to appear, just be used to control the printer. Truncating
escape sequences before sending them to the printer results in
unpredictable, and usually undesired, behavior. This truncation behavior is
the norm for most output in the system (as, for example, in displaying such
fields in an entry window), making it difficult to send printer or video
control codes from within these higher-level processes.
Can I print using proportional typefaces?
It is quite possible to set up a proportional font such as Roman or
Helvetica within the printer tables. However, Advanced Revelation continues
to format using monospacing. For example, columns in R/LIST reports are
aligned using embedded spaces. If you select a proportional font as the
current font for your printer and then print an R/LIST (or other high-level)
report, the columns will not align properly because of the varying widths
of the characters in the report.
Can I have more than three printers available?
Yes. You can modify the PRINTER_SELECT window as described in Technical
Bulletin #85, and change the row limit for that prompt.
This window simply updates the environment variable @ENVIRON.SET, although
not immediately. You can therefore also update field 92 by adding printers
(using @VM as a delimiter) to the list there, and adding the appropriate
printer port information to field 93.
Can I control fonts in PostScript?
Yes. The PostScript driver supports, in addition to Courier, the fonts
Times-Roman, New Century Schoolbook, and Helvetica. See the Postscript
driver definition (in the Printer Definitions window) to see which @-codes
activate which fonts. Be aware, however, of the limitations of using
proportional typefaces (see above).
Changing the fonts supported by the PostScript driver entails changing the
Prelude and is beyond the scope of this Technical Bulletin.
/* Call as the Print Process Open code and command: code - S command -
PRINTINIT This program sends the PRNINIT$ code defined in the current
printer driver to the printer.
$insert include, print_constants
hold_printer = getprinter() ;* save off the curent state of the printer
printer on ;* turn the printer on
print @(prninit$): ;* print the initialize code
printer off ;* turn the printer off
printer hold_printer ;* return the printer to the way it was found