Writing scripts to change tables in Mensis
Mensis includes an interpreter so you can write scripts to modify fonts.
If you start pfaedit with a script on the command line it will not put up
any windows and it will exit when the script is done.
$ mensis -script scriptfile.pe {fontnames}
PfaEdit can also be used as an interpreter that the shell will automatically
pass scripts to. If you a mark your script files as executable
$ chmod +x scriptfile.pe
and begin each one with the line
#!/usr/local/bin/mensis
(or wherever pfaedit happens to reside on your system) then you can invoke
the script just by typing
$ scriptfile.pe {fontnames}
If you wish Mensis to read a script from stdin then you can use "-" as a
"filename" for stdin. (If you build Mensis without X11 then pfaedit will
attempt to read a script file from stdin
if none is given on
the command line.)
If you set the environment variable MENSIS_VERBOSE
(it doesn't
need a value, just needs to be set) then Mensis will print scripts to stdout
as it executes them.
The syntax is rather like a mixture of C and shell commands. Every file
corresponds to a procedure. As in a shell script arguments passed to the
file are identified as $1, $2, ... $n. $0 is the file name itself. $argc
gives the number of arguments. $argv[<expr>] provides array access
to the arguments.
Terms can be
-
A variable name (like "$1" or "i" or "@fontvar" or "_global")
The scope of the variable depends on the initial character of its name.
-
A '$' signifies that it is a built-in variable. The user cannot create any
new variables beginning with '$'. Some, but not all, of these may be assigned
to.
-
A '_' signifies that the variable is global, it is always available. You
can use these to store context across different script files (or to access
data within nested script files).
-
A '@' signifies that the variable is associated with the font. Any two scripts
looking at the same font will have access to the same variables.
-
A variable which begins with a letter is a local variable. It is only meaningful
within the current script file. Nested script files may have different variables
with the same names.
-
an integer expressed in decimal, hex or octal
-
a unicode code point (which has a prefix of "0u" or "0U" and is followed
by a string of hex digits. This is only used by the select command.
-
A string which may be enclosed in double quotes
-
Single quotes are used to define a 4 character tag, used to name tables,
etc. ('OS/2', 'maxp')
-
a 4 character tag followed by a dot followed by a name gives access to the
named field within the table with that tag
-
a procedure to call or file to invoke.
-
an expression within parentheses
There are three different comments supported:
-
Starting with a "#" character and proceding to end of line
-
Starting with "//" and proceding to end of line
-
Starting with "/*" and proceding to "*/"
Expressions are similar to those in C, a few operators have been omitted,
a few added from shell scripts. Operator precedence has been simplified slightly.
So operators (and their precedences) are:
-
unary operators (+, -, !, ~, ++ (prefix and postfix), --(prefix and postfix),
() (procedure call), [] (array index), :h, :t, :r, :e
Most of these are as expected in C, the last four are borrowed from shell
scripts and are applied to strings
-
:h gives the head (directory) of a pathspec
-
:t gives the tail (filename) of a pathspec
-
:r gives the pathspec without the extension (if any)
-
:e gives the extension
-
*, /, % (binary multiplicative operators)
-
+, - (binary arithmetric operators)
If the first operand of + is a string then + will be treated as concatenation
rather than addition. If the second operand is a number it will be converted
to a string (decimal representation) and then concatenated.
-
==, !=, >, <, >=, <= (comparison operators, may be applied to
either two integers or two strings)
-
&&, & (logical and, bitwise and. (logical and will do short circuit
evaluation))
-
||, |, ^ (logical or, bitwise or, bitwise exclusive or (logical or will do
short circuit evaluation))
-
=, +=, -=, *=, /=, %= (assignment operators as in C. The += will act as
concatenation if the first operand is a string.)
Note there is no comma operator, and no "?:" operator. The precedence of
"and" and "or" has been simplified, as has that of the assignment operators.
Procedure calls may be applied either to a name token, or to a string. If
the name or string is recognized as one of Mensis's internal procedures it
will be executed, otherwise it will be assumed to be a filename containing
another mensis script file, this file will be invoked (since filenames can
contain characters not legal in name tokens it is important to allow general
strings to specify filenames). If the procedure name does not contain a directory
then it is assumed to be in the same directory as the current script file.
Arrays are passed by reference, strings and integers are passed by value.
Variables may be created by assigning a value to them (only with the "="),
so:
i=3
could be used to define "i" as a variable. Variables are limited in scope
to the current file, they will not be inherited by called procedures.
A statement may be
-
an expression
-
if ( expression )
statements
{elseif ( expression )
statements}
[else
statements]
endif
-
while ( expression )
statements
endloop
-
foreach
statements
endloop
-
return [ expression ]
-
shift
As with C, non-zero expressions are defined to be true.
A return statement may be followed by a return value (the expression) or
a procedure may return nothing (void).
The shift statement is stolen from shell scripts and shifts all arguments
down by one. (argument 0, the name of the script file, remains unchanged.
The foreach statement requires that there be a current font. It executes
the statements once for each character in the selection. Within the statements
only one character at a time will be selected. After execution the selection
will be restored to what it was initially. (Caveat: Don't reencode the font
within a foreach statement).
Statements are terminated either by a new line or a semicolon.
Trivial example:
i=0; #semicolon is not needed here, but it's ok
while ( i<3 )
if ( i==1 /* pointless comment */ )
Print( "Got to one" ) // Another comment
endif
++i
endloop
Mensis maintains the concept of a "current file" and "current font" almost
all commands refer only to the current font (and require that there be a
font). If you start a script with File->Execute Script, the font you were
editing will be current, otherwise there will be no initial current font.
The Open(), New() and Close() commands all change the current font. Mensis
also maintains a list of all fonts that are currently open. This list is
in no particular order. The list starts with $firstfont.
All builtin variables begin with "$", you may not
create any variables that start with "$" yourself (though you may assign
to (some) already existing ones)
-
$0
the current script filename
-
$1
the first argument to the script file
-
$2
the second argument to the script file
-
...
-
$argc
the number of arguments passed to the script file (this
will always be at least 1 as $0 is always present)
-
$argv
allows you to access the array of all the arguments
-
$curfont
the name of the filename in which the current font
resides
-
$firstfont
the name of the filename of the font which is first
on the font list (Can be used by Open()), if there are no fonts loaded this
returns an empty string. This can be used to determine if any font at all
is loaded into pfaedit.
-
$nextfont
the name of the filename of the font which follows
the current font on the list (or the empty string if the current font is
the last one on the list)
-
$fontchanged
returns 1 if the current font has changed, 0 if
it has not changed since it was read in (or saved).
-
$fontname
the name contained in the postscript FontName field
-
$filename
the name of the file containing the font.
-
$trace
if this is set to one then PfaEdit will trace each procedure
call.
-
$version
returns a string containing the current version of
pfaedit. This should look something like "020817".
-
$
<Preference Item> (for example
$AutoHint
) allows you to examine the value of that preference
item (to set it use SetPref
)
The following example will perform an action on all loaded fonts:
file = $firstfont
while ( file != "" )
Open(file)
/* Do Stuff */
file = $nextfont
endloop
The built in procedures are very similar to the
menu items with the same names.
-
Print(arg1,arg2,arg3,...)
-
This corresponds to no menu item. It will print all of its arguments to stdout.
It can execute with no current font.
-
PostNotice(str)
-
When run from the UI will put up a window displaying the string (the window
will not block the program and will disappear after a minute or so). When
run from the command line will write the string to stderr.
-
Error(str)
-
Prints out str as an error message and aborts the current script
-
AskUser(question[,default-answer])
-
Asks the user the question and returns an answer (a string). A default-answer
may be specified too.
-
Array(size)
-
Allocates an array of the indicated size.
a = Array(10)
i = 0;
while ( i<10 )
a[i] = i++
endloop
a[3] = "string"
a[4] = Array(10)
a[4][0] = "Nested array";
-
SizeOf(arr)
-
Returns the number of elements in an array.
-
Strsub(str,start[,end])
-
Returns a substring of the string argument. The substring beings at position
indexed by start and ends at the position indexed by end (if end is omitted
the end of the string will be used, the first position is position 0). Thus
Strsub("abcdef",2,3) == "c"
and Strsub("abcdef",2) ==
"cdef"
-
Strlen(str)
-
Returns the length of the string.
-
Strstr(haystack,needle)
-
Returns the index of the first occurance of the string needle within the
string haystack (or -1 if not found).
-
Strrstr(haystack,needle)
-
Returns the index of the last occurance of the string needle within the string
haystack (or -1 if not found).
-
Strcasestr(haystack,needle)
-
Returns the index of the first occurance of the string needle within the
string haystack ignoring case in the search (or -1 if not found).
-
Strcasecmp(str1,str2)
-
Compares the two strings ignoring case, returns zero if the two are equal,
a negative number if str1<str2 and a positive number if str1>str2
-
Strtol(str[,base])
-
Parses as much of str as possible and returns the integer value it represents.
A second argument may be used to specify the base of the conversion (it defaults
to 10). Behavior is as for strtol(3).
-
Strskipint(str[,base])
-
Parses as much of str as possible and returns the offset to the first character
that could not be parsed.
-
GetPref(str)
-
Gets the value of the preference item whose name is contained in str. Only
boolean, integer, real, string and file preference items may be returned.
Boolean and real items are returned with integer type and file items are
returned with string type.
-
SetPrefs(str,val[,val2])
-
Sets the value of the preference item whose name is containted in str. If
the preference item has a real type then a second argument may be passed
and the value set will be val/val2.
-
UnicodeFromName(name)
-
Looks the string "name" up in PfaEdit's database of commonly used glyph names
and returns the unicode value associated with that name, or -1 if not found.
This does not check the current font (if any).
-
Chr(int)
Chr(array)
-
Takes an integer [0,255] and returns a single character string containing
that code point. Internally PfaEdit interprets strings as if they were in
ISO8859-1 (well really, PfaEdit just uses ASCII-US internally). If passed
an array, it should be an array of integers and the result is the string.
-
Ord(string[,pos])
-
Returns an array of integers represenging the encoding of the characters
in the string. If pos is given it should be an integer less than the string
length and the function will return the integer encoding of that character
in the string.
-
Utf8(int)
-
Takes an integer [0,0x10ffff] and returns the utf8 string representing that
unicode code point. If passed an array of integers it will generate a utf8
string containing all of those unicode code points. (it does not expect to
get surrogates).
-
-
Open(filename)
-
This makes the font named by filename be the current font. If filename has
not yet been loaded into memory it will be loaded now. It can execute without
a current font.
-
Close()
-
This frees up any memory taken up by the current font and drops it off the
list of loaded fonts. After this executes there will be no current font.
-
Save([filename])
-
If no filename is specified then this saves the current file back into its
original name. With one argument it executes a SaveAs command, saving the
current file to that filename.
-
Quit(status)
-
Causes PfaEdit to exit with the given status. It can execute with no current
font.
-
FontCount()
-
Returns the number of fonts in the current file (returns 1 unless the file
is a ttc file)
-
FontName([index])
-
Returns the name of the index'th font in the current file. If index is omitted
then the current font's name is returned.
-
SetCurrentFont(index)
-
Makes the index'th font be current.
-
GetCurrentFont()
-
Returns the index of the current font.
-
TableCount([index])
-
Returns the number of tables in the current font or the indexth font.
-
TableTag(table-index,[index])
-
Returns the tag of the table at position table-index in the current font
(or the font at the given font-index)
-
TableIndex(table-tag,[index])
-
Returns the table index of the table with the given tag within the indicated
font.
-
GetTableField(field-name,table-tag[,index])
GetTableField(field-name,table-index[,index])
-
Returns an integer containing the value of the named
field in the specified table.
-
SetTableField(field-name,value,table-tag[,index])
SetTableField(field-name,value,table-index[,index])
-
-
GetTableOffset(offset,size,table-tag[,index])
GetTableOffset(offset,size,table-index[,index])
-
Returns an integer containing the value of the value at the specified byte
offset in the specified table. The size field may be either 1, 2, or 4.
-
SetTableOffset(offset,size,value,table-tag[,index])
SetTableOffset(offset,size,value,table-index[,index])
The Execute Script dialog
This dialog allows you to type a script directly in to PfaEdit and then run
it. Of course the most common case is that you'll have a script file somewhere
that you want to execute, so there's a button [Call] down at the bottom of
the dlg. Pressing [Call] will bring up a file picker dlg looking for files
with the extension *.pe (you can change that by typing a wildcard sequence
and pressing the [Filter] button). After you have selected your scipt the
appropriate text to text to invoke it will be placed in the text area.
The current file of the script will be set to whatever font you invoked it
from.
-- TOC --