Expressions and Formulas
The TrueType engine is capable of performing simple arithmetical operations, and it is easy to combine these to perform more complex operations. You must always remember, however, that TrueType arithmetic operates on F26dot6 fixed-point numbers, and all operations return the same kind of fixed-point numbers. There is no way to obtain results with higher precision. Thus certain kinds of calculations are impossible in TrueType, and you must always be careful, when combining the operations that TrueType can do, to consider the limited precision of the intermediate results that get passed from operation to operation.
There are two ways to combine operations in Xgridfit. One is to write expressions rather like the expressions used in other programming languages (though fewer operators are available); the other is to use <formula> elements, within which operations can be chained.
Expressions
Expressions in Xgridfit resemble expressions in other programming languages: they consist of numbers and identifiers coordinated with operators; they can be simple (e.g. "bottom-pt + 1") or complex; Xgridfit parses them according to certain rules of precedence which are worth knowing; and the rules of precedence may be overridden by using parentheses, which can be nested.
There is little point in describing the syntax of expressions in detail, since they are so familiar to everyone who has done any programming; instead this section will list the operators, note a few peculiarities, and present some examples
Operators | |
---|---|
First precedence | |
and | Logical and. Example: pixels-per-em > 10 and pixels-per-em < 20 |
or | Logical or. Example: pixels-per-em < 10 or pixels-per-em > 20 |
Second precedence | |
= | Equals. Example: pixels-per-em = 15 |
> | Greater than. Example: control-value(lc-vert-stem) > 1p |
< | Less than. Example: control-value(lc-vert-stem) < 1p |
>= | Greater than or equal. Example (where v has previously been declared as a variable): v >= 0.35 |
<= | Less than or equal. Example: v <= 0.35 |
!= | Not equal. Example: round(control-value(left-side)) != control-value(left-side) |
Third precedence | |
+ | Addition. Example: top-point + 1 |
- | Subtraction. Example: top-point - 1 |
* | Multiplication. Example: lc-vert-stem * 1.2 |
/ | Division. Example: lc-vert-stem / 2.0 |
Fourth precedence | |
-- |
Treats the arguments on both sides of the
operator as point numbers and returns the
current distance (in pixels) between them,
as measured on the projection
vector. Ordinarily the argument on the
left-hand side of the operator should be the
point on the left or bottom; reverse the
numbers to change the sign of the
result. Example: round(stem-left -- stem-right) |
--- |
Like --, but returns the distance
between points in the original
outline. Example: absolute((stem-left --- stem-right) - (stem-left -- stem-right)) |
Fifth precedence | |
odd | True if the argument is odd. Example: odd(v) |
even | True if the argument is even. Example: even v |
not | Reverses the boolean value of the argument. Example: not(v > 4.0) |
floor | The greatest integer value less than the argument. Example: floor(control-value(lc-vert-stem)) |
ceiling | The smallest integer value greater than the argument. Example: ceiling(control-value(lc-vert-stem) / 2) |
absolute | The absolute value of the argument. Example: absolute(control-value(lc-vert-stem)) |
negative | The negation of the argument. Example: negative(v) |
round | The argument rounded according to the current round state. The "color" is the <default> set with type="color" (gray if not set). Example: round(control-value(lc-vert-stem)) |
round-gray | The argument rounded according to the current round state. The "color" is gray. Example: round-gray(control-value(lc-vert-stem)) |
round-black | The argument rounded according to the current round state. The "color" is black. Example: round-black(control-value(lc-vert-stem)) |
round-white | The argument rounded according to the current round state. The "color" is white. Example: round-white(control-value(lc-vert-stem)) |
index | Returns an index of (pointer to) a control value or variable. Example: index(lc-vert-stem) |
control-value | The control value at the index represented by the argument. Example: control-value(lc-vert-stem) |
coord | The current coordinate (x or y depending on the projection vector) of a point. Example: coord(stem-left) |
initial-coord | Like coord, but returns the original coordinate of a point: that is, its coordinate at the beginning of the glyph program. Example: initial-coord(bottom-point + 1) |
x-coord | Like coord, but always returns the current coordinate of a point on the x axis. The setting of the projection vector is the same after the operation as before. coord is more efficient than this and the following three operators when the coordinate of a point on the current projection vector is needed. |
y-coord | Like coord, but always returns the current coordinate of a point on the y axis. The setting of the projection vector is the same after the operation as before. |
initial-x-coord | Like initial-coord, but always returns the original coordinate of a point on the x axis. The setting of the projection vector is the same after the operation as before. |
initial-y-coord | Like initial-coord, but always returns the original coordinate of a point on the y axis. The setting of the projection vector is the same after the operation as before. |
variable | Treats the argument as an index of (pointer to) a variable and returns its value. Example: variable(v) |
nan | Returns true (1) if the argument cannot be resolved to a number at compile-time (e.g. it is the name of a variable, or the name of a <range>). If the argument is a number, returns false (0). Example: nan(v) |
point | Causes the compiler to regard the argument as a point number. In a <glyph> program with an offset parameter, the offset is automatically added to the argument. Use this operator in contexts where the compiler does not automatically recognize a number as a point number. Do not use it in the num attribute of a <point> element, as this will cause the offset to be added twice. Example: point(a) -- point(b) |
Here is an example of precedence:
<if test="pixels-per-em < 10 or pixels-per-em > 20 and round-state = to-grid">
Xgridfit breaks the expression at the "or" (which has the same precedence as the "and" but occurs farther to the left); it evaluates first "pixels-per-em < 10", second "pixels-per-em > 20 and round-state = to-grid", and finally executes OR on the two values. If that is not what you want, you may use parentheses to alter the order in which constituents are evaluated:
<if test="(pixels-per-em < 10 or pixels-per-em > 20) and round-state = to-grid">
Now Xgridfit breaks the expression at the "and" and evaluates everything to the left of it (inside the parentheses), then everything to the right of it, and finally executes AND.
Fifth-precedence operators are all unary: they operate on a single value. If this is a simple value it may be separated from the operator by a space; if it is an expression it must be enclosed in parentheses.
Binary operators (those that operate on two values) must always be surrounded by whitespace. This will not work:
<point num="top+2"/>
It must be like this:
<point num="top + 2"/>
However, the whitespace may be any number of spaces, tabs, a line break, and so on, for the spacing of an expression is always normalized before it is evaluated.
Note that when all of the values in an expression are number literals, constants or other identifiers that can be resolved to numbers at compile time, and the only operators are first- or second-precedence operators and the arithmetic operators "+" and "-", Xgridfit resolves the whole expression to a single number at compile time. This optimizes the most common cases, where a point number is expressed by addition to or subtraction from a constant.
Formulas
A <formula> is a list of arithmetical operations whose result can be assigned to a variable, a control value, or any of those graphics variables that can be written to. The format is like this:
<formula result-to="minimum-distance"> <divide dividend="minimum-distance" divisor="2.0"/> <add value1="0.5"/> </formula>
The <formula> may contain any of the arithmetic elements: <add>, <subtract>, <divide>, <multiply>, <absolute>, <negate>, <floor>, <ceiling>, <minimum>, <maximum>, <round>. These can also occur outside the <formula>, but they behave differently there. When one of these elements outside the formula lacks a result-to attribute, Xgridfit tries to write the result back to one of the operands; failing that, it leaves the result on the run-time stack and prints a warning. But within the <formula> no attempt is made to write the result to one of the operands, and no warning is printed when the result is left on the stack; instead, it is assumed that the next arithmetic element will take one or both of its operands from the stack.
In the example above, the current minimum distance is divided by two, and the result of that operation is passed to the <add> element, where 0.5 is added to this new value; then the sum is passed back to the <formula> element, which writes it to the minimum-distance graphics variable, thus setting a new minimum distance.
The <formula> element was added to Xgridfit before the expression feature, and it looks rather clunky in comparison. The same operation could be performed more tersely thus:
<set-minimum-distance value="(minimum-distance / 2.0) + 0.5"/>
and the code generated by Xgridfit would be the same.