Go to the previous, next section.
SM can use a single set of subroutine calls to plot on almost any terminal, and on many printers. The routines that it uses, called stdgraph, were originally taken from the IRAF GIO package written at Kitt Peak by Doug Tody @footnote #{Graphics I/O Design, Doug Tody, March 1985. NOAO (Kitt Peak)} and converted to C and partially re-written to be integrated into SM. Despite our extensive rewrite, these routines should probably still be considered to be in the public domain.
Stdgraph uses a file called a graphcap file to specify the properties of
terminals, in a way that is similar to the termcap facility of Unix. You
don't have to know anything about termcap to read this section; you don't
have to read this section unless you want to change the graphcap file
to add a new device, to fix a bug, or to change the way that SM treats
your plotting device. The name of the graphcap file is given by the variable
graphcap
in the environment file.
A list of files to be searched in order may be given instead of a single graphcap file (up to a current maximum of three). The usual way to accomplich this is to add an entry
+graphcap /u/rhl/sm/graphcapabove any other graphcap entries in your `.sm' file, which instructs SM to put `/u/rhl/sm/graphcap' first in the list of files, followed by any others that might appear, either in your file or in some other that the system provides (ask the person who installed SM where the default `.sm' file is; usually something like `/usr/local/lib/.sm').
A graphcap file is a way of describing a terminal in a concise way, so a
programme can discover which idiosyncrasies a terminal has without having
to be recompiled. A graphcap file consists of a number of entries, one for
each device supported, and to add a new terminal all that one has to do is
to add another entry.
It is also possible to define variables in graphcap files, which are used
in SY
entries.
You can compile selected entries in the graphcap file, so as to
improve access time for popular terminals. If this has been done, changing
the graphcap file for one of these terminals will have no effect until
it is recompiled, see section Compiling Graphcap for details.
For a list of all the capabilities that SM uses see the index to graphcap at the end of this appendix.
Some devices are not supported through stdgraph (graphics drawn to a
SunView window would be an example), but they still appear in graphcap
with a special entry (DV
) giving the name of the appropriate hard-coded
device driver.
Each entry consists of a name for the device, followed by a list of
aliases, followed by a list of fields, separated by colons. A \ may be used
to continue an entry onto the next line, and lines starting with a
#
are comments (comment lines are permitted both between and
within entries).
As a rather complex example,
the graphcap entry for a Tektronix 4012 reads:
tek4010|tek4012|TEK4010|TEK4012|Tektronix 4010/2:\ :ch#.0294:cw#.0125:co#80:li#35:xr#1024:yr#800:\ :MC=^M:CL=^[^L:CN#6:GD=^X:GE=^[1^]:\ :ML=^[(1$0)`($1)a($2)c($3)d($4)b($$:lt=01234:\ :OW=^]^_:RC=^[^Z:SC=(,!3, & *, &+!1, & *, &+!2:\ :TB=^]%t^_:VS=^]:\ :xr#1024:XY=%t:yr#780:This is one of the longest entries in the graphcap file - all of the terminals which are Tektronix emulators explicitly include this entry, so they only need provide the capabilities that are different from the Tektronix. As an example, the entry for a Pericom reads
pericom|Pericom:\ :GE=^]:TB=^](2#7-!2)%t^_:\ :tc=tek4012:The
|
separate the aliases, and the final field tc=tek4012
tells
stdgraph to take all other fields from the entry for tek4012
, given
above. If you have specified a list of graphcap files, each will be
searched in order for each :tc=
continuation. If you don't want the
search to begin again use TC
, e.g.
graphon|Graphon which claims to support lw:\ :LW=:TC=graphon:if you had used
:tc=graphon:
this would have been recursive and
illegal, but as TC
doesn't restart the search it merely has the
effect of adding (or in general, replacing) an capability in a
preexisting graphcap entry.
Control characters are entered as ^A, ^B, and so on (those
are two characters, ^ and `A'). `Escape' may
be represented as ^[, \E, or in octal as \033. Because the
normal way of handling strings in C treats \0
as meaning
`end of string' you can't simply put a \000
into a graphcap
entry, instead write \377
and SM'll interpret it as \0
. (If
you need a real \377
enter \377\377
).
If a delay of so many
milliseconds is required before the transmission of a string, it is given
first (followed by a *
if it is to be applied to each line
affected). This leads to problems with graphcap entries that start with
numbers, you must precede them with a space or (if the string is run through
the encoder) insert a no-op e.g. :CP=()1000:
.
Numerical values are preceded by a #, so :co#80:
means that co
(the number of columns
displayed) is 80, while :MC=^M:
means that MC (the cursor
delimiter) consists of the character ^M. This could
just as well have been written :MC=\010:
. If the first character of
a capability is `@', it specifies that that capability is not present for that
terminal (e.g. :lt@=1234:
specifies that lt
is not defined).
A field may simply not be provided if it is irrelevant, although in this
case it may be supplied by a tc
or TC
continuation.
A common set of graphcap entries to `comment out' are TB
and
TE
, which deal with hardware character sets. If you don't want
your plotter to use it's internal fonts simply insert `@' before the `='.
By inserting their private file before the system one in the list of
graphcap files, users can tailor the entries to their liking.
We use a subset of the graphcap capabilities defined by the IRAF group, and the
distinction between upper and lower case parameters comes from them. In a few
cases our usage is different from theirs, in these cases we have
specified our own capabilities
(CD
MC
,
DD
SY
,
LT
ML
, and
TS
TB
.
We have also added the lt
, BP
, BR
, CO
, CS
,
CT
, DC
, DT
, DV
, EP
, ER
, and TC
.
capabilities.).
First the lower case, which specify mostly dimensions:
ch
co
cw
li
lt
pc
xr
yr
co
and li
are not currently used.
The capitalised capabilities mostly tell
the stdgraph routines how to plot lines, clear the screen and so forth. Some
of these are no more than character strings to send to the terminal,
(e.g. CL
to clear a screen), but
some use the graphcap entries to programme a sort of RPN calculator, which
computes the bit-patterns that the terminals demand. This calculator is usually
referred to as the `encoder'.
We'll first list all the capabilities in a reasonably ordered way,
then describe the encoder and what it can do,
and then go through a number of examples.
First the fields which are simple character strings to be written to the terminal. The second column is an attempt to explain the etymology of the two character name.
CL (CLear)
CW (Close Workstation)
DS (Draw Start)
DE (Draw End)
FD (Fill Draw)
FE (Fill End)
FS (Fill Start)
GD (Graphics Disable)
GE (Graphics Enable)
IF (Initialisation File)
LR (Load Registers)
ME (Mark End)
MS (Mark Start)
OW (Open Workstation)
OX (Open workstation)
OY (Open workstation)
OZ (Open workstation)
PG (PaGe)
VE (? End)
VS (? Start)
PG
should start a new page.
The GD
and GE
are used by terminals which spend some of their
time being graphics terminals, and some being regular text terminals.
The various "... Start" and "... End" capabilities assume that the
points in question are specified by the XY
entry (except for
FS
/FE
where FD
is used instead). Typically, the
`start' is used to put the device into (e.g.) line-drawing mode, then
the line is drawn with a sequence of XY
's, then it is taken out
of (e.g.) line mode with the `end'. The support for filling areas
assumes that a region is specified by drawing a line around it; if
this isn't so, you'll have to omit area fill from graphcap, and rely
on SM emulating it for you. An example would be a Graphon
GO-250, which has an area fill where you fill rectangular areas by
specifying opposing corners; this is not acceptable to SM.
Some operations require an argument, for instance setting the hardware line type, specifying which cursor to read @footnote #{Actually, SM always uses cursor 1}, or specifying coordinates. In the following properties, the expected parameters are listed after the field names, the first to go into register 1, the second into register 2, and so on. If you haven't skipped forward to the section on the encoder this will seem obscure, but all will become clearer.
CO(r,g,b) (COlour)
CS(n) (Colour Start)
CT(i) (Colour Type)
DC (Default Colour)
LW(f) (Line Weight)
MC(i,x,y) (sM Cursor)
ML(i) (sM Line)
RC(c) (Read Cursor)
SC (Scan Cursor)
TB(x,y) (Text Begin)
TE (Text End)
XY(x,y) (X Y)
ML
entry -- for a linetype to be supported in hardware it must also be
included in the lt
list, e.g. lt=01234
. Similarly, for hardware
fonts you must include ch
and cw
, and TB
must be present
even if it does nothing. Note that LW
is passed a floating point number,
and that the special case 0 is special, meaning choose the most efficient line
thickness for the device.
The folowing capabilities have to do with rasterising and are discussed in their own section near the bottom of this appendix:
BP (Bit Pattern)
BR(i) (Begin Row)
EP (Empty Pattern)
ER (End Row)
ll (lINE lENGTH)
DR=hex
.
MR (Many Rows)
nb (nUM bYTES)
RA (RAster)
RD (Raster Device)
xr
, yr
, CW
, OW
,
OX
, OY
, OZ
,
OF
, and SY
which are also used by stdgraph itself.
Finally there are some capabilities that are designed for driving hardcopy devices and devices that may not use stdgraph at all:
DT (Device Type)
DV (DriVer)
OF (Out File)
RT (Record Terminator)
SY (SYstem)
OF
file may be specified with the last characters being `XXXXXX', in
this case the Xs are replaced by a random characters, to make a unique
filename. If the variable temp_dir
is defined in the environment
file, then OF
is created in that directory, otherwise it is put in the
current directory. The DT
string, if present, specifies the type
of device in use. Currently the values are only used under VMS, where
they are used to decide how to open files. The recognised values are
"qms" and "imagen". In general DT should be omitted, as it
requires programming support, but it can help stdgraph to deal with
hostile operating systems. For a discussion of the DV
entry
see section New Devices and New Machines.
The SY
string is passed to the operating system after graphcap
variables have been expanded (they are similar to macros in Unix's
make
). A variable is defined with a line like:
name = valuewhere
name
must start in the first column. Any white space surrounding
the equals sign is removed, as are any trailing blanks. If value
starts
with a $ it is taken to be a regular SM variable.
Variables may be defined in any of the graphcap files in the search path,
and if a name appears more than once the first value
found will be used (if you change graphcap without leaving SM the variables
are re-read). There is no guarantee that all the graphcap files in the
path will be read but this is unlikely to be a problem.
The major use for graphcap variables is probably for encoding
rasterise
's full name:
BIN = /usr/local/bin device|some device:\ :DV=raster:OF=tst_XXXXXX:\ ... :SY=${BIN2/rasterise -r $0 $F $1:
Variables are written as ${name2
not $name
, which
means that they will not (usually) conflict with the operating system's
uses for dollar signs. The graphcap variable F
is special, as it
always expands to the filename specified as OF
. As a
concession to history it may be written as $F
instead of ${F2
.
Also special are $"prompt"
, which is replaced by a string read from the
keyboard (you are prompted with prompt
),
and $n
which is replaced by the n'th argument to the DEVICE
command.
For example, if the DEVICE command were DEVICE qms lca0 Hello
(or DEVICE 1 qms lca0 Hello
), then
the device name qms
would be $0, lca0
would be $1 and
Hello
$2.
If a `$' is found under other circumstances it is simply treated as a dollar
sign, but if you wish you can escape it with a \ (but remember that
the \ must itself be escaped so to explicitly
escape a dollar in an SY
string you must type \\$
).
This means that (under Unix)
you can access environment variables from SY
strings, e.g.
:SY=mv ${F2 $HOME:
. If a variable is referenced but no value is
provided when the device is opened a warning message is printed; this
message can be suppressed by referring to the variable as (e.g.)
$%1
.
The SY string is only used if an OF file has been specified. There is no
guarantee that SY is supported by all operating systems, but it is certainly
available under Unix and VMS (SY requires the C call `system()', as defined
for Unix. We have provided one for VMS, and any serious SM
implementation would have to have one too.)
A trivial example of SY in use on an Unix system would be:
:SY=cat $F ; rm $F:OF=out_XXXXXX:(cat prints a file, ; separates multiple commands on a line, rm deletes a file). Because not all operating systems can support multiple commands on one line, you can use \n within a SY string to separate commands. For example, under VMS that
SY
string could have been
written
:SY=type $F. \n delete $F..*:OF=out_XXXXXX:(Type adds a `.lis' unless explicitly given a closing `.', delete requires a version number, hence the
$F.
and $F..*
.)
An example of the use of $""
would be
:SY=mv $F $"Output filename? ":which renames the
OF
file to whatever you want.
The RT
capability has been deleted in version 2.0, in favour of
using DT
; The RA
capability has been replaced in version
2.1 by :DV=raster:
.
Different terminals have very different ways of doing the same thing. For
example to move the beam to (200,200), a vt240 in REGIS mode needs to be
told `[200,259]', while a Tektronix 4010 needs `&h&H'. In order to cope
with this much diversity, stdgraph has a binary encoder with a 50 element
stack, 10 registers and about a dozen operators. The encoder communicates with
the rest of the world through its registers - for example in encoding a
coordinate pair it expects to find x in register 1, and y in register 2. When
reading a graphcap string, initially stdgraph simply copies the input
characters to an output string, which is then written to the terminal.
This is exactly what it does when it interprets the OW string
for a Tektronix, OW=^]^_
. However, in addition
to characters such as ^
being special, it also recognises the
following as being special:
'
%
(
'
%
)
#nnn
$
.
,
`str`
&
+
-
*
/
<
>
=
;
0-9
!N
!!
|
Unless otherwise specified the stack is taken to be integer-valued,
although in fact it can support either integer or floating point
values. There is no type checking -- if you ask the encoder to print
the bottom of the stack as a float, but you stored an int, you can expect
trouble. If it is needed we might add more floating point support;
apart from printing the bottom of the stack, the only floating
point operation supported is `|' which rounds the bottom of the stack
(taken to be a float), converting it to an integer (so, for example,
1|1!
converts the contents of register 1 from float to int).
All the binary operators operate on the bottom 2 elements of the stack, and push the answer onto the bottom. Any other character is interpreted as an integer, and pushed onto the stack - for instance, `' is the same as `#64', octal 100. A blank is the octal constant 040.
The %
command means, `format the bottom of the stack, and write
it to the
output string'. The format string may be any printf format specifier (printf
is the C formatted i/o function. In practice, the only formats that you are
likely to need are %c
, %d
, %g
and %t
-- and
%t
isn't even in C!
%c
means `write the integer as a character', %d
means
`format the number as a decimal integer', %6d
means
`and make it fill 6 characters', and %g
means format a floating
point number.
If you should need to know more, look at any book on C.) The special format
%t
means `take x and y from registers 1 and 2, and format them for a
Tektronix'. As we shall see below, you can programme the encoder to do this,
but Tektronix emulators are so common that %t
is provided for
efficiency's
sake. In fact there are two Tektronics formats, %t
for 10 bit addresses,
and %T
for 12 bit addresses.
The switch and branch instructions are discussed below, while examining
specimen ML
and SC
strings.
As a simple example, the ANSI command to set a non-graphics cursor to a given line and column is
^[[ line ; column HAssuming that the x and y coordinates are in registers 1 and 2 respectively, the corresponding graphcap string would be
"^[[(2)%d;(1)%dH"(where the quotes are not part of the format.) What if line and column coordinates start at 1, but the terminal wants them starting at 0? then the format would be
"^[[(2#1-)%d;(1#1-)%dH"You could write those
#1
's as ^A
which would be slightly
faster, but why bother?
As promised above, it is also possible to encode Tektronix-type coordinates. The desired bit format for a 10-bit address is
0 1 ya y9 y8 y7 y6 1 1 y5 y4 y3 y2 y1 0 1 xa x9 x8 x7 x6 1 0 x5 x4 x3 x2 x1where x1 is the least significant bit in x, and ya is the tenth bit in y. If x and y are in registers 1 and 2, the simplest XY (move/draw to (x,y)) string is
"%t"but if this weren't available the following string would work:
"(2 / +.2 &`+.1 / +.1 &@+."(as before, the double quotes don't belong to the format). To understand this, First look up the octal values of ` ' (040), "' (0140), and `@' (0100). Then the first
`('
puts the encoder into encode mode. `2 /'
pushes the Y value
onto the
stack, and right shifts it by 5 bits (` ' is 100000 in binary). The next
` +.'
adds the resulting bit pattern `0 0 ya y9 y8 y7 y6' to 0100000 and
transfers it to the output string, and we have produced the desired first
byte. The other bytes are produced in a similar fashion.
As another example consider an AED512, which is reputed to desire the bit sequence
xa x9 x8 yb ya y9 y8 x7 x6 x5 x4 x3 x2 x1 y7 y6 y5 y4 y3 y2 y1The graphcap string
"(#128!919/^N*29/+.19&.29&."will accomplish this. We could further optimise this by loading the value `#128' into register 9 once and for all with the LR capability, so a part of the graphcap entry would appear as
":LR=#128!9:XY=(19/^N*29/+.19&.29&.:"I've never seen an AED512, but this should work anyway.
The switch instruction has the form
$i ... $j-k ... $l ... $D ... $$where
i
, j
, k
, and l
are integers.
The encoder pops the bottom value off the stack adds `0' to make it
a character, and scans forward looking for a $
followed by that
character.
$2-5
would match the characters `2', `3', `4', or `5'. When it
has met its match, it executes the instructions that it meets until it
reaches the next $
in execute mode. The encoder then skips forward
until just after the
$$
, and resumes scanning. If the character from the stack is not
matched by
any of the cases, the encoder will use the $D
(i.e. default) case,
if present.
As an example, consider how stdgraph sets the type of line to draw. SM expects linetype 0 to be solid, 1 to be dotted, and so on. We expect a linetype in register 1 and have to do something with it.
For a Tektronix, the linetypes are set by an ML entry:
ML=^[(1$0)`($1)a($2)c($3)d($4)b($$What does this do? The
^[
is simple, it is executed in copy
mode, and writes the character ^[
to the output string.
The (1
enters encode mode, and places the contents of register 1, the
desired linetype, on the stack. Then begins the switch. If the linetype is
0, then the encoder scans past the $0
and starts reading the string
again with )`
. The )
takes the encoder back to copy mode,
so it copies `
to the output string, and encounters a
($
which puts it back into encode mode. Once in encode mode it
recognises the $
as the end-of-case, and scans forward until it reaches
$$
, where it stops. We deduce that the set-linetype-0 escape sequence
is ^[`
. If register 1 had contained a 2, after
entering the switch the encoder would have scanned forward to $2
(ignoring
all characters as it went), and copied c
to the output string.
If you want to support erasing of individual lines (LTYPE ERASE
or
LTYPE 10
) you'll have to include a $\:
case in your switch
(as :
follows 9
in the ascii character set, and an un-escaped
:
would end the graphcap entry). You'll have to escape the :
in the lt
list as well. When leaving erase mode, by specifying any
other line type, the device will first
be set to LTYPE 11
(i.e. ML
'll get a ;
) before it's
set to the desired
LTYPE
; this gives the driver a chance to reset itself. It's wise
to also turn off
erase mode when closing the device. An example of an entry supporting
erasing lines is a graphon, which includes
:lt=01234\:;:CW=^[1^]^[^A^[2\ :ML=^_^[(1$0)`($1)a($2)c($3)d($4)b($\:)`^[^P($;)^[^A($$:as
^[^P
puts a graphon into erase mode, and
^[^A
takes it out. Note that in erase mode the
linetype is set to solid (^[`
), so as to erase all types of lines.
There is also a branch instruction, which has syntax
<boolean><offset>;If the boolean is true (non-zero), then skip (offset - 1) characters in the programme string. The offset may be either positive or negative, and the `;' is at offset 0. For example,
(0#15;)Goodbye(#1#8;)Hello()\nwill print `Goodbye\n' if register 0 contains zero, or `Hello\n' otherwise. As an example of the use of `;', consider using the encoder to decode a string. Remember that `,' meant `read a character onto the stack', and that there was a graphcap capability SC to decode cursor responses. Suppose that we are dealing with a vt240 in REGIS mode, then a cursor read will return a string of the form `k[nnn,mmm]' where `k' is the character you hit, and (nnn,mmm) is the cursor position. We want to put k into register 3, and (x,y) into registers 1 and 2. This is a little messy, as we'll have to convert the ascii positions into integers. The desired graphcap entry is
SC=(#0!1#0!2,!3,#0!8,#48-!99$0-91#10*9+!1#1!8$$8#1=#-39;\ 0!8,#48-!99$0-92#10*9+!2#1!8$$8#1=#-39;62-!2):The first part is simple enough, store 0 in registers 1 and 2, store the first character in register 3, read a character (the [), and store 0 in register 8. Then we come to
,#48-!99$0-91#10*9+!1#1!8$$8#1=#-39;
.
The ,#48-
reads a character and converts it to an digit (48 is the
decimal
code for `0'), then stores it in register 9. The switch then checks if we do
have a digit, if so we multiply register 1 by 10 and add the new digit. We then
set register 8 to 1 and finish the switch which is here being used as an
if statement. The 8#1=#-39;
tests register 8 against 1 (i.e. checks if
we found a digit), and if we did it jumps back 39 characters, to read the
next character@footnote #{In counting characters for jumps, the ; is at
character 0 and combinations such as ^N count as one character}.
So we are accumulating the integer nnn in register 1, just as we needed to.
The rest of the string deals with decoding the y coordinate.
Sometimes you don't want to read from the input string, but from the
keyboard instead. In this case use `str`
, e.g.
(`Hello\: `#48-$0)False($D)True($$)\n:
will prompt you with
Hello:
, then read a character from the keyboard. If you enter
a `0' it'll print False
, otherwise it'll print True
. Of course,
in reality you'd want to do something more useful (such as erasing
the screen).
prompt
, then you will be prompted for the key you would
have hit, and the (x,y) position the cursor would have been at, if the
terminal that you were using could support a cursor.}
There are two ways to do this,
either by specifying a sequence of characters which `end' the response
string
along with a minimum number of characters to read, or by specifying a pattern
that the terminal response is to match. A typical example of the former is a
Tektronix whose cursor response may be chosen to be ^M
(this is
called the GIN response, and can usually be set in the terminal setup). We
know that the terminal will also send 5 other bytes (the key struck and the
encoded x,y coordinates so we would specify
:MC=^M:CN=6:On the other hand, a REGIS terminal sends `k[nnn,mmm]'. This can be specified as
:MC=?[#*,#*]:CN=-6:where the negative value of CN means that we are providing a pattern not just a terminator (as before, the absolute value of CN is the minimum number of bytes in a cursor response). In MC strings, but nowhere else, the characters
?
, #
, and *
are special (although their special meanings may be escaped with
a \
). ?
will match any character, #
any digit, and
*
means `match zero or more of the preceding characters'. So a MC
string of
a#*?ba
will match `aaa1111bbaa' at the third character. (Incidently,
a#*?a
would match at the first). Because this special character syntax is
different from that used in standard graphcap files for IRAF, the name of
this graphcap parameter has been changed from CD
to MC
.
If your cursor is atached to a mouse, if possible the buttons should be set up to generate `e', `p', and `q' from left to right (if you have that many buttons). If you have only one button, `p' is probably the best choice.
The number passed to CT
are the same as those specified with the
CTYPE INTEGER
command, so initially they specify
default, white, black, blue, red, green, magenta, yellow, and cyan
(white is 1).
These are the colours corresponding to turning one, zero, two, or three of the
primary colours on. The default colour to use for a device is
specified by the DC
capability, e.g. :DC="red":
.
The CS
and CO
capabilities are used to support the CTYPE = expr
command. First CS
is used to tell the device how many
colours to expect, then CO
is used for each number, with red, green,
and blue as its arguments. In this case CT
passes an index
into the set of CO
values. If you want to get an index, but
don't need CS
and CO
, you must still provide them; just
provide a no-op such as :CS=():
.
tc
to satisfy most of your device's needs.
But let's assume that you are faced with a totally new type of device
and really do have to start from scratch. First find out how large
your device is, and fill in the xr
and yr
entries. If you
are going to use hardware character sets you also need ch
and
cw
. Next decide on the string to initialise the device -- does
it need to be set into some weird mode -- and put it into OW
.
Put the string to reset it into CW
. Now, if the initialised
device needs to be put into a special graphics mode put it into GE
and its inverse into GD
. Next, you need to tell SM how to
draw a line and move the plot pointer. So enter the DS
, XY
,
DE
, VS
, and VE
capabilities. Of course, if one
isn't required, don't put it in. If you have some sort of
printer you probably want to store all the commands in a file (OF=
), and to plot them (SY=
). You should now be ready to make
your first test, so plot a box. If it doesn't look right, fix it. Or you
might like to try printing the cover (load cover cover
).
When all is well, you can begin looking into options that might make your
graphcap entry more efficient. Look through this appendix to see what
is available. Does your device support line types? Add
ML
and lt
. Heavy lines? LW
. Coloured lines or a
cursor? See section Using Colours with Graphcap.
Filled polygons? FS
, FD
, and FE
. Dots? MS
ME
.
Hardware characters? TS
TE
. If your device produces
hardcopy you should arrange to start a new page with PG
(the
PAGE
command).
When you have finished please send us your new entry.
DEVICE raster
and a separate programme called rasterise
.
You specify that a device in a graphcap file is a raster device by
using DV
: :DV=raster:
(The old form :RA:
is no
longer supported).
It communicates with the rasteriser through graphcap, so the whole
process is user transparent. A separate rasterising programme was
written so as to allow the plot to be produced in the background while
you do more productive things, and to allow the rasterising to be
done on a remote machine.
DEVICE raster
produces a file, whose name is specified as usual
by the OF
field in graphcap, containing the vectors to be
plotted (as groups of four short integers) in device coordinates,
where the size of the device is taken from xr
and yr
. When
the device is closed, the command specified by SY
are executed,
and these will usually be of the form
rasterise -r $0 $F outfile\n print_it outfile\n delete outfile
where print_it
is the proper way of actually getting a plot. Under Unix,
the command might well be something like
(rasterise -r $0 $F - | lpr -v -r -P$1)&
dispensing with the
temporary outfile
.
What do these rasterise
commands do? The command syntax is
rasterise [-flags] device infile outfile
, where the infile may
be specified as `-' to use standard input (sys$input to VMS),
where the outfile may
be specifed as `-' to use standard output (sys$output to VMS).
Possible flags are r
to remove the infile after use, R
to
rotate the plot through 90 degrees, and v
for more verbose operation.
Rasterise
then reads the data in the infile, and produces a
rasterised version, row by row, on the outfile. In order to do this,
it looks in graphcap for an entry for device
, and uses the xr
,
yr
, OW
(and O[XYZ]
), and CW
fields as usual.
@footnote #{In looking for the graphcap file, any environment file
or search path specified on the SM command line with a -f
or -u
flag is ignored.
}
Let's first consider a simple, one-line-at-a-time device such as a line
printer. Before writing each row to outfile
, rasterise encodes
the BR
(Begin Row) capability, using the current row number as an
argument, and encodes ER
(End Row) at the end of the line. By
default, it assumes that the raster device simply wants bits turned on
where a dot is required, but this can be overridden using the BP
and EP
capabilities. EP
(Empty Pixel) specifies the bit
pattern for a character to represent white space. In the simple case
mentioned a moment ago, this would be simply NUL, with no bits on, but
sometimes this doesn't suffice (see examples below). BP
(Bit
Pattern) is a string, giving the bit patterns required to turn on the
various pixels. In the default case, BP
could be specified as
BP=\001\002\004\010\020\040\100\200
, so \001
would turn on
the first (rightmost) dot. Because there are eight characters given in
the string, raster
assumes that it can fit eight pixels into a
single character. If you don't specify a BP
this is what will be
used.
Some devices desire or require that the data be sent as hexadecimal numbers
rather than as binary; see the RD=hex
graphcap entry.
Some other devices (e.g. Epson printers) choose to print several lines
at a time, so a single byte transmitted to the device might print 8
lines, but only the first pixel of each line. Such devices are
described to graphcap by being given the MR
(Many Rows) capability and
a number nb
which describes how many bytes deep the
printing band is (if omitted nb
defaults to 1). In this case,
BP
is used to describe which bits are turned on vertically
rather than horizontally but everything is otherwise the same as for
the simple case.
As an example, consider the HP laserjet. You'd specify it as
DEVICE laserjet
, and its Unix graphcap entry reads:
laserjet|HP laserjet (high resolution):\ :DV=raster:xr#1280:yr#640:CW=^[*rB:OW=^[*r1280^[*rA:BR=^[*b160W:\ :OF=hp_XXXXXX:\ :SY=/usr/local/sm/rasterise -r $0 $F - > /dev/hp&:On opening the device, it gets the string
^[*r1280^[*rA
,
setting the resolution and raster mode. Then, at the beginning of each
rastered line it gets ^[*b160W
specifying that 160 bytes
are coming its way, then finally ^[*rB
to restore it to
alpha mode. (It doesn't need to know which row it is on, so the BR
string doesn't tell it, and the default BP
and EP
are fine).
After the input file is read it is
deleted, and the output file is sent to the standard output, whence it is
redirected to the proper device, in this case directly rather than
through a spooler.
A more complex example is a printronix printer, which encodes 6 pixels in each byte, and requires that bit 7 be turned on. It also needs an escape sequence at the end of each line. The corresponding graphcap entry is
printronix|DEC printronix printer:\ :DV=raster:xr#792:yr#792:CW=^L:OW=^L:BR=ER=^E^J:\ :BP=\001\002\004\010\020\040:EP=\100:\ :OF=pr_XXXXXX:\ :SY=(/usr/local/sm/rasterise -r $0 $F - | rsh wombat lpr)&:We use
EP
to turn on the seventh bit everywhere, as required,
and specify only 6 values for BP
, so only 6 dots will be packed
into each character. The BR
entry is empty, and ER
provides
the needed escape sequences at ends of lines. In this case SY
sends
the plot over a network to machine wombat
.
Some devices are not able to simply accept a string of bytes with an
occasional escape sequence. For example, a versatec needs to have the
bit order changed, or a simple screen plotter might want to write a
*
if a bit is set and a space otherwise. If this is the extent
of your pathology, you can deal with it via the provided capabilities.
(Fortunately adding a *
onto a space makes a *
, so you can
use :EP= :BP=*:
for the latter.)
If you have a really bad device, it is possible to add new coded
device drivers to rasterise
.
For the convenience of such devices there is a graphcap capability
RD
which specifies the name of a type of raster device. If
rasterise
recognises the device
it it calls a different set of routines to deal with the rows of data.
Otherwise it proceeds as discussed in the previous paragraph. This behaviour
is similar to that of the DEVICE
command in using stdgraph if it
doesn't recognise a device name.
If you find that you do need to write routines for some device, don't
be too disheartened. Rasterise
will still do the book-keeping and
rasterising for you, your work will be limited to a couple of output
routines. If you need to know more, see the source for rasterise. The only
time that I used this capability came about two years after rasterise was
written, and was RD=hex
which specifies that lines be written as
hexadecimal numbers rather than as 8-bit characters (e.g. write the two
characters FF
instead
of the single character `\377'). The line length is given as ll
.
When stdgraph attempts to use the compiled capabilities, it checks that the current graphcap file has exactly the same name as the one that cacheg.dat was compiled from, if it isn't then it reads the graphcap file anyway. This provides a mechanism for those without C compilers to change the graphcap entries of pre-compiled devices. If you have a list of graphcap files, the name of the first is checked against the name in the `cacheg.dat' file.
@c doesn't throw away a chunk of text
Go to the previous, next section.