Closed behdad closed 1 year ago
cc @rsheeter @lorp @twardoch
There's no further requirements on the mapping. The mapping is NOT required to be reversible.
The pre-normalization/post-normalization to be determined / documented.
I suggested using varation math on avar
mappings in a call to @twardoch a few months ago. I should have written it up! I think we’ll need a new ItemVariationStore format, since we’re talking about deltas in n-space, not the 1-space of MVAR
/HVAR
/VVAR
or the 2-space of gvar
.
So if I understand your proposal correctly, these deltas are n-D vectors in normalized variation space, with linear response when 1 axis is non-default, quadratic response when 2 axes are non-default, cubic response when 3 axes are non-default, etc. As you say, normalization is to be determined. What about behaviour if the vector causes a transition across 0 on any axis? Maybe that’s fine.
On further thought:
Let's use a VarIdxMap
, aka DeltaSetIndexMap
for the (optional!) mapping. The mapping logic would be different from that of HVAR
table in that, unlike in HVAR
, the last entry is not repeated ad infinitum. Entries not in the mapping get a varidx of NO_VARIATION
(aka 0xFFFFFFFF
; or just 0 variation).
This also removes the 16bit limit on the number of axes, conceptually.
I also wanted to say let's borrow from how we extended COLRv1, and instead of going to major 2, go to major 1 minor 1, such that older implementations can use older representation still. While we can do that, it has some inconvenience because the old representation has to be parsed before new representation can be found. Still, I'm supportive of possibly doing it that way.
struct avar2 {
uint16 major; // set to 2
uint16 minor; // set to 0
Offset32To<ItemVariationStore> varStore; // VarStore mapping axes from unwarped user-space to warped design-space
Offset32To<DeltaSetIndexMap> map; // Optional VarIdxMap mapping axis index into VarIdx to be looked up in VarStore
};
The pre-normalization/post-normalization to be determined / documented.
Axis values are normalized to -1,+1 based on fvar
axis min,default,max; then they are transformed via avar1 or avar2. Finally they are clamped to -1,+1 again. Some implementations then convert them to F2DOT14 at this point.
There is a functional requirement that if all input axes are at their fvar
default position, then all output axes are at zero. But there's no mechanism to try to enforce that. OT1.8 made such an assumption and thought it enforced it, but failed at it.
There is a functional requirement that if all input axes are at their
fvar
default position, then all output axes are at zero.
Or rather, the functional requirement is that: if all input axes are at their fvar
default position, the output should be identical to situation that no deltas are applied. That is, as if all output axes are zero...
I suggested using varation math on
avar
mappings in a call to @twardoch a few months ago. I should have written it up! I think we’ll need a new ItemVariationStore format, since we’re talking about deltas in n-space, not the 1-space ofMVAR
/HVAR
/VVAR
or the 2-space ofgvar
.
No no no. gvar
is different in that it encodes spatial deltas.
ItemVariationStore
always encodes 1-dimensional scalar deltas. That doesn't have to change. We encode each axes deltas separately.
So if I understand your proposal correctly, these deltas are n-D vectors in normalized variation space,
Except that each dimension's deltas is separately encoded.
with linear response when 1 axis is non-default, quadratic response when 2 axes are non-default, cubic response when 3 axes are non-default, etc.
Correct. Same non-linearity that we enable across the system.
As you say, normalization is to be determined. What about behaviour if the vector causes a transition across 0 on any axis? Maybe that’s fine.
I tried to address that in my previous two comments.
So if I understand your proposal correctly, these deltas are n-D vectors in normalized variation space,
Except that each dimension's deltas is separately encoded.
How can you keep axes separate if an arbitrary location in n-space maps to another arbitrary location in n-space?
How can you keep axes separate if an arbitrary location in n-space maps to another arbitrary location in n-space?
Each output axis is a piecewise-linear function of all input axes.
Imagine a point rotating in HOI. Both output X and Y are functions of both input X and Y, but you can represent/encode them separately.
I don’t follow you. With 2 fvar
axes both acting on avar
, a ~point~ designspace location is acted on by the sum of axis0 × vector0 (linear), axis1 × vector1 (linear), and axis0 × axis1 × vector2 (quadratic), where the vectors are 2-D.
Now I don't follow you either. Let's get on VC.
There's no requirement that if all input axes are at default, then all output axes must be at zero. This would allow moving default freely. See: https://github.com/be-fonts/boring-expansion-spec/issues/17#issuecomment-924976649
We can declare fonts with avar
2 requiring variations processing. I want to think more about it though.
Will https://github.com/google/fonts/issues/2981#issuecomment-927606320 be supported?
Will google/fonts#2981 (comment) be supported?
Yes. No such constraints.
Related issues/links:
Apple's VF version of SF Pro just dropped last week, with similar VF axes to Roboto Serif, https://v-fonts.com/fonts/sf-pro
The grade axis isn't "safe" with the weight axis; a good case study for avar2 :)
Nice comments from @tiroj at https://twitter.com/TiroTypeworks/status/1524085630367506432
After some more thinking I decided it might be better to retain avar1 fields that do a linear per-axis mapping first, then do the full-matrix mapping. I also sketched a simple designspace-file xml that implements wght axis HOI: https://gist.github.com/behdad/7e74e0970280a0cf621671af6f0fa749#file-avar1-1-designspace-sketch
So this is the new avar table format I'm proposing, in fonttools otData format:
('avar', [
('Version', 'Version', None, None, 'Version of the avar table- 0x00010000 or 0x00020000'),
('uint16', 'Reserved', None, None, 'Permanently reserved; set to zero'),
('uint16', 'AxisCount', None, None, 'The number of variation axes for this font. This must be the same number as axisCount in the "fvar" table'),
('AxisSegmentMap', 'AxisSegmentMap', 'AxisCount', 0, 'The segment maps array — one segment map for each axis, in the order of axes specified in the "fvar" table'),
('LOffset', 'VarIdxMap', None, 'Version >= 0x00020000', ''),
('LOffset', 'VarStore', None, 'Version >= 0x00020000', ''),
]),
('LOffset', 'VarIdxMap', None, 'Version >= 0x00020000', ''),
Note this VarIdxMap is called DeltaSetIndexMap in the OT spec.
Here's sample script to generate avar2 table from RobotoFlex relationships between wght/wdth/opsz axes and its parametric axes: https://github.com/behdad/robotoflex-avar2/blob/main/robotoflex-avar2.py
The code for this is in HarfBuzz 5.0.0, and a FreeType patch is in:
https://gitlab.freedesktop.org/freetype/freetype/-/merge_requests/188
A sample font file (RobotoFlex) is in:
https://drive.google.com/file/d/1fcb9pyt6P7tDOnMf7AaTK4B7Apt4P1mK/view
The wght, wdth, and opsz axes are routed through avar2.
I'm drafting a spec for this here: https://github.com/harfbuzz/boring-expansion-spec/blob/avar2/avar2.md
This is complete.
Update: the aim of this proposal is just to enable non-orthogonal axes distortions. The higher-order interpolation will come by way of
ItemVariationStore
upgrades, via eg. https://github.com/be-fonts/boring-expansion-spec/issues/17Update: Settled on the following format: If version is
0x00020000
, the format1-like struct is followed by the following fields: