Open davelab6 opened 1 month ago
To expand on this a little:
Suppose you have a font which has a wdth range of 100-400-1000. But some glyphs have a designed range of 400-800.
I know that gvar
stores regions and not store masters, and there is good reason why it does this, but let's assume for a moment that gvar
stores masters. (Because designers think it terms of masters so they assume this is how the gvar
table works.)
For the full glyphs, you have two masters, one at 400, one at 1000. And for the "sparse" glyphs, you would have have two masters, one at 400, one at 800, and because there was no data about how to interpolate things in the 800-1000 range, the outline during this part of the designspace would be the same as the 800 weight and everything would be fine. Only one variation (deltas from the 400 in glyf
) in the gvar
table, so very efficient.
"But how should the font know what to do in the 800-1000 range? There's no data so it might extrapolate or fall back to the 400 outlines." Okay, fine, let's call this "gvar stores corner masters". We add another master at 1000 which is identical to the master at 800, same deltas from the 400 default. Only two gvar
table entries, and because all the deltas are identical they could be shareable through a common subtable (we're imagining a different font format) so still very efficient.
Instead what we have right now is two regions, one which peaks at 800 (and then falls off towards 1000), and one which "tops up" the 800-1000 range:
The brown deltas are an absolute mirror image of the red ones (Σ(δ₀…δₙ₋₁) in 800-1000 is constant).
This is kind of bearable with one axis. Now let's add an opsz axis ranging from 6-18-144, where we are similarly not filling out the whole range: some glyphs are 17-18. In a "gvar stores masters" world we have wght=400,opsz=18 in glyf
and three deltas:
In "gvar stores corner masters" we have:
glyf
)11 deltas, but there are only three binary subtables because most of the corner masters are synthetic, and therefore sharable.
However, because gvar uses regions and has this "fades-away-and-requires-top-up" behaviour, we currently have to store all 11 sets of deltas:
Even though we are only representing four actual masters.
There are two possible ways around this:
1) An alternative interpolation model which actually does store deltas for masters at fixed points, not regions. 2) A per-glyph avar2 style designspace fencing where we just map everything from opsz=800-1000 to the same region, everything from opsz=6-17 and so on.
The upshot of this is that merging a 2.5M font four-master font into a 3.5M font produces a 26M font. (I then use horrible binary hacking to drop "small" gvar table entries to get it down to 15M...)
Tents that would just "stay up" towards the end of the axis would be very useful.
A per-glyph avar2 style designspace
@behdad what do you think the glyph limit will be imposed by avar2 in this case?
A related issue with gvar is a limitation with "shared tuples", such that only "peak" tuples can be shared; "start" and "end" tuples must be embedded in the gvar data for each glyph. Now if a font does not have any intermediate masters, this is fine, since "start" and "end" are implied by "peak", and a well-built font has all such tuples shared. But if a font has an intermediate form at the same designspace location in all glyphs — a very common case — then we waste lots of space duplicating our "start" and "end" tuple definitions as embedded tuples.
The issue gets potentially serious when axisCount gets large. The size of each tuple is axisCount * 2 (2 = sizeof F2DOT14).
Examples:
Font | #axes | #glyphs with intermediates | #embedded peak tuples | #embedded start/end tuples | Embedded tuple data (bytes) |
---|---|---|---|---|---|
Bitter | 1 | 14 | 1 | 28 | 114 |
Recursive | 5 | 371 | 0 | 4450 | 89000 |
It will be noted that if one was to 10x the number of axes in Recursive, all the new axes having no gvar deltas, the tuple data would nevertheless bloat by 10x to 890000 bytes.
Thus in any redesign of gvar I would hope that intermediate tuples can be shared, as well as peak tuples. One way to do this would be to store the intermediate tuples as sets of three (start, peak, end) in the sharedTuples array. A flag in the tuple variation tables would control whether only the peak or all 3 tuples should be read from the given tupleIndex. Note that, because of flags in the tupleIndex field, the maximum number of shared tuple ids is currently 4096, which may be too small. Another method would be to provide gvar an ItemVariationStore (containing 0 ItemVariationData structures) to store all the regions used in gvar, thereby simplifying TupleVariationHeader to point to a regionId in the IVS, dropping the fields tupleIndex, peakTuple, intermediateStartTuple, intermediateEndTuple.
Centralizing the storage of regions will also have a (marginal?) performance benefit, since the scalar associated with each region can be calculated just once, indexed by regionId. [edited]
"Tents that stay up" could be defined within the existing data structure: the startCoord
, peakCoord
and endCoord
fields are defined as F2DOT14
, and the spec says:
The three values must all be within the range -1.0 to +1.0. startCoord
We could say that if startCoord
is the lowest negative number F2DOT14
can represent, the tent must be "up" from -1.0 to peakCoord
. Similarly with endCoord
: if it is the highest positive number that F2DOT14
can represent, the tent must be "up" from its peakCoord
until 1.0.
TBH. I think that "tents that can stay up" are way more useful here than per-glyph avar2, given that the former easily "shelves" off multi-dimensional spaces beyond set limits, and the latter is already notoriously hard to express such a "fence", as so eloquently demoed by Laurence.
"Per-glyph avar2" doesn't make sense to me. You already can do that: define whatever new axis you want in avar2, and use it only in a subset of glyphs.
All limitations shown come from gvar. I'm working on a replacement that allows for better sharing, etc.
@simoncozens has been hacking up what is effectively merging a font like Roboto Flex into a font like the MegaMerged Noto Sans, and he said today:
I suspect that the current spec's gvar has limitations that real-world projects are (about to be) running into.
@justvanrossum also wrote me recently,
Do we need to explore spec changes that overcome limitations in gvar?