Writing scripts to change tables in Mensis

Mensis includes an interpreter so you can write scripts to modify fonts.

Starting scripts

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.

Scripting Language

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

There are three different comments supported:

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:

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

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)

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 --