The basis around which the command interpreter is written is a grammar which is passed a set of tokens ( analogous to words in English ) which it parses, given a set of grammatical rules. As it recognises each rule, it executes the code associated with that rule. See section The SM Grammar.
An example would be:
aa : BB CC
{
printf("Rule BB CC found\n");
}
which specifies that the rule aa consists of the token BB followed by
CC,
and that if rule aa is recognised the programme should print that fact
out. Conventionally, uppercase names are reserved for `terminal symbols',
and lowercase for `non-terminal symbols' where terminal symbols are
those that are passed to the parser ( analogous to words ), and
non-terminal symbols are tokens that the parser has constructed out of
terminal symbols (analogous to phrases). The right hand side of a rule
may contain a mixture and non-terminal symbols, and symbols may be
assigned a
value(22).
SM generates tokens for the grammar roughly as follows:
When characters are typed at the keyboard, they are read by a routine
which runs in CBREAK mode (PASSALL for VMS), and
receives each character as it is typed. It is this routine that handles
command line editing, the history system, and
key bindings.(23)
Following a carriage return, it passes the whole line to the
lexical analyser, which divides
the input stream into integers, floats, strings, or words. In addition it
recognises ${}^ as having special meanings (see below under
variables ($) and history (^)). As in
C, the escape sequence `\n' is replaced by a newline, which means that
commands which read to the end of the line may be fooled into thinking
that they have found it; see the examples at the end of the section.
A { sets the flag `noexpand', which
turns off the interpretation of all special symbols, and causes all
tokens to be returned as WORD. The matching } unsets this flag.
This mechanism is used in defining macros and various lists.
A word is anything which is not otherwise recognised, so for
example `hello_there.c' or `1.2e' would be considered words.
Symbols are separated by white space, taken to be spaces tabs or newlines,
or the characters !, {, }, +, -,
*, /, =, ?, !, ,, <,
>,
(, or ). This behaviour can be modified by enclosing a string in
double quotes, when no characters (except ^) are special, and
tokens are delimited only by the end of the line, or some character
after the closing quote.
Enclosing in quotes is rather
similar to enclosing in {}, except that quotes have no grammatical
significance. A string in double quotes is always treated as a word,
but the quotes must not have been discarded by the time that the
lexical analysis occurs.
For example, "2.80" is a float, as SM will have digested
the " before looking at the string. You can fool it with "2.80 ".
A string begins with a ' and continues to the next ': they
are used in certain contexts where SM needs to know if a
WORD or STRING is involved, for example in a PRINT
command. It's worth noting that the '...' are stripped when the
string is recognised -- if you need to preserve them make sure that
noexpand is set (e.g. SET s={ 'a' 'b' 'c'} ).
The output from this programme is passed to a second stage of lexical
analysis. This passes integers and floats through unaltered, while
words are passed through a filter to see if they are external
tokens(24)
from the grammar (such as CONNECT).
If a word is recognised as being a token then that token is returned,
otherwise the token WORD is passed, and the text of the word is stored.
Tokens may be written in
either lower or upper case, but for clarity they are written in upper
case in this document. The overloading of lowercase tokens is achieved
at this stage by simply refusing to recognise them as keywords.
The input stream is now fully analysed into tokens and is passed to the
parser, which is written in YACC.
If the sequence of tokens seen corresponds to a grammar rule,
the parser executes the appropriate section
of code, which is written in C. If the parser doesn't understand, it
tells you that you have a syntax error and prints the last
logical line that it was processing, with the error underlined. If you can't
figure out which command it really failed on, try setting the VERBOSE
flag to be 4 or more. This produces a voluminous output, which will stop
suddenly when the error re-occurs.
One simple rule in the grammar is that a WORD should be
treated as a possible macro.
If the command interpreter is faced with a pair of grammar rules such as
AA BB CC
and
AA BB
it may not know whether to treat the tokens AA BB as the first part of
AA BB CC or as the complete command AA BB followed by the token
CC beginning the next command
without examining the next token. This ambiguity only arises if a command can
begin CC, and may
be dealt with by defining the second rule as
AA BB \n
This should be borne in mind whenever
SM complains about a syntax error in an apparently valid command (such
as LIST MACRO HELP, intended as first LIST MACRO and then the valid
command HELP). The presence of a required carriage return also sometimes
requires that macros be spread over a number of lines rather than as one
long list of commands, although a carriage return may always be written as
`\n', which makes SM think that it has found a carriage
return. There is a also requirement that an ELSEless IF statement
should end with a newline; this is produced by a subtlety of the way
that IF's are processed and is discussed under IF.
SM places a restriction upon commands such as RELOCATE which
expect more than one argument, which is that the arguments must be
numbers rather than (scalar) expressions. This is required by the
unary minus, as if the grammar sees expr1 - expr2 it cannot know
whether this is the two expressions expr1 and -expr2, or the single
expression expr1-expr2. Unless the grammar is changed, for instance by
using commas to separate arguments, this restriction cannot be lifted;
it can, however, frequently be circumvented using macros such as rel
discussed under `Useful Macros'. As an alternative, in almost all
cases the expression can be enclosed in parentheses, for example
connect (lg(x)) (-lg(rho)).
Executing a macro consists of substituting the text of the macro for
its name. In order to understand how SM does this you have to
know a bit more about how it processes input characters. We said above
that it `passed the whole line' to the lexical analyser. What it
actually does is to pass a pointer to the line, and starts reading
from the beginning of the line. Now if you execute a macro, all that
is done is that we now pass a pointer to the text of the macro, and
start reading from it instead. The old pointer is pushed onto the top
of a stack. When SM comes to the `\0' at the end of the macro
text, the stack is popped and input continues as if the macro had
never been seen. When we come to the end of the `whole line' pushed at
the top of this paragraph, it is popped, and SM gives
you a prompt for more input.
Of course, if a macro had been seen while the first
macro was being executed, the first one would get pushed onto the
stack, and attention transferred to the the new one. If a macro has
any arguments, their definitions are pushed onto an argument stack
which is popped at the proper times. To jump ahead a little, variables
are implemented in a very similar way, being pushed onto the stack, as
are DO and FOREACH loops, and perhaps more surprisingly
IF statements.
The strange behaviour of RETURN at the end of macros comes about
because when the input routine is reading the RETURN it has to
read one character beyond it, so as to know that it isn't dealing with,
say, RETURN_OF_THE_NATIVE. But in looking for the next character
it has to pop the macro off the stack, so when the RETURN is
acted upon we have already returned from where we wanted to
return from, and we now RETURN from the wrong place. In a
similar way, an IF at the end of a macro will cause the parser
to look for an ELSE, thereby popping the macro stack if there
isn't one. If the IF test was true, and contained references to
macro arguments, there will be a problem as either there will be no
macros defined, or the arguments to the previous macro on the stack
will be supplied.
Macro definitions are currently stored in the form of a weight-balanced tree (actually a tree). This means that the access time for a given macro only grows as the logarithm of the total number defined. In the future it may be possible to choose the weights depending on the access probability for a given macro, but this is not currently possible. Definitions of variables and vectors are stored in the same way.
It seems worth discussing the implementation of these commands.
Both loops consist of a definition of a variable, together with instructions
about what to do with it, followed by a list of commands within a set
of {}, while IF just has the command list.
It is not possible for the main grammar to execute commands or
macros, as the YACC implementation is
non-reentrant, so the best that it can do is to push the commands onto
the input stack as a sort of temporary macro, after defining the
initial value of the loop parameter. When the `\0' at the end of the
loop appears, instead of popping the macro stack we simply define the
loop parameter to have its next value, and jump back to the
beginning. This means that you can't change the value of a loop parameter,
as it'll be reset anyway, but you can use it as a sort of local variable.
IF statements are similar, in that we read the entire list
before executing it. Once more, a temporary buffer is pushed onto the
stack, with instructions to delete it after use. The reason that a newline
is required after an ELSEless IF is that the grammar will
have already read the next token to see if it was ELSE. If it
wasn't, then it will seem to have been typed before the body of the
IF. For example, IF( test ) { echo Hello } PROMPT : will be
parsed as IF( test ) { PROMPT echo Hello } : if test is true,
but correctly as IF( test ) { echo Hello } PROMPT : if it is false.
Because an extra \n does no harm, we demand it.
If you want to watch SM thinking about these examples, the command
VERBOSE 4 will make it print out in detail each token as it reads it,
and each macro or variable as it expands it. To turn this off, use
VERBOSE 0 or VERBOSE 1.
To really see the parser at work, try a negative value of verbosity.
This will report every step that the parser takes, providing that it
was compiled with DEBUG defined. A second negative value will turn the
information off again.
PROMPT @
PROMPT is an external token, so PROMPT is passed to the
grammar which recognises the rule
PROMPT WORD, and sets the prompt to be `@'. When it has finished,
control is passed back to the input routine.
MACRO p { PROMPT }
p to be PROMPT
p @
p as a keyword, so it returns
WORD
and as the grammar has no other interpretation of a WORD in this
context,
it passes p to the macro interpreter, which
replaces it by PROMPT (i.e. pushes PROMPT onto the input stack).
SM now thinks that you have just typed
PROMPT @, and behaves as described in the first example.
MACRO pp 1 { PROMPT $1 }
pp is declared to have one argument, which is referred to as
$1. After pp is invoked it reads the next (whitespace delimited) word
from the input stream, and replaces $1 by that word.
pp @
@.
pp
PROMPT.
PRMPT
PRMPT isn't an external token, it is a WORD, so SM
tries to execute
it as a macro and complains if it isn't defined.
DEFINE Hi Hello
Hi is defined to have the value Hello.
WRITE STANDARD $Hi Gentle User
$Hi SM pushes the value of the variable Hi
onto the stack and then reads it, popping it off again when it has finished.
The WRITE STANDARD command writes Hello Gentle Reader (i.e.
up to the end of the line) to the terminal.
WRITE STANDARD $Hi Gentle User \n pp "SM>"
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(25) 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 accomplish this is to add an entry
+graphcap /u/rhl/sm/graphcap
above 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
Of these, 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)
For hardcopy devices 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(26), 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)
Some of the above comments are a little cryptic, but we return to the
various graphcap parameters that take arguments as examples after describing
the encoder. Note that it isn't sufficient to change the
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 following 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)
Raster devices also make use of 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)
The 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 = value
where 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=${BIN}/rasterise -r $0 $F $1:
Variables are written as ${name} 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 ${F}.
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 ${F} $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:
'
%
(
When in `encode' mode, the following operators are available:
'
%
)
#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 top 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 top of the stack, the only floating
point operation supported is `|' which rounds the top 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 top 2 elements of the stack, and push the answer onto the top. 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 top 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 H
Assuming 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 x1
where 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 y1
The 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 top 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()\n
will 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(27).
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).
We have just been through a long explanation of how to decode a cursor string,
but how did stdgraph know what to read in the first place? After receiving the
RC string, the terminal will send back a sequence of bytes, and the format
of these bytes must be specified in graphcap.(28)
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 attached 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=():.
You might think that all that you have to do to modify a graphcap entry is to start up your favourite editor, and start typing. You could do that, many people have, but it isn't recommended because you'll have to do it again when a new release of SM comes along. It's better to use SM's graphcap search path (see section The Graphcap File) as follows:
Let us assume that you want to modify the xterm entry to print something every-time that is changes from graphics to alpha mode or vice versa (why? so as to fix a problem with excessive screen switching). First set up the system `.sm' file to look like:
+graphcap /my/private/graphcap graphcap /usr/local/lib/sm/graphcap
which tells SM to first search `/my/private/graphcap' then `/usr/local/lib/sm/graphcap' (the graphcap file that we provide) for graphcap entries. Then edit `/my/private/graphcap' and add the entry
xterm|an xterm with noisy mode flipping:\
:GE=\nE\n^[[?38h:GD=^[^C\nD\n:TC=xterm:
Note the use of :TC=xterm: which says that SM should skip this file
when looking for the definition of xterm, thus avoiding an infinite
loop.
When a new version of SM is released your new definition will continue
to work (unless we change the definition of xterm in which case
you'll have some work to do anyway). You can use this technique to change
entries or add your own new ones without modifying the system file; all
of your changes are in `/my/private/graphcap'.
So, if you're faced with a new piece of hardware what should you do?
First of all, don't panic -- writing entries is quite simple. Second,
see if your device is basically the same as one that already exists
in graphcap, for example the entry for `graphon' uses the `selanar'
entry, and it in turn uses `tek4010'. You might be able to get away
with using tc to satisfy most of your device's needs.
Before writing your new entry please read the previous section to learn the recommended strategy for modifying graphcap files.
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.
Stdgraph can only handle devices that can plot vectors specified by
their endpoints; unfortunately some devices (such as most line
printers) can only plot graphs when they have been reduced to rows of
`on' and `off' pixels. SM supports such devices through
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 specified 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.(29)
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.
(This section is really for someone maintaining SM.) Rather than have stdgraph read the graphcap every time that it opens a devices, it is possible to compile the capabilities of the more popular devices into the executable. This is done by preparing an include file which initialises the appropriate arrays, using the programme `compile_g' in the main directory. After this file (called cacheg.dat) has been prepared, files depending on it must be recompiled and SM must be relinked. The use of compile_g is pretty much self-explanatory, you give it a list of the devices you want and it produces the cacheg.dat file. Problems arise, however, if you don't have a valid cacheg.dat file, as then you can't compile compile_g in the first case. Fortunately, it is possible to bootstrap a cacheg.dat file (by defining BOOTSTRAP to the C-preprocessor), and proceed from there.
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.
sm_alpha etc.?
The SM callable interface is different from that of
Mongo and corresponds directly to the interactive version. Almost
all of the commands available in SM can be called from either
fortran or C, the exceptions being those concerned with the macro processor,
variables, history, and vector manipulations. We assume that if you want to
call graphics routines directly, then you are prepared to take responsibility
for such things. In C (and probably pascal, modula, or ADA), the calls have the
same names as the commands with the prefix sm_, so to set the limits say
sm_limits(0.0,1.0,0.0,1.0);. On VMS, and with some unix systems (notably
HPUX and AIX), if you are writing fortran, you must
prepend
an `f' to the command - call fsm_limits(0.0,1.0,0.0,1.0). If you forget
this
`f', your programmes will compile, but they `won't' work. (They'll give
segmentation violations, most likely. Why? Because you will be calling the
C functions directly, not the Fortran interface functions, and the languages
have different ways of passing arguments, so e.g. you will pass the address of
the variable from your Fortran program to a C function that expects to get the
value of the variable. This translation is precisely what the interface
functions are set up to do for you).(30)
If your fortran compiler cannot find the function names, you might have to
read the section on how SM chooses function names (see section How to Choose the Name for Fortran Subroutines).
So how are you supposed to know what to call the functions? Well, our best suggestion is to ask the person who build SM on your system; the second best suggestion is to look in `libplotsub.a' (or `libplotsub.olb' on VMS systems), and see what the names of the functions are. On VMS, try this:
lib/list/names libplotsub/lib
and look at the names it lists under module INTERFACE. On a unix system
you can use nm (or, if desperate strings). The output is quite long,
so I'd filter the output, e.g.:
nm libplotsub.a | grep errorbar
If you see things in the output like fsm_errorbar, then
you know this is one of those machines where you have to prepend the `f' for
the fortran interface functions.
If you used an ANSI compiler to link SM (or your guru did) then you should provide prototypes for the SM functions that you call if you use the C interface (Fortran knows nothing of such things). This can be done by including the file `sm_declare.h'.
In what follows, we will assume that you are not writing fortran under VMS, so we will omit the leading effs.
To use SM functions, you must link with appropriate libraries. You
will always need to link with the three SM libraries, libplotsub,
libdevices, and libutils in that order. Specifically,
under Unix, you'll need to include the files `libplotsub.a',
`libdevices.a', and `libutils.a'
when you link (it's probably easier to use -lplotsub -ldevices -lutils,
along with a -Ldir if needed).
and under VMS you'll
need `libplotsub.olb/lib', `libdevices.olb/lib', and `libutils.olb/lib'. I can't tell you where
they'll be on your system.
In addition, you may need to link (after utils)
any libraries used by
the devices that have been compiled into your version of SM.
For example, if you use the X-windows driver, you'll need to link with
the X-library (-lX). Consult a local guru in case of any trouble -
the person who installed SM has had to work this out already.
A list of functions giving the calling sequence for all the
available functions follows, but first an example. Note especially the use of
graphics and alpha to set the terminal to graphics mode, and
return
to a normal terminal afterwards. We would recommend always calling these,
if they do nothing (e.g. on a laser printer) they'll do no harm.
A related function is gflush() which will update graphics on the
screen, for instance with stdgraph where output is usually buffered.
Some devices (such as GL on an SGI, or X-windows on machines without
backing store such as RS/6000's) need some help from you to redraw the
screen. You do this by calling redraw whenever you are waiting
for input, passing it `0' (the file descriptor of standard input, for
those of you familiar with C). When input is ready, redraw will
return and your application can proceed. It should do no harm on systems
where it does no good.
If you don't want to be bothered with calling
sm_alpha, sm_graphics, sm_gflush, and
sm_redraw, See section Why do I have to call sm_alpha, sm_graphics, sm_gflush, and sm_redraw?.
There are examples of programmes (in both C and fortran)
calling SM in the directory sm/callable; you might want to look at them
before starting your own project.
integer NXY
parameter (NXY=20)
integer sm_device
integer i
real x(NXY),y(NXY)
c
do 1 i=1,NXY
x(i) = i
y(i) = i*i
1 continue
c
if(sm_device('hirez') .lt. 0) then
print *,'Can"t open hirez'
stop
endif
call sm_graphics
call sm_defvar("TeX_strings","1")
call sm_limits(-1.,22.,0.,500.)
call sm_ctype('red')
call sm_box(1,2,0,0)
call sm_ptype(40.,1)
call sm_points(x,y,NXY)
call sm_xlabel('X axis')
call sm_ylabel('Not x axis')
call sm_alpha
print *,'hit any key to exit'
call sm_redraw(0)
read(5,*) i
end
Remember, if you were running VMS, HPUX, or AIX then all those calls would
start `f' - call fsm_graphics and so forth.
Note that sm_box takes all of its four possible arguments, and that
commands such as sm_points add an argument to specify the number
of points to plot.
You must, of course, ensure that you use the correct type of variables,
passing integers or reals as required (and as listed below). If you are
using C, you must carefully distinguish between passing by value, and
passing by address (which we only use when an array is expected, and
for returning a cursor position).
The functions are as follows. For fuller definitions of the
arguments look at the main description of the command. The only ones
that are different are sm_conn for sm_connect (due to a collision
with a system routine), and sm_curs, sm_defvar,
and sm_plotsym because they don't
quite correspond to any interactive commands. A common source of
trouble is not noticing that the sm_ptype call is the vector
form of the command, so to set a ptype of `4 1' you must
say sm_ptype(41.0,1).
The arguments are declared in fortran in this list. real x(n)
means that x is an array of size n.
`Real' means single precision. (So in C,
character
char *;
integer
int;
real
float;
real()
float *. We deal with converting the
calling conventions from one language to another, but note comments at
the bottom of this table.)
sm_alpha
sm_angle(a)
sm_axis(a1,a2,as,ab,ax,ay,al,il,ic)
sm_box(x1,y1,x2,y2)
sm_conn(x,y,n)
sm_ctype(c)
sm_curs(x,y,k)
sm_defvar(str1,str2)
sm_device(str)
sm_dot
sm_draw(x,y)
sm_erase
sm_errorbar(x,y,e,k,n)
sm_expand(e)
sm_format(xf,yf)
sm_gflush
sm_graphics
sm_grelocate(x,y)
sm_grid(i)
sm_hardcopy
sm_histogram(x,y,n)
sm_identification(str)
sm_label(str)
sm_limits(x1,x2,y1,y2)
sm_location(x1,x2,y1,y2)
sm_ltype(lt)
sm_lweight(lw)
sm_notation(xl,xh,yl,yh)
sm_plotsym(x,y,n,sym,ns)
sm_points(x,y,n)
sm_ptype(pp,n)
sm_putlabel(i,str)
sm_redraw(i)
sm_relocate(x,y)
sm_shade(delta,x,y,n,type)
sm_ticksize(xs,xb,ys,yb)
sm_window(nx,ny,x,y,x2,y2)
sm_toscreen(ux,uy,sx,sy)
sm_xlabel(str)
sm_ylabel(str)
sm_device returns 0 if it opens the requested device, or -1 if it
fails. You must declare sm_device as returning integer, of course.
sm_curs returns after you hit any key, returning the coordinates of
the point, and the key struck as an integer (e.g. `a' as 97 in ascii).
In C, all three arguments are pointers, two to float and one to int.
sm_defvar defines a variable, it is identical to the interactive command
DEFINE name value. It is only used to certain define variables that are
significant to SM, currently file_type, graphcap,
TeX_strings, x_gutter, and y_gutter.
Fortran users might or might not need to double \s in TeX labels,
depending on the foibles of your compilers (you are more
likely to need the doubled \\ under unix).
sm_plotsym is like first
using the PTYPE { ... } command to define sym, and then
POINTS to plot x against y. The sym array consists of triples
of integers, (move x y) where move is 1 to move the plot pointer,
0 otherwise. This is not quite the same as the interactive command,
but doesn't involve any characters. The move integer may not be omitted.
The `file descriptor' that sm_redraw demands will almost always be
0 (standard input) on unix boxes; if it is needed by VMS or
other operating systems I will add a note here when I write the device driver.
sm_shade shades the inside of the curve specified by x and y if type is
1, or the area below a histogram specified by x and y if type is 2.
The line spacing in screen coordinates is delta.
You can use sm_toscreen to convert from user to screen coordinates
(which run from 0 to 32767, and are used by sm_grelocate).
The second pair of arguments should be pointers to int if called from C.
sm_window takes an extra two arguments over the interactive version;
they are the values that you'd specify using the #.# form of
window specification; for example, WINDOW 4 5 1 2.3 corresponds to
the call sm_window(4,5,1,2,1,3).
The functions to manipulate 2-dimensional data are described in the next section.
The use of the 2-D functions may require a little more explanation.
If you want to use SM to read your data,
producing contour plots is very similar to doing so interactively, with the
difference that instead of defining the variable file_type, you
must call the function sm_filetype with the desired value as the
argument before reading the data. This is equivalent to calling
sm_defvar to
define the variable file_type, and is only supported for
backwards compatibility.
As an alternative, you can fill your own data array,
and pass it to SM to be contoured with sm_defimage.
The function calls follow (again in fortran), followed by some explanation.
sm_contour
sm_defimage(arr,x1,x2,y1,y2,nx,ny)
sm_delimage
sm_filetype(type)
sm_levels(l,n)
sm_minmax(x,y)
sm_readimage(file,x1,x2,y1,y2)
The translation to other languages is not quite as simple as above.
In C, sm_minmax expects to be passed pointers to floats, and
the first argument to sm_defimage is not a 2-D array, but an array of
pointers to the rows of the image. x1, x2, y1, y2 specify the limits
as in the interactive IMAGE command; if they are set to be 0.0 then
the dimensions of the array will be used. As an example, again in non-VMS
fortran:
integer NXY
parameter (NXY=20)
integer sm_device
integer i,j
real z(NXY,NXY),lev(NXY)
c
do 2 i=1,NXY
do 1 i=1,NXY
x(i,j) = (i-NXY/2)**2 + (j-1)**2
1 continue
2 continue
c
if(sm_device('postscript latypus') .ne. 0) then
print *,'Can"t open printer'
stop
endif
call sm_graphics
call sm_limits(-1.,21.,-1.,21.)
call sm_box(1,2,0,0)
call sm_defimage(z,0.,20.,0.,20.,NXY,NXY)
call sm_minmax(amin,amax)
do i=1,NXY
lev(i)=amin + (i - 1.)/(NXY - 1)*(amax - amin)
3 continue
call sm_levels(lev,NXY)
call sm_contour
call sm_hardcopy
call sm_alpha
end
It is also possible to call the SM parser directly from your own programmes. This is usually done by people who have large data arrays that they want to plot, so we have also provided a call to define your arrays as SM vectors. To use this, your SM guru must build an extra library, (called `libparser.a' on unix systems). This is done with the command
make Parser
in the top level SM source directory. If this has been done, you can link
the parser into your code by including the flag -lparser before
the other SM libraries (but after any -L flags). The programmes
`interp' and `finterp' in the `callable' directory provide
examples of calling SM's parser; `finterp.f' looks like:
real a(10)
integer i
do 1 i=1,10
a(i) = i
1 continue
call sm_array_to_vector(a,10,'xyz')
print *,'Calling SM parser...'
call sm_parser('-q')
print *,'Exited parser'
end
Here the array a (with 10 elements) is to be called xyz in SM,
and SM is to be invoked with the command line options -q. You can of
course define as many vectors as you feel like, but using DELETE to
delete them from the parser is a serious error (i.e. don't!).
sm_array_to_vector(arr,n,name)
sm_parser(args)
array_to_vector) (character args).
sm_alpha, sm_graphics, sm_gflush, and sm_redraw?
The question sometimes comes up, `Why do I have to bother with these
calls sm_alpha, sm_graphics, sm_gflush, and
sm_redraw? After all, other graphics libraries don't require them.'
The answer is that it all depends on how sophisticated and device dependent you want to be.
If you are using something like an xterm to plot to, then yes,
you do need to explicitly switch between graphics and alpha screens --
or else you'll get gibberish on the text screen (something like
`?_"s#M"s>H"s#M#b#M!s#F!s#F0"s$X") and no graphics on the
graphics screen, or else your graphs and your input commands (and some
gibberish to handle the screen editor) mixed together on the graphics screen.
Of course the SM routines could have done the switches for you,
but if the mode were
changed everytime that it might need switching, you'd get an immense
amount of mode switching. Why would that matter? Try running the code on
a terminal like a vt240 that physically has only one screen.
If you didn't care about devices like xterm and were determined
to only use devices like x11 or sgi or postscript
you wouldn't have to worry about any of this.
Next to gflush. For efficiency, SM doesn't guarantee that every
write request is actually written to the screen when received, so at the
end of a function call there could be lines or points that haven't yet
reached the screen. gflush deals with this by flushing the
graphics buffers, thus
ensuring that all pending writes to the screen are completed. If you
don't care about possible missing points, don't worry about it.
Finally redraw. On a traditional terminal such as an I^2S or a
tektronics 4010, there was no need to worry about other windows
appearing on top of your beautiful graphics window and erasing what was
there; for some devices such as xterm there is no problem as the
terminal emulator is always looking at the screen, like a faithful dog,
waiting for you to type something or for one of its screens to get
corrupted. In the old days of sunview, Sun had hacked the lowest level
system calls (read etc.) to allow an application programme to call the
equivalent of redraw automatically (well, under a reasonably
common set of conditions). But if you are running on something like an
X11 display or a GL display on an SGI, there is no-one looking
out for events that erase your window, and you have to do it
yourself. If you don't care about refreshing the screen when raised to
the top of the window stack, there's no need to worry about redraw.
So all these complications with alpha, redraw, and friends
are due to SM being designed to handle all the possible nasty cases. If
you use SM interactively, it is all done for you, but if you want to
call SM functions you have to decide how much responsibility you want to
take.
Of course, you can write a trivial wrapper about each SM primitive that
calls sm_graphics before calling (e.g.) sm_points, then
sm_gflush and
sm_alpha just before returning.
In a similar way, you could write a general i/o function that calls
sm_gflush
then sm_alpha, then prompts you, then calls sm_redraw,
then reads the reply, then calls sm_graphics. But I don't think that
the SM primitives should do it for you.
As discussed above, the names used for SM callable functions from fortran
are, in general, different from those used in (almost?) all other languages,
as fortran's calling conventions are different, so we have to provide interface
code (in the file `sm/src/plotsub/interface.c'). The SM function
sm_func may be called as AAAsm_funcBBB where AAA and
BBB are the values of the C-preprocessor macros FORTRAN_PREPEND
and FORTRAN_APPEND respectively; their default values are nothing and
_. If you want to change this, edit sm/src/options.h and
define the values that you desire. Note: with C compilers that don't adhere
to the C89 ansi standard, you are restricted to the values f and
_ for PRE- and APPEND.
After changing options.h (or options.skl if you want your
changes to survive the running of `set_opts'), rebuild SM and you
should be in business.
Previous editions of this manual included a printed copy of the SM
grammar, prepared directly from the source code using the get_grammar
utility.
This was helpful in understanding otherwise bizarre objections to your
favoured command, as it specified exactly what SM would accept,
but used a lot of paper. We have therefore omitted it from this edition,
but you are welcome to make yourself a copy.
There is limited support for 2-dimensional graphics in SM,
and we do not expect to make many extensions in this area. Currently it is
possible to read an unformatted image, to draw contours, to generate
a Floyd-Steinberg dithered image, to extract values
from the image into vectors, and to obtain image values with a cursor.
Previous editions of this manual (prior to version 2.3) have traditionally
said: `We expect to support half-tone imaging in the nearish future';
as of this release there is a macro (called greyscale and written
by Gabor Toth here at Princeton) that draws reasonably efficient
grey scale images on any supported output device.
The problem of specifying data formats for images is difficult, and we have
adopted an approach of using a `filecap' file to describe the unformatted
files. This allows the user to write the file using C or Fortran (or,
presumably, lisp) and then use SM to read the data.
The name of the entry in the filecap file is given by the
variable file_type, which may be given in your `.sm' file
if you use the standard startup macro.
The format of data on disk is first the x- and y- dimensions of the
image, then the data in
row-ascending order. The exact statements used to write the data may depend
on the chosen value of file_type (or vice-versa). A description
of the filecap fields comes at the end of this appendix; most
users should never have to change this file.
It is also possible to read data from a formatted file and create an image
inside SM, for example if x and y are integers in the
range 0-9:
IMAGE (10,10) # declare a 10*10 array
READ { x 1 y 2 vals 3 } # read some data
SET IMAGE(x,y) = vals # and put it into the image
The command used to read an image is IMAGE, and you may optionally
specify the x and y range covered by the data
(e.g. IMAGE datafile xmin xmax ymin ymax; this also works with
declarations like the one in the previous paragraph). Only the region of the
image lying within the current limits is plotted when contouring - just
like any other graphics operation in SM. Images may be deleted
with the DELETE IMAGE command. To extract values from an image,
use the special expression IMAGE(vec_x,vec_y) which has the
value of the image at the points (vec_x,vec_y).
You can extract variables from the image's header using the command
DEFINE name IMAGE. See the discussion of filecap if you need
to know what variables can be retrieved this way.
If you want to use non-interactive SM (i.e. write your own command
interpreter), there is a call (defimage) to define your data array
to the contouring package, but this is of course not available interactively.
Filecap is similar in graphcap or the Unix termcap, and in fact the
`filecap' entries may be physically in the same file as graphcap (i.e.
added as if they were graphcap entries; of course an attempt to say
device fits will lead to confusion...).
Filecap can be specified as a list of files to be searched in order.
The fields in filecap are used to specify the data type of the file,
the record format of the file (record-length, inter-record gaps,
etc.) and the header used (length of header, offset of desired items).
Note that these are `unformatted' files, as written by C write() or
fortran write(num) statements.
The current filecap is as follows:
#
# Filecap describes unformatted file formats to SM. The syntax is
# identical to termcap or graphcap files.
#
c|C|C files:\
:HS#8:nx#0:ny#4:
ch|CH|C files with headers:\
:HS#24:nx#0:ny#4:x0#8:x1#12:y0#16:y1#20:
fits|cfits|FITS|CFITS|C FITS files:\
:DA=fits:RL=2880:
no_header|Files with no header, will prompt for nx, ny:\
:HS#0:
unix|UNIX|Fortran unformatted files under Unix on a Vax:\
:HS#-1:nx#0:ny#4:RS#4:RL#-1:RE#4:
unix_int|UNIX_INT|Like unix, but integer*4:\
:DA=int:tc=unix:
unix_short|UNIX_SHORT|Like unix, but integer*2:\
:DA=short:tc=unix:
vms_var|VMS_VAR|Fortran unformatted under VMS, recordtype=variable:\
:HS#8:nx#0:ny#4:
vms_fixed|VMS_FIXED|Fortran unformatted under VMS, recordtype=fixed:\
:HS#-1:RS#0:RL#-1:REnx#0:ny#4:
# This seems to be correct, based on one example
vms_direct:VMS_DIRECT|Fortran unformatted, direct access, under vms:\
:HS#8:RS#0:RE#0:nx#0:nx#4:
Comment lines start with a #, continuation lines start with white space
(a tab or a space) and \ may be used to continue an entry on to the
next line.
The first few fields in an entry are separated by |, and are alternative
names for the same entry. For example,
fortran unformatted files under Unix may be referred to as unix or
UNIX. Fields are separated by colons, and are of the form
:CC#nnn: for numbers, and :SS=str: for strings. Omitted fields
may be specified as :CC@nnn:, or simply omitted.
Filecap capabilities currently used are:
DA (DAta type)
FS (File Start)
HS (Header Size)
RE (Record End)
NS (No Swap)
RL (Record Length)
RS (Record Start)
SW (SWap)
nx (Number X)
ny (Number Y)
x0 (X 0)
x1 (X 1)
y0 (Y 0)
y1 (Y 1)
In addition, HH is supported as an archaic form of HS.
In terms of these quantities a file will look like this:
As mentioned below, HS can be negative and is then taken as the record
length of the header RL_H, in which case the file will be
like:
Note that the first real data record begins with an RS -- this means
that
if you are writing fortran you must write the header in a
different write statement than the one that you use to write the data
(i.e. write(fd) nx,ny,arr will not work).
Most parameters are optional, and will default to 0. If RE or RS is specified, you must give RL as well. If you specify it as negative, then we'll look for it from the operating system. You must provide a value for HS (or HH if you're old fashioned), if it is negative we'll assume that even the header has a record structure, with RS and RE just like any other record. Its record length will be taken to be RL, if RL is positive, otherwise we'll find it from the operating system. If HS and RL are both negative there is no reason why the record length of the header should be the same as that of the data.
If neither nx nor ny
are present in the graphcap entry you will
be prompted(31)
for the x and y dimensions of the file, otherwise they
must both be present.
If they are negative
they are taken to be the negation of the true dimension (so a filecap entry
:ny#-6: specifies that the y-dimension of the data is 6), but usually
they are taken as the offsets of the values of the x and y dimensions in
the file, relative to the start of the header (if you set HS to be zero
you can specify nx and ny on the command line, but they must be on a
separate line, either a real separate line, or following a \n). Note
that HS excludes
FS, so HS will usually be 2*sizeof(int) irrespective of the value of
FS, and nx will usually be 0. If HS is negative, then the nx and ny
offsets also ignore RS,
i.e. nx is still usually 0.
As an alternative to specifying the actual file sizes as negative
integer values of nx or ny (e.g. :nx#-10:) you can simply specify
them as positive string values: :nx=10:. Note how this differs
from :nx#6:.
Possibilities for DA are char, float (default),
int,
long, and short, all as in C, and also fits for FITS
format data. (If you don't know what FITS format is, don't worry about
it. It's a style of header and record structure used for data
transport in astronomy(32).
If you do know about FITS, then we assume that
each record is 2880 by long, although possibly with RS and RE non-zero.
The header is processed for the size of the file and the value of
BITPIX. If the file is not SIMPLE, it is taken to be 4 by floating
point data. CRPIX, CRVAL, and CDELT
are interpreted correctly, as are BZERO and BSCALE. X0, x1, y0,
and y1 are specified as the keywords X0, X1, Y0, and Y1 respectively,
and take preference over CRPIX etc.
FITS files on a machine with vax byte order are supposed to be byte
swapped, but you can override this by specifying the NS capability
which stops SM from doing any byte swapping).
If you specify SW it takes preference over any other instructions
about byte swapping and forces byte-swapping for 2 byte (short) data,
and byte-and-word swapping for 4 byte (int) data.
If x0 and x1, or y0 and y1 are omitted,
the range of
values on the appropriate axis is taken to be 0 to nx-1 (or ny-1).
You can override these values with the IMAGE command.
The values of X0 (and so on) can be obtained directly using
DEFINE X0 IMAGE; Other values can be specified in `filecap',
for example a filecap entry :aa#24: specifies that
DEFINE aa IMAGE should recover the (floating-point) number stored
at byte offset 24. Because of the way that filecap files work, you are
restricted to two-character names.
If you are using FITS files, all the keywords from the header are
available, but you should remember that SM is case sensitive.
For example, suppose I wrote a file using the Unix f77 compiler, with some code that looked like:
integer *4 nx,ny,arr(10,20)
c
nx = 10
ny = 20
write(8) nx,ny
write(8) arr
(Omitting opening unit 8 as an unformatted file, and filling
the array with data). Then I could read it in SM by defining
file_type
to be unix_int. The filecap entry indicates that the length of the header
is to be obtained from unix (in fact from the file, it'll be the
record length of the header), as is the record length.
Both the start-of-record (RS) and end-of-record (RE) gaps are 4 by
long (they in fact contain the record length, but you needn't know that).
The number of x-records is at zero offset in the file,
and y-records is at offset 4. In other words, allowing for FS being 0 and
RS being 4,
nx occupies bytes 4-7 in the file, and ny 8-11.
The data is taken to be 4-byte integers (integer*4 to fortran).
In fact, the first record consists of only the values of
nx and ny, so the record length of the first record is 8 by, and part
of an equivalent filecap entry would be
:FS#4:HS#12:
where I have interpreted RS as FS, and added the RE onto the end of the header length HS. If I had written the data out line-by-line in a do loop, the file type would still be unix_int, but the record length actually used by SM in reading the file would be different (see section Image).
As another example, I use an image processing system called Wolf that used to use files with the arcane structure of 200 bytes of header, then the x- and y-size of the file, given as ints, then more header up to a total offset of 1024 bytes from the start of the file, then the data written as short integers. I could write a header with code that looked somewhat like (omitting all error checking):
int fd,i;
int xs = 20,ys = 10;
short arr[10][20];
...
lseek(fd,200L,0);
write(fd,(char *)&xs,sizeof(int);
write(fd,(char *)&ys,sizeof(int);
lseek(fd,1024L,0);
for(i = 0;i < ys;i++) write(fd,(char *)arr[i],xs*sizeof(short));
The corresponding filecap entry is
wolf|Wolf-IfA files:\
:HS#1024:nx#200:ny#204:DA=short:
which is pretty simple really.
Termcap is a Unix idea, that makes it possible to write software that
runs on a wide range of different terminals. The implementation that
SM uses contains no Unix source code. The idea is similar to
graphcap or filecap -- each property of the terminal is given as a
field in a file. For example, the string to clear to end of line is
called ce, and for ansi terminals is given as ce=^[[K. The
format for termcap files is described under
`graphcap', which is identical. Our termcap is identical to the Unix
one, so for details see section 5 of the Unix manual. The file to use
as a termcap file is specified as termcap in your `.sm'
file, or as the environment variable TERMCAP (logical variable
to VMS). If SM can't open TERMCAP as a file, it is
taken to be the value of the entry for your terminal. If it doesn't
begin with a colon, it is treated like a termcap file, and searched
for the desired terminal. If it does begin with a colon, it is simply
taken to be the termcap entry to use.
A TERMCAP
variable takes precedence over an entry in your `.sm' file. As for
graphcap, a list of termcap files can be specified, to be searched in order.
A full termcap entry can be pretty long, but fortunately SM only uses a small subset. (The rest are needed by full scale screen editors, but we haven't written one.) In particular we use:
ce (Clear End)
ch (Cursor Horizontal)
cm (Cursor Movement)
cn (ColumNs)
dc (Delete Character)
is (InitialiSe)
ks (Keypad Start)
ks (Keypad End)
kd (Kursor Down)
kl (Kursor Left)
kr (Kursor Right)
ku (Kursor Up)
k1 (Key 1)
k2, k3, and k4)
li (Lines)
pc (Pad Character)
All of these are strings except co and li which expect
numbers, and pc which expects a single character.
The editor uses all of these except co, although the
k ones are only provided as a convenience to you, mapping the
arrow keys. In fact, these arrow keys may supersede desired bindings
such as ^K to delete to end of line (e.g. on a televideo 912). If
this happens, use the EDIT or READ EDIT commands to fix
things up.
The cursor addressing strings ch and cm use a variant of
the C printf %. Consider the string as a function, with a stack on
which are pushed first the desired column, and then the desired line
(so the line is on top).
The possible % formats operate on the stack, and pop it after output.
d
2
3
.
+x
>xy
r
i
%
We do not support %n, %B, or %D out of pure laziness.
A couple of examples might help. To go to (5,60) a vt100 expects to receive
the string ^[[5;60H, so the termcap entry reads
:cm=\E[%i%d;%dH:. We need the %i to go to one-indexed
coordinates, and the escape is written \E. The :'s delimit
the termcap entry. A Televideo 912
expects to be given first ^[=, then
the coordinates as characters, where ` ' is 0, `!'
is 1, and so forth through the ascii character set. To convert a given
number to this form we add ` ', so the termcap entry is
:cm=\E=%+ %+ :.
If an entry begins with a number, it gives the number of milliseconds
of padding that the terminal requires (it is optionally followed by an
*, which we can and do ignore). If the terminal requires a pad character
that is not simply ascii NIL it should be given as pc, so
to use `a' as a pad character specify :pc#97:. The
baudrate is taken to be 9600, unless you specify it as the ttybaud
variable in your `.sm' file. We have never yet seen SM
have any trouble with padding, so we wouldn't worry about any of this
if we were you.
You might wonder why we need both ch and cm. The simple
answer is that we don't, but that we do want one of them. Because cm moves the cursor to a specific line, its use requires that
SM remember which line you are on which can be tough what with
switching to and from graphics mode. We therefore assume that you are
on the last line of the terminal, as set by li or explicitly by
the TERMTYPE command. If you provided ch this problem
wouldn't arise but many terminals don't support such a capability and
we have to code around this deficiency. To summarise: use ch if
you can, failing that use cm.
There is a problem with using the last line of the screen as the
SM command line, and this is that some terminals use the same
screen for graphics as for text, and your graph will merrily scroll
away as you type. The graphcap GD (Graphics Disable) command can
be set to take you to the top of the screen, but cm won't keep
you there. Your only chance is to disable cursor motion, either by
setting
ch to `disabled', or by specifying a negative screen size to
TERMTYPE which has the same effect. You can still use the
editor, but it'll be slower. If you still have trouble, try
TERMTYPE dumb, which really is a bit brain damaged, or
TERMTYPE none which disables the editor entirely.
As we have discussed extensively (see section The Stdgraph Graphics Kernel), most plotting devices can be accommodated within the framework of stdgraph/graphcap without writing a line of C; raster devices can use the raster device driver. Sometimes, however, this is not possible and you must write a real driver. An example would be making SM run native under X11; running under a terminal emulator can be done with graphcap.
If you do make any changes, write any new drivers, or modify graphcap (except trivially) please send us copies of your modifications.
Let us assume that you really do need a new driver, how do you go
about it?
SM communicates with devices solely through a graphcap entry,
some external variables
(defined in sm.h) and a structure called
DEVICES which is defined in devices.h and declared in
plotsub/devices.c.
When you issue a DEVICE devname args
command, the stdgraph driver is called with the string devname args,
and it opens the graphcap entry for devname. If it finds an entry
of the form :DV=driver: it tries to find a hardcoded device driver
called "driver" (you'll see in a moment where the names come
from). If it can find such a driver it calls its setup function (to
be defined in a moment) with args as an argument. Your device
driver is now in charge, and no more stdgraph routines are called
until your device is closed; after you return from your close function
stdgraph's close function is called to close the graphcap descriptor.
What do you have to remember from this paragraph? That you need a
graphcap entry, if only a trivial one along the lines of
my_device|all_mine:DV=my_driver:
which associates the driver my_driver with the device referred
to as either my_device or all_mine. Because stdgraph's
stg_open and stg_close functions are called around your
device code
the standard graphcap support for OW, IF, DC, OF,
SY, and CW is automatically provided if you want it; see
the imagen
driver and graphcap entries for an example. You might also want to
remember that you can directly access any graphcap entry for your
device from within your driver, using the functions ttygets,
ttygetr, ttygeti, and ttygetb.
The current definition of DEVICES looks like this:
typedef struct { /* output device dependent functions */
char d_name[40]; /* name of device */
int (*dev_setup)(); /* initialisation */
void (*dev_enable)(), /* enable graphics */
void (*dev_line)(), /* draw a line from x1, y1 to x2, y2 */
void (*dev_reloc)(), /* relocate current position */
void (*dev_draw)(), /* draw a line from current pos to x,y */
int (*dev_char)(), /* hardware characters */
int (*dev_ltype)(), /* hardware line type */
int (*dev_lweight)(), /* hardware line weight */
void (*dev_erase)(), /* erase graphics screen */
void (*dev_idle)(), /* switch from graph to alpha mode */
int (*dev_cursor)(), /* cursor display */
void (*dev_close)(), /* close device */
int (*dev_dot)(), /* draw a single pixel dot */
int (*dev_fill_pt)(), /* draw a filled point */
int (*dev_fill_polygon)(), /* draw a filled polygon */
void (*dev_ctype)(), /* set colour of line */
int (*dev_set_ctype)(), /* set possible colours of lines */
void (*dev_gflush)(); /* flush graphics */
void (*dev_page)(); /* start a new page */
void (*dev_redraw)(); /* redraw the screen, if need be */
} DEVICES;
Note that this is the DEVICES structure at the time of writing;
be sure to see that it is still correct when you write your driver!
To add a new device to SM, all you have to do is to write functions
to fill as many of the slots in DEVICES as possible, add their
definitions to `sm_declare.h', then add an
element to the array devices[] in devices.c. After recompiling, SM will
recognise your device by the name d_name that you gave it in devices[].
Traditionally the functions have the
same name as those given above with the dev replaced by some
mnemonic for your new device. You should surround both your driver and
the entry in devices[] with #ifdef's so that all I have to do to
totally lose your device is to not #define it to the compiler. In writing
your device you may want to use graphcap to give it various pieces
of information. This can be done; see the imagen driver for an example.
You should include the file `sm_declare.h' (which automatically includes `options.h') which provides declarations for all SM functions (including prototypes if the compiler supports them), and you should add your own declarations to this file.
If you don't provide some of the functions, SM will try to
emulate them in its software. For example, of your ltype function returns
-1, then we will chop your lines for you. There are a set of functions
for nodevice that have the correct types, you can use them for
functions that you don't want to provide (e.g. if you don't support
dev_ltype you can use no_ltype).
Some of the routines, e.g. dev_char
are sometimes passed NULL arguments to inquire if they can provide
particular capabilities. In the following paragraphs we discuss all
the functions, their arguments, what they do, and what they return.
All coordinates are given in screen units, where the device is 32768 pixels along a side.
dev_setup(str) (char *str;)
DEVICE
command was DEVICE newdev abcde zyxwvut, setup will be passed
abcde zyxwvut. If the device can't be opened for some reason
setup should return -1, otherwise 0. Setup has a number of
book-keeping responsibilities: setting the value of termout to 1 if
the device is a terminal, otherwise 0; setting the value of ldef to
the maximum spacing between lines if they are to appear as one
thick line rather than a set of parallel ones, (used if we have to
emulate LWEIGHT); setting the variables xscreen_to_pix and
yscreen_to_pix to give the conversion from SCREEN to device coordinates;
calling default_ctype with the name of the default
colour for lines (e.g. default_ctype("white")). There is no need
to look in the `.sm' file for a foreground entry, this is
done for you (by set_dev()) after dev_setup returns, but you should
deal with any background entry if you feel so inclined.
There is a function parse_color
that converts a colour name into r, g, and b values (see the sunwindows
setup function for an example of its use) that may help.
Note that you should not call stg_setup -- this was done for you
automatically before your setup function was called. No return value.
dev_enable()
dev_line(x1,y1,x2,y2) (int x1, y1, x2, y2;)
dev_reloc(x,y) (int x,y;)
dev_draw(x,y) (int x,y;)
dev_char(str,x,y) (char *str; int x,y;)
dev_ltype(i) (int i;)
LTYPE to be i, as in the
interactive command.
Return 0 if you support the ltype asked for, otherwise -1. If you can't support
a linetype, make sure that you are drawing solid lines, so that
SM can emulate it for you. LTYPE 10 is special, it is
generated by the LTYPE ERASE command, and asks you to delete lines
as they are drawn, LTYPE 11 is used to indicate the end of LTYPE ERASE mode.
dev_lweight(lw) (real lw;)
LWEIGHT to be lw, as in the interactive command. Lines
with lweight 0 should be the natural thickness of a line on your
device, higher lweight lines are usually drawn with a thickness
of LDEF*lweight in SCREEN coordinates. Return 0 if you support
the lweight asked for, otherwise -1.
dev_erase()
dev_idle()
dev_cursor(x,y) (int *x,*y)
dev_close()
stg_close -- this is done for you
automatically. No return value.
dev_dot(x,y) (int x,y)
DOT command).
You'll have to do the move to (x,y)
yourself. Return 0 if you can draw dots, otherwise -1.
dev_fill_pt(n) (int n;)
PTYPE n 3 points.
Remember to allow for EXPAND and ANGLE.
Return 0 if you can draw the point, otherwise -1;
dev_fill_polygon(n) (int style; float *x, *y; int n;)
x and y (a total of n)
points) in the style style (0: filled; all other values are currently
undefined). Return 0 if you can fill polygons in the desired style,
otherwise -1. You may get queries with on style with n == 0.
dev_ctype(r,g,b) (int r,g,b;)
dev_set_ctype(col,n) (COLOR *col; int n;)
dev_gflush()
dev_page()
dev_redraw(fildes) (int fd)
get1char() before it tries to read a
character from SM's standard input (file descriptor fd).
It is intended for the use of drivers that need to keep an
eye on `their' window. For example, the Silicon Graphics driver needs
to redraw a window every time that it is moved. Such device drivers
will probably want to poll fildes (e.g. using select())
to see that there really is input before returning; look at
the sgi or x11 driver if you want an example.
No return value.
If you are still confused, look at some of the hardware drivers that are already available.
Porting to new Unix machines should be relatively simple. If the
machine runs BSD Unix the only changes that should be necessary are
to the exception handler routines in main.c, to reflect the error
conditions signalled by your new machine. For a Sys V Unix, you'll have
to edit `options.h' to define SYS_V.
Otherwise, your only
problems should be hidden bugs which flourish in new architectures.
Otherwise there isn't all that much that I can tell you. SM runs on Unix (BSD and SysV) and VMS machines, and the places where the code is #ifdef'd for those operating systems is your best bet for places where there are machine dependencies. That said, there are a few obvious problem areas.
First consider get1char, which is the routine that reads terminal input. It has to be able to get one character a time from the terminal, and not interpret control characters. This is obviously operating system dependent (CBREAK under 4.3BSD, PASSALL under VMS, ...). Get1char expects to be passed ^A to start operations, and EOF to end them. Anything else means `return a character please'. The terminal output functions used by stdgraph are also machine dependent, for example they use QIO calls under VMS.
As mentioned above, the exception handlers try to interpret the exceptions that they receive and this may require a little modification in main.c.
Hardcopy devices need the system() system call to send commands to the operating system, and if you don't have one you are in trouble.
In a few places SM needs to make assumptions about how file
names are put together, and these will need changing. On a similar
note, some operating systems (e.g. VMS) are very picky about opening
files and you may have to be careful. Note the use of the DT
graphcap capability to signal particular requirements to the programme.
A machine that didn't use ascii would be rather a nuisance as makeyyl assumes that the characters for A-Z are contiguous, and although this could be easily fixed I am not sure that other problems wouldn't arise.
A final rather horrifying thought is that Bison might not compile on a machine that doesn't have YACC as an alternative. I don't know how to fix that, you'd just have to hope for the best, or else run Bison/YACC on a different machine and copy over control.c.
This appendix describes the collection of useful macros, written by a variety
of people, that are to be found in the default macro directory, i.e.
the directory pointed to by the macro entry in your `.sm' file. If ever you write a useful (or simply clever) macro, why
not send it to us for inclusion in the next version of SM?
The macros are arranged in a number of files which may be read using the
load macro. For example, to load the file fonts type
load fonts. To forget a set of definitions, use unload.
Under Unix, there is a macro lsm that can be used to list the
contents of macro libraries, e.g. lsm utils. It is more-or-less
complete depending on the value of VERBOSE, just like LIST MACRO.
The list that follows gives the name and a one-line synopsis of all the macros
in the default files as of the date of this manual. The full text of any macro
may be examined via the help <macroname> command in SM; the
default files may be printed for those who desire a hardcopy.
file `abbrev' in directory `macro' (This is a file of all unambiguous abbreviations of keywords. It is created using the shell script `abbrev' in the main SM directory. Its use may interfere with cunning macros, and is not recommended).
file `cover' in directory `macro'
cover # draw the cover
file `default' in directory `macro'
batch ## run the history buffer, but don't delete from history list bell ## ring terminal bell calc ## evaluate an expression cd ## change directories compatible ## define macros to be compatible with Mongo declare ## declare a vector $1: declare name size del ## delete last command from history list del1 ## don't put command on history list echo ## write to terminal ed ## edit a macro, or the previous one if no argument edit_all ## edit history buffer emacs_all ## edit the history list using an external editor. emacs_hist ## edit the history list using an external editor. error_handler ## Handle ^C interrupts execute # read and execute an SM file of commands (1 per line) extend_history # Extend history buffer to be of size $1 for # Repeat a macro while a condition is true, like C's for(;;) gripe ## complain to Robert (Unix only) h ## get help head ## print the top of the current data (or other) file hm ## help with the last macro edited hv ## help with variable insert ## insert text after line $1 load ## load macros in default directory load2 ## load macros in (second) default directory ls ## list macros lsm ## list macros in a file in default macro directory lsv ## list variables q ## check, then quit re ## macro read reset_ctype # Reset the default ctypes (except "default") repeat # Repeat a macro `name' while the condition is true sav ## save to a file $1, don't save from files `$mfiles' show ## show current values of various things startup ## macro invoked upon startup undef ## undefine a variable undo ## undo [macro] : undo (i.e. erase lines drawn by) macro $1 unload ## forget macros from a file v ## set verbosity wr ## macro write
file `demos' in directory `macro'
square # sum the Fourier series for a square wave, using $1 terms colours # draw a circle in a number of colours until ^C stops it crings # draw a set of coloured circles gauss_convolve # convolve 2 Gaussians with sigmas $1 and $2 grey_sincos # draw a grey scale image of a sin(x)cos(y) surface scribble # use cursor to draw a line, and then shade the interior shading # draw an ammonite sundial # draw a sundial, allowing for the analemma
file `fonts' in directory `macro'
fonts # draw the font table TeX_defs # draw the "TeX" definitions make_char # help create a new character
file `fourier' in directory `macro'
# # Macros to make dealing with complex numbers for FFT's easier # Assumes that complex vector `name' is represented by two vectors # called name_r and name_i # fft # Direct FFT: fft name name ifft # Inverse FFT: ifft name name cadd # Add complex numbers: $1 = $2 + $3 cdiv # Divide complex numbers: $1 = $2/$3 cmod # Modulus: $1 = |$2| cmult # Multiply complex numbers: $1 = $2*$3 csub # Subtract complex numbers: $1 = $2 - $3 imag # Imaginary part: $1 = Im($2) real # Real part: $1 = Re($2) vcentre # shift a vector so that its 0th element appears in the middle vcenter # an alias for vcentre
file `images' in directory `macro'
hist_equalise # Histogram equalise an image gamma # Gamma correct an image
file `irregular' in directory `macro' These macros were written by Gabor Toth, to whom many thanks. They enable you to define an SM IMAGE from 3 vectors, (x,y) and the value
irregular_image # Set IMAGE from 3 vectors (x,y) and z regulargrid # Determine x and y coordinates for an nx*ny enclosing grid t triangulate # Determine regular grid & interpolation weights trigrid # Interpolate to the regular grid trigridtry # Demonstrate the use of trigrid
file `math' in directory `macro'
# # Written by Daniel Pfenniger (PFENNIGER@obs.unige.ch) # `A&S' is Abramowitz & Stegun # J0 # Bessel func. J0(|x|), A&S, 9.4.1,.3 Y0 # Bessel func. Y0(|x|), |x|>0, A&S, 9.4.2-3 J1 # Bessel func. J1(|x|), A&S, 9.4.4,.6 Y1 # Bessel func. Y1(|x|), |x|>0, A&S, 9.4.5-6 I0 # Modified Bessel func. I0(|x|), A&S, 9.8.1-2 I1 # Modified Bessel func. I1(|x|), A&S, 9.8.3-4 K0 # Modified Bessel func. K0(|x|), |x|>0, A&S, 9.8.5-6 K1 # Modified Bessel func. K1(|x|), |x|>0, A&S, 9.8.7-8 K # Complete elliptic integral K(m), 0<=m<1, A&S 17.3.34 E # Complete elliptic integral E(m), 0<=m<=1, A&S 17.3.36 fresnel # Calculate the Fresnel integrals C, S, and F
file `matrix' in directory `macro'
matrix # Introduction to matrix utilities mdeclare # declare $1 to be a $2x$2 square matrix mdimen # print matrix $1's dimension mprint # print the matrix $1 (given as $1_0 $1_1 etc -- see minv) madd # set matrix $1 equal to $2+$3 minv # Quick matrix inversion, done in place. mset # set matrix $1 equal to $2 mmult # set matrix $1 equal to $2*$3 mmult_c # set matrix $1 equal to $2*$3 where $3 is a scalar mmult_v # set vector $1 equal to $2*$3 where $3 is a vector mmult_vT # set vector $1 equal to $2^T*$3 where $2 is a vector
file `mongo' in directory `macro'
(omitting those that are simply abbreviations, like ang)
da ## set data file dev ## set device dra ## draw, accepting expressions ecolumn ## define vector error_col as error vector end ## quit, not on history era ## erase screen, not on history lis ## list history, not on history hard ## make a hardcopy of what you type (or get by history) hardcopy ## close the old device and set dev type to 0 hcopy ## hcopy [printer] [l1] [l2]: Make hardcopy of playback buffer hmacro ## hmacro [macro] [printer]: make hardcopy of `macro' on `printer' identification ## write an id to the top right hand corner of screen input ## execute an Mongo file mongo # make SM resemble Mongo as closely as possible pcolumn ## set point column (Mongo) playback ## define "all" from buffer, and run it read_all ## read a macro file, putting `all' onto the history buffer read_hist ## read history from a file read_old ## read an Mongo file onto the history buffer rel ## relocate, accepting expressions save_all ## write the playback list to a file (use sav instead) terminal ## device toplabel ## put label at top of plot xcolumn ## read a column into vector x (Mongo) xlogarithm ## take log of vector x (Mongo) ycolumn ## read a column into y ylogarithm ## take log of y
file `project' in directory `macro' Miscellaneous projections (e.g. equal area). Contributed by Michael Strauss.
aitoff #convert l and b to x y in aitoff coordinates: aitoff1 # Convert l and b to x y in reverse aitoff coordinates: aitoffdec # Draw a line of constant declination in an aitoff plot aitoffdec1 # Draw a line of constant declination in a reverse aitoff plot aitoffgrid # Put up equal area grid in Aitoff aitoffgrid1 # Puts up equal area grid in Aitoff. Reverse sense calc_lb # Calculates r, l and b given x, y, and z circle_exclude # After circleplot, draws lines corresponding to circlelabel # Draw axes for a redshift circleplot circleplot # Plot redshift points; requires circlelabel to have eqgal # Usage: ra dec l b; Convert (ra, dec) to (l,b). findbreak ## Sort x y so that xp, yp are continuous galeq # Usage: galeq l b ra dec. Convert (l,b) to (ra, dec) galgrid # Puts up equal area grid galplot # Usage: galplot ra dec l b galpoints # Plot points on an equal area grid; galpoints l b gstrans # Convert supergalactic to galactic coordinates hemiconvert # Convert from l b to x y for hemisphere plotting hemidec # Draw a line of constant declination on a pair of hemiplot # Sky plot in two hemispheres, equal area invaitoff # Convert x y in aitoff coordinates to l, b pielabel # Draw axes for a redshift pieplot pieplot # Plot redshift points; requires pielabel to have radtick # Put tick marks on a radial axis sgtrans # Convert galactic to supergalactic
file `stats' in directory `macro'
cgauss # evaluate a Cumulated Gaussian : N($mean,$sig) chisq # Return chi^2 distribution of vector $1 with $2 dof draw_KS # Draw a cumulated curve, for looking at KS statistics erfc # calculate complementary error function erfc($1) factorial # Use Stirling's formula to calculate a factorial ($1)! gauss # evaluate a Gaussian : N($mean,$sig) gaussdev # return a N(0,1) random vector linfit # linear least squares fit for any number of parameters log_fac # Use Stirling's formula to calculate a log factorial. lsq # do a least squares fit to a set of vectors lsq2 # do a least squares fit to a set of vectors, errors in x and y prob_KS # probability of getting a given value of the K-S statistic prob_wilc # return probability in $$2 that x exceeds $1 from Wilcoxon rxy # find Pearson Correlation Coefficient for two vectors smirnov1 # calculate 1 sided Kolmogorov-Smirnov statistic for vector smirnov2 # calculate 2 sided Kolmogorov-Smirnov statistic for vectors spear # calculate Spearman rank correlation coefficient for 2 vectors stats # stats vector mean sigma kurtosis : calculate $mean $sigma etc stats2 # stats vector weights mean sigma kurtosis stats_med # stats_med vector median SIQR : calc $median $SIQR from vector wilcoxon # calculate Wilcoxon statistic for 2 vectors wlsq # do a weighted least squares fit to a set of vectors
file `trigd' in directory `macro' Equivalents for all SM trig functions, taking degree arguments. Names add an extra `d' (e.g. sind)
file `utils' in directory `macro'
alpha_poi # alpha_poi x y z. Like poi x y, but use z as labels for points
arc # the arclength along the curve ($1,$2), e.g. set v=arc(x,y)
arrow # use the cursor to define an arrow.
barhist # draw a bar histogram
boxit # use the cursor to define a box, and draw it
circle # draw a circle, centre ($1,$2) radius $3
cumulate # find the cumulative distribution of $1 in $2
draw_arrow # draw an arrow from ($1,$2) to ($3,$4)
draw_box # draw a box, defined by two corners
error_x # draw x-error bars: error x y size
error_y # draw y-error bars: error x y size
get_hist # get_hist input output-x output-y base top width
gauss # evaluate a Gaussian : N($mean,$sig)
get # syntax: get i j. Read a column from a file
glevels # Set grey levels. Usage: glevels expr
greyscale # Draw a grey-scale image.
# Usage: greyscale [npx npy maxweight dmargin]
info # Get help about a command from SM's info files
interp # Linearily interpolate $3 into ($1,$2), giving $4
interp2 # Linearily interpolate $3 into ($1,$2), giving $4
is_file # Return true if file $1 exists
is_macro # Return true if $1 is a macro
is_set # define variable $$1 if the $3'rd bit is set in $2
is_vector # Return true if $1 is a vector
logerr # syntax: logerr x y error, where y is logged, and error isn't
mconcat # Concatenate 2 macros, optionally renaming result
modulo # find $1 modulo $2
number # convert a string vector to an arithmetic one
pairs # pairs x1 y1 x2 y2. connect (x1,y1) to (x2,y2)
polar # draw a circle as an `axis' for polar coordinates
pmatrix # print the matrix $1
puts # Draw a line of text, then move to the start of the next line
qminv # Quick matrix inversion, done in place
repeated # Return an array that is true for repeated elements in $1
reverse # reverse the order of a vector
save_vec # put the definition of a vector onto the history list
set_window # cycle through all available windows
shade_box # shade a box, spacing $1, defined by two corners
shed # shade region between x y and x2 y2 with n lines
simp # Simpson's rule integration: simp answer x y
smooth # boxcar smooth a vector
smooth2 # smooth a vector with a given filter
smooth3 # smooth a vector with a given filter omitting points
2dhistogram # convert the current image to a 2-d histogram
thin # create a "thinned" version of a vector for plotting points
thin2 # "thin" the end of a vector (i.e. sparse sampling)
thin3 # "thin" the central portion of a vector (i.e. sparse sampling)
uniq # Remove duplicate elements of $1, e.g. u = uniq(x)
upper # define a variable giving an `upper limit' symbol
vecminmax # find the minimum and maximum of a vector
vfield # plot a vector field: vfield x y len angle
vfield2 # plot a vector field, vfield x y len angle (len in x-axis units)
vfield3 # plot a vector field: vfield x y len angle (len in y-axis units)
vfield4 # Vector field at points x, y; components vx vy
SM differs in a number of ways from Mongo, and these fall
into three groups: those which are enhancements, those which are
generalisations, and those which are simply incompatibilities. We do not feel
that there is a fourth group for
degradations. For those users of Mongo intimidated by change, we note that
in most cases it is possible to ignore the enhancements
by using a macro presented in the next section. This macro redefines
commands to reproduce the old syntax; for example
limits is defined to mean LIMITS x y. It is also possible to read
Mongo files using the READ OLD command, and the macros
input and read_old based upon it. The following list
of enhancements in not complete; See the distribution notes from the
current release of SM.
Enhancements:
Any number of vectors may be defined. Vectors may be manipulated arithmetically. Vectors are named. Vectors may be defined from the keyboard usingDOloops or expressions. Vectors may be defined using the cursor. Any vector may be used for plotting. Any vector may be used for thePTYPEorERRORBARcommands. A history feature is implemented. The playback buffer may be edited. Macros may be defined from the keyboard, and edited. ADOconstruct is available. AFOREACHconstruct is available. Character strings may be read from a file and used freely as labels or names. Data may be read from rows as well as columns in files. Only those parts of a vector satisfying a logical condition need be plotted. Vectors may be sorted or fit with splines. Macros exist for doing least square fit to sets of points,constructing cumulative distributions and histograms, drawing circles, and shading regions. All devices have the same range of device coordinates, 0-32767. The entire SM environment may be saved for later resumption withSAVEandRESTORE. The special variable$dateexpands to the current date and time. You can define private point types.
There are also a few incompatibilities:
DEFINEis used to define variables; macros are defined usingMACRO. Macro arguments must be declared, and are referred to as$n, not&n. The formLIMITSis not supported (it's meaningless); useLIMITS x y, or the macrolim, mentioned above. (But note thatREAD OLDallows for these, and makes suitable changes.)WINDOWnow takes 4 arguments.
READ OLD reads a Mongo file, converts its contents to a form
acceptable to SM, and defines them as a macro.
Any macro definition (i.e. from a line beginning def to
a line beginning end) is converted to the SM form (i.e.
`$s' not `&s') and defined. The commands CONNECT,
HISTOGRAM, LIMITS, and POINTS are converted to LIMITS x y, and so
forth. ERRORBAR, ECOLUMN, and WINDOWS are also converted. READ OLD
will fail if the Mongo file contains abbreviations such as xc for XCOLUMN,
then your only hope is to define the same abbreviations. In many cases this
will have already been done, for instance xc expands to read x .
Comments (beginning !) are optionally converted to standard
SM
# comments (depending on how the file `read_old.c' was compiled.)
Note that it is advisable to convert these old Mongo macro files to SM macros, to enable you to take advantage of SM's features. You can do this by simply using READ OLD to read them into SM, and then MACRO WRITE or SAVE to write the converted macro out to disk.
There is also a macro equivalent of the old INPUT command.
input 1 ## read and execute a Mongo (not SM) file
READ OLD _temp $1
_temp
MACRO _temp DELETE
This version of compatibility is more complete than in pre-version 2 SM, it also conflicts more strongly with normal SM operations.
The macro compatibility defines mimics for the Mongo commands
which assume that the only vectors are x and y. We
strongly recommend that you do not use this macro! If you want
to use it anyway, commands like limits alpha beta will give
syntax errors. You can turn compatibility mode off again with compatibility 0. The macro itself is a little complicated, it turns
off the special meaning of (e.g.) limits, and replaces it with a
macro that reproduces the old behaviour, in this case LIMITS x
y. The new definitions are in the file `compatible' in the
default macro directory, as specified in your `.sm' file. At
the time of writing, the commands connect, errorbar, histogram, limits, list, points, read, and
window are redefined to reproduce the old syntax. In addition,
help is defined to not appear on your history buffer, and define is defined to create macros interactively. You might also be
interested in other redefinitions of commands (e.g. list to mean
list the playback buffer), if so look at `overloading' in the index.
It should be clear
that this set of definitions could thoroughly confuse SM if
you try to take advantage of its features; in the realm of
compatibility mode, it is strictly caveat emptor.
compatible 11 ## define macros to be compatible with Mongo
# If the argument is non-zero or omitted,
# compatibility mode is turned on.
# note that some of these make it hard to use regular SM!
if($?1 == 0) {
compatible 1
RETURN
}
if($1 == 0) {
MACRO DELETE "$!macro"compatible
}
FOREACH w { connect define errorbar help histogram limits \
list points read window write } {
OVERLOAD $w $1
}
if($1) {
MACRO READ "$!macro"compatible
}
# So newline will end IF statement
These are a sub-set of the well-known Hershey fonts(33)
and the available characters are listed in the following table, which
were generated from within SM by saying load fonts fonts.
For details on SM's implementation of TeX and the `traditional' style see the section in the main body of this manual (see section Drawing Labels and SM's TeX Emulation).
The characters in a font are specified using a programme read_fonts
which you can use to make binary font files from the list of
Hershey characters, using an index file to specify what character
should go where. The binary fonts file also specifies which TeX
`definitions' are available (e.g. \alpha). The first 4 bytes
of the file are an integer (in binary) that specifies the format of
the file; when the format is changed in an incompatible way this number
if changed and you will have to rebuild your font files (`make fonts').
The default font table is
illustrated at the end of this appendix. Which font file you want to
use is specified as the fonts entry in your
`.sm' file.(34).
The fonts.bin fonts have been cleaned up a bit for version 2.0
of SM, although the
order of characters in the greek and roman fonts is unchanged. There
is a new font, `Old English' or \o or \oe, and a good number of new
characters are provided. Neither of these fonts supports the `private'
fonts, that is there in case users desperately need something,
when they can make their own binary font file. For example, there is a
set of Hershey oriental fonts that can be used to help SM write Japanese
(see section Using SM to write Japanese).
The complete list of (occidental) Hershey
characters is given in a file called hershey_oc.dat, and is in
the public domain. Each character is specified by a number in the
first 5 columns, then a number of strokes in the next 3, then pairs of
letters in the remaining columns up to 72, and in as many 72 character
lines as are needed. (Annoyingly, if a line consists of exactly 72
characters, the next must be left blank).
Each pair of characters consists of a coordinate,
with the origin at (R,R), and the y axis pointing down.
A ` ' indicates that the next point is a move, otherwise just
connect the dots. The very first pair is different, as it specifies
the left and right spacing for the character. If this isn't clear, try
drawing a few characters on graph paper, character 2001 (roman A) for example.
There are a few characters that have
traditionally been available in Mongo that are not in the Hershey set,
these have been added to the end of the `hershey_oc.dat' file, plus a
few that we thought deserved adding.
If you want to create your own characters, the macro make_char in
`fonts' (i.e. load fonts make_char) might help. It uses the
cursor to make a string that is (nearly) in the correct form for
inclusion in `hershey_oc.dat'
The programme read_fonts reads this file,
an `index' file that specifies the characters to be put into the
fonts, and a list of TeX definitions. The index file consists of
character numbers, or ranges
consisting of two numbers separated by a minus sign. Comments go from
the character # to end of line. Each font consists of 96 characters
in ascii order, and fonts appear in the index in the order
rm, gr, sc, ti, oe, and then the various private fonts (privateA, privateB,
..., privateG.).
You can also use cat_fonts to concatenate
binary font files prepared with read_fonts.
The format of the TeX definition file
is that each definition has a line to itself, lines starting with a
# are comments. A line consists of a name, some whitespace, the number
of arguments (optional, defaults to zero), the name
of the font to use, a single white-space character, and the value of
the definition to the end of the line; you can continue onto another line
by putting a \ at the end of the line. You can use any of the normal
font specifications, or cu which means use the current font.
For example
alpha gr a alsoalpha 0 cu \gr a alphatoo 1 gr \#1a
defines \alpha the conventional way as the character a
in the greek font, then defines alsoalpha in a less efficient
way (by specifying the current font, then explicitly switching to
greek), then defines alphatoo as a large
, used as \alphatoo5. There's no reason
why your definitions can't be reasonably complicated, see for example
the definition of \TeX.
If the number of arguments is given as -1 the TeX symbol will be interpreted as an alias for the specified font, for example the TeX definition file begins
katakana -1 privateA hiragana -1 privateB
to make \katakana change to the privateA font.
The main Makefile prepares your binary font file for you. There is also
a programme cat_fonts that can be used to concatenate two or more
binary fonts files; an example is given in the section on Japanese fonts
(section Using SM to write Japanese).
It is also possible to build SM fonts for other languages:
SM is currently able to use either katakana or hiragana fonts, but no kanji are yet supported. The order of characters in the fonts is likely to change, based on the desires of the users. Please let me know what you want.
As distributed, the Japanese font tables aren't built. To make them, say
make jfonts.bin in the toplevel SM directory, and ensure that your
`.sm' file sets fonts correctly. The Japanese characters are
available as \hiragana and \katakana. You can list the characters
available with load fonts jTeX_fonts.
One question is whether \katakana 2 should print an Arabic (western)
or a Japanese two; I am open to suggestions.
There are a variety of kanji characters present, but no easy way to
select them. If someone would like to donate a table of the
correspondence between some accepted ascii encoding and the available
characters, I will ensure that the character encoded as AB os
available as (say) \kanjiAB, and provide a programme to
generate such codes from the output of at least one popular word
processor, expecting the user to cut-and-paste the result. Of course,
it'd be quicker if you did it for me!
Go to the first, previous, next, last section, table of contents.