We activate Bit 7
in Simple Glyph Flags 1, to support new point types. In a font, if any glyph used this extension, the 0th bit of head
.glyphDataFormat
should be set.
In each contour, there would be three kind of control points (“Knots”)
- Bit
0
is set —— On-curve (Z
) - Bit
0
is clear:- Bit
7
is clear —— Quadratic off-curve (Q
) - Bit
7
is set —— Cubic off-curve (B
)
- Bit
Since Z
, B
and Q
knots could occur in arbitrary order, a normalization pass should be preformed right after the glyph is being rasterized. It would convert all the off-curve knots to combination of on-curve points (Z
) or cubic control points (C
). Things between two Z
points could only be empty of two C
points.
The conversion is handled knot-by-knot, while the knot before and after it are also be considered to perform high-quality conversion. A curve with only Z
and Q
knots would be normalized just like the current TrueType specifies, and curves with only Z
and B
knots would be normalized as cubic B-splines.
- Let
answer
= Empty list of (list of points). - For each input knot
k
at indexi
:- Let
p
andq
are the knot before and after it. - If
k
is a Z knot:- Set
answer
(i
) to list containing 1 point:- Z(
k
)
- Z(
- Set
- If
k
is a B knot:- If
pkq
follows ZBZ pattern:- Set
answer
(i
) to a list containing 2 points:- C(mix(
p
,k
, 2/3)) - C(mix(
q
,k
, 2/3))
- C(mix(
- Set
- Else If
pkq
follows ZBB or BBZ pattern:- Set
answer
(i
) to a list containing 1 point:- C(
k
)
- C(
- Set
- Otherwise:
- Let
factorBefore
be 1/3 if type ofp
is Q, 2/3 if is B. - Let
factorAfter
be 1/3 if type ofq
is Q, 2/3 if is B. - Let
before
= mix(p
,k
,factorBefore
) - Let
after
= mix(q
,k
,factorAfter
) - Set
answer
(i
) to a list containing 3 points:- C(
before
) - Z(mix(
before
,after
, 1/2)) - C(
after
)
- C(
- Let
- If
- If
k
is a Q knot:- Let
halfSegBefore
= Empty point list. - Let
halfSegAfter
= Empty point list. - If
p
is a Z knot: SethalfSegBefore
to list containing 1 point:- C(mix(
p
,k
, 2/3))
- C(mix(
- If
p
is a Q knot: SethalfSegBefore
to list containing 2 points:- Z(mix(
p
,k
, 1/2)) - C(mix(
p
,k
, 5/6))
- Z(mix(
- If
q
is a Z knot: SethalfSegAfter
to list containing 1 point:- C(mix(
q
,k
, 2/3))
- C(mix(
- If
p
is a Q knot: SethalfSegAfter
to list containing 2 points:- C(mix(
q
,k
, 5/6)) - Z(mix(
q
,k
, 1/2))
- C(mix(
- Set
answer
(i
) tohalfSegBefore
++halfSegAfter
- Let
- Let
- Return the
answer
flattened.
JavaScript Code
function Z(pt){ return {type: "Z", x: pt.x, y: pt.y} }
function C(pt){ return {type: "C", x: pt.x, y: pt.y} }
function mix(pt1, pt2, factor) {
return {
x: pt1.x + (pt2.x - pt1.x) * factor,
y: pt1.y + (pt2.y - pt1.y) * factor
}
}
function convertKnots(knots) {
let answer = [];
for (let i = 0; i < knots.length; i++) {
const k = knots[i];
const p = knots[(i + knots.length - 1) % knots.length];
const q = knots[(i + knots.length + 1) % knots.length];
switch(k.type) {
case "Z" : answer[i] = [Z(k)]; break;
case "B" :
if (p.type === "Z" && q.type === "Z") {
answer[i] = [C(mix(p, k, 2/3)), C(mix(q, k, 2/3))];
} else if (p.type === "Z" && q.type === "B" || p.type === "B" && q.type === "Z") {
answer[i] = [C(k)];
} else {
const factorBefore = p.type === "B" ? 2/3 : 1/3;
const factorAfter = q.type === "B" ? 2/3 : 1/3;
const before = mix(p, k, factorBefore);
const after = mix(q, k, factorAfter);
answer[i] = [C(before), Z(mix(before, after, 1/2)), C(after)]
}
break;
case "Q":
let halfSegBefore = [];
let halfSegAfter = [];
if (p.type === "Z")
halfSegBefore = [C(mix(p, k, 2/3))];
if (p.type === "Q")
halfSegBefore = [Z(mix(p, k, 1/2)), C(mix(p, k, 5/6))];
if (q.type === "Z")
halfSegAfter = [C(mix(q, k, 2/3))];
if (q.type === "Q")
halfSegAfter = [C(mix(q, k, 5/6)), Z(mix(q, k, 1/2))];
answer[i] = [...halfSegBefore, ...halfSegAfter]
}
}
return answer.flatten()
}
This extension enables complex glyphs combined together with Boolean operators. It would be extremely useful when creating variable glyphs with complex topology.
A Boolean Composer combines two layers (Background b
, and Argument a
) into one, and decide whether an area should be inked by the inked-ness in the two given layers. Typically a composer could be described with three binary bits (iba)
. The meaning of each byte is:
- Bit
i
: Whether the areas should be inked, if it is inked in both the Background and Argument. - Bit
b
: Whether the areas should be inked, if it is inked in the Background but not the Argument. - Bit
a
: Whether the areas should be inked, if it is inked in the Argument but not the Background.
There are 8 possible combinations:
(000)
: It would result an empty result, so it would be invalid or assigned to special meanings.(001)
: Argument minus Background.(010)
: Background minus Argument.(011)
: Background XOR Argument.(100)
: Intersection.(101)
: Copy Argument.(110)
: Copy Background.(111)
: Union
This extension activates three unused bits in Composite Glyph Flags 2 to support Boolean Composite Glyphs in glyf
table. In a font, if any glyph used this extension, the 1st bit of head
.glyphDataFormat
should be set.
We define a new kind of composite glyph, Boolean Composite Glyph (BCG), as a glyph composed with components using Boolean expression. The existing kind of composite glyph would be named as Direct Composite (DCG).
BCGs' components could be either BCG, DCG or Simple, while for DCGs, it could only include DCGs or Simples.
Bit 15
-13
are the Boolean Composer bits. Whether a glyph should be BCG or DCG is decided by these bits:
- If all components' Bit
15
-13
are all clear, then it is a DCG.- A rasterizer should check whether its components are all DCG or Simple.
- If all components' Bit
15
-13
are all valid Boolean Composers, then it is a BCG. - Otherwise the glyph data is corrupted. A proper rasterizer should reject it.
For a BCG, the formula of combining components would like this:
(((∅ <op1> Component1) <op2> Component2) <op3> Component3) ...
We initialize a background to empty, and combine each component as the Argument in order, where the operator is defined by the Boolean Composer bits. It is recommended that for the first component in each BCG, the Composer should be (111)
(Union).
The Boolean composition does not affect the coordinate computing process, also the gvar
application and hinting. After the outline is hinted, the formula would be used in the last rasterization step to produce the final glyph.
Since components of a BCG could be another BCG, it is possible to define arbitrary-complex Boolean Composition formulas (in a tree form) that form a glyph.
Example: for a inverted glyph like ❸
, it could be defined in this manner:
- Component 1, being the circle background, using Composer
(111)
(Union). - Component 2, being the hole, using Composer
(010)
(Background minus Argument).
We enable two new operators and a "Layer Stack".
The CharString would be organized in this manner:
charString -> initialHints? layer? contourSet?
initialHints -> hs* vs* cm* hm*
layer -> contourSet "endlayer"
| layer layer composer# "boolcomp"
contourSet -> mt subpath
Two new operators:
- ⊢
endlayer
(0C 26
)- Argument stack must be empty.
- Pack the constructed paths into a Layer, and push into a Layer Stack.
- composer# ⊢
boolcomp
(0C 27
)- The composer# is a three-bit integer defines a Boolean Composer. See the definition above.
- Argument stack would be cleared.
- Two layers from the Layer Stack would be popped and perform a Boolean Composition.
- The first layer popped would be Argument.
- The second would be Background.
- The result would be pushed into the Layer Stack.
When the CharString if fully parsed, all the layers in the Layer Stack, and the active constructed path would be combined together with Union.