In Microsoft's official OpenType spec page on GPOS Pair Positioning, PosFormat 1
, there is a small side-remark which is not explained any further:
A PairPos subtable also defines an offset to a Coverage table
(coverageOffset) that lists the indices of the first glyphs in each
pair. More than one pair can have the same first glyph, but the Coverage
table will list that glyph only once.
The problem lies here:
When investigating a font, you can write out the PairPos code for a font such as Unicode.org's TestGPOSTwo.otf (https://github.com/unicode-org/text-rendering-tests/tree/master/fonts)
uni25EF -> sun x1=-800
now what character comes at the '...'? PairSetCount is 2; and
A PairSet table enumerates all the glyph pairs that begin with a covered glyph.
so this must indeed be a case where originally uni25EF appeared twice in the coverage list – and thus included only once.
For other fonts, the value of pairSetCount is equal to the length of Coverage; each covered glyph only gets used once as the "first glyph". The specification acknowledges this may not be the case but does not offer an alternative.
A number of other tools and libraries agree with my interpretation so far (opentype.js, otl_dump.php). The otherwise reliable DTL OTMaster Light reports the second occurrence as '.notdef'.
I am unable to duplicate a valid .fea file to exactly mimic TestGPOSTwo's behaviour. makeotf complains it sees two same kerning pairs and only uses the larger value – it must be a common mistake – and my Feature-Fu is not strong enough to fool it otherwise.
It is very likely TestGPOSTwo.otf was artificially constructed, but the accompanying document states
The second subtable has two PairSets, both kerning
◯ U+25EF LARGE CIRCLE and ☼ U+263C WHITE SUN WITH RAYS. The first
PairSet applies kerning so that the two symbols will exactly
overlap. If the second PairSet was applied, it would add spacing
to move the two symbols away from each other. But a correct text
rendering engine should walk the PairSets in the order given by
the font, and stop processing after finding the first
which indicates that first character ought to be valid for both pairs, even though a bad renderer might use the second value instead of the first.
Any insights on this?