boring-expansion-spec icon indicating copy to clipboard operation
boring-expansion-spec copied to clipboard

Allow moving default instance of a varfont with avar2

Open behdad opened this issue 2 years ago • 35 comments

Imagine we have a font with only two masters, at wght=100 and wght=1000, but we want the default of the font to be at wght=400. OT1.8 variation model requires that a master be added at wght=400. We want to enable not requiring that.

This is almost possible already with avar2, just needs a couple of minor tweaks.

  • First tweak is that we like the client to process avar2 even if no variations are set on the font. Without that it's impossible to do this. The no-variation-processing if variations are not set is an optimization (a good one). To process avar2 doesn't fully disable that optimization, whereas now one can enable the optimization if the output of avar2 was a "no-variation", ie. all axes were normalized-0.

With the above, then the way to shift the default of the font would be to, eg. take the normalized input wght values of -1,0,+1 (corresponding to 100,400,1000) and shift them to eg. 0,.4,+1. This is almost possible already, except for another glitch, which is that in the OT1.8 model, there's supposed to be no delta values at the normalized=0 (ie. default) location. That is the same optimization idea. However, the no-delta-at-default is an optimization only. The format does allow for storing deltas at default. If a delta-set specifies no axis, it will applies always. So, by requiring processing the ItemVariationStore at all locations, even at default, we can enable shifting 0 to eg. +.4. So:

  • We need to relax a restriction of OT1.8 variation model, at least in the avar2 VarStore processing, to always add the delta from the VarStore even if the normalized axis values are all 0.

Whether the second change should be done across font-wide variation-processing is another decision to be made.

behdad avatar May 18 '23 17:05 behdad

cc @nedley I would appreciate your feedback. cc @justvanrossum

behdad avatar May 18 '23 21:05 behdad

cc @frankrolf

behdad avatar May 18 '23 21:05 behdad

Some references:

  • https://github.com/fonttools/fonttools/issues/1020
  • https://www.microsoft.com/typography/otspec/otvarcommonformats.htm:

If the given axis should not factor into the scalar calculation for a region, then this is achieved by setting peakCoord to zero. In this case, startCoord can be any non-positive value, and endCoord can be any non-negative value. It is recommended either that all three be set to zero, or that startCoord be set to -1.0 and endCoord be set to +1.0.

To me it seems that the above is necessary only for gvar/cvar, which is defined so that each variation tuple contains a tent for each fvar axis, and therefore needs a way to say "this axis does not participate". VarStore doesn't need that.

https://github.com/fonttools/fonttools/issues/1020 was fixed here: https://github.com/fonttools/fonttools/commit/a1629fa52bddad12efe49f5e9565f96c3a00ae8c, but I now believe it could also have been fixed by changing the following line to only check the peak value, instead of start/peak/end:

  • https://github.com/fonttools/fonttools/blob/65bc6105f7aec3478427525d23ddf2e3c8c4b21e/Lib/fontTools/ttLib/tables/TupleVariation.py#L765

justvanrossum avatar May 19 '23 08:05 justvanrossum

@behdad while there may be strong use cases for "moving the default" as you describe using avar2, it seems to me that the frequently requested use case you describe is much better handled in fvar. For example, we can take one of the 16 InstanceRecord flags to declare that this instance should be used as the default instance.

A separate issue is that perhaps we need a new name for this kind of instance, such as "representative", because "default" sounds too much like "fallback", and there’s a lot of documentation defining default instance as what is used when variations are unavailable.

Lorp avatar May 19 '23 21:05 Lorp

@behdad while there may be strong use cases for "moving the default" as you describe using avar2, it seems to me that the frequently requested use case you describe is much better handled in fvar. For example, we can take one of the 16 InstanceRecord flags to declare that this instance should be used as the default instance.

That's an interesting approach.

behdad avatar May 19 '23 21:05 behdad

use case you describe is much better handled in fvar

I'm not sure that's true. Each fvar axis has a default value, and that is a fine user-facing concept. Assigning a "default location" via the instances fundamentally goes against that concept.

justvanrossum avatar May 20 '23 09:05 justvanrossum

A separate issue is that perhaps we need a new name for this kind of instance, such as "representative", because "default" sounds too much like "fallback", and there’s a lot of documentation defining default instance as what is used when variations are unavailable.

Then we'd have two names for two related but subtly different concepts. I think that's a very bad idea.

justvanrossum avatar May 20 '23 09:05 justvanrossum

Then we'd have two names for two related but subtly different concepts. I think that's a very bad idea.

Hmm, but @Behdad’s proposal already undermines one well-established meaning for Default, being the instance you get when variations are unavailable. Is that meaning already redundant?

Each fvar axis has a default value, and that is a fine user-facing concept.

What are the disadvantages of treating the Representative instance as the Default in apps?

Lorp avatar May 20 '23 20:05 Lorp

one well-established meaning for Default, being the instance you get when variations are unavailable

I don't think this meaning is very relevant anymore for end users.

The requested feature fundamentally cannot work in situations when variations are unavailable, so I am not sure how relevant the observation is.

We don't want to change anything from the user's perspective in a vf-savvy environment, we just want to make it possible to build fonts that don't need a master at the default to actually not have a master at the default. The user-facing parts of the font shouldn't change: you'd give a user an updated replacement font and all they would notice is that the font file is smaller.

justvanrossum avatar May 20 '23 20:05 justvanrossum

We don't want to change anything from the user's perspective in a vf-savvy environment, we just want to make it possible to build fonts that don't need a master at the default to actually not have a master at the default. The user-facing parts of the font shouldn't change: you'd give a user an updated replacement font and all they would notice is that the font file is smaller.

I’m with you all the way on this wish! I’m simply proposing the same result with less maths.

Lorp avatar May 21 '23 21:05 Lorp

If my understanding is correct, the request here is to allow avar2 (or something else, perhaps an fvar flag) to enable variation processing for what would otherwise be the default instance; the claimed use case for this is to allow a variable font to be regenerated with fewer deltas. Since there’s no way of accomplishing this without producing a different default instance for clients that don't support variations, my question is: why not go ahead change the default instance from wght=400 to 100?

nedley avatar May 26 '23 23:05 nedley

This is based on a suggestion I made at the recent ATypI panel on Variable Fonts.

My main concern – in most cases, a corner of the VF designspace is not a suitable representative of the family as a whole. Usually, the Regular (or equivalent) is at the core of the design, which is the style that will also get the most use.

What I was hoping for is a flag (for VF-savvy systems) that indicates “this style is my design representative”. As such, my suggestion was based on aesthetic considerations, not technical reasons.

frankrolf avatar May 27 '23 07:05 frankrolf

cc @skef

behdad avatar Jul 25 '23 18:07 behdad

Thanks @behdad

Adobe also favors using an fvar InstanceRecord bit for this purpose. Here's our write-up on this that we'll probably start circulating elsewhere: vfdefault.pdf

Note that we don't think this should necessarily preclude other options, such as avar remapping, for cases where that makes sense. We do think that there should be a simpler option for font engineers that don't want to tread into those deep waters.

In researching this the main objection to using an fvar instance seems to be that some people object to that mechanism generally, in favor of this or that kind of entry in STAT. My tentative response to that concern would be:

  1. fvar instances seem to be the most salient means of picking out a particular point in design space
  2. If some people would prefer that particular InstanceRecords only be used for particular cases (such as setting what we've called the "interface default", or allowing access to an instance via a string, or presenting an instance in a UI), we should figure out how to separate out those cases with additional flag bits.

skef avatar Jul 25 '23 18:07 skef

Adobe also favors using an fvar InstanceRecord bit for this purpose. Here's our write-up on this that we'll probably start circulating elsewhere: vfdefault.pdf

Note that we don't think this should necessarily preclude other options, such as avar remapping, for cases where that makes sense. We do think that there should be a simpler option for font engineers that don't want to tread into those deep waters.

Thanks @skef. I agree that fvar flag makes most sense. I still want to relax some conditions on avar2 if feasible.

behdad avatar Jul 25 '23 18:07 behdad

Adding link to TypeDrawers discussion Are customers buying or using variable fonts? where the idea of "representative style" and "representative instance" is raised.

Some thought is needed for recommended behaviour in CSS. Should the Default instance or the Representative instance be used when no axis locations are specified? And what if some axis locations are specified but not others? Should unspecified axes default to Default or should they default to Representative?

Lorp avatar Jul 25 '23 19:07 Lorp

Some thought is needed for recommended behaviour in CSS.

HTML text always has currently-set weight/width/slant/opsz. So it doesn't care about the default instance as long as those axes exist.

This whole issue is mostly moot on most systems.

behdad avatar Jul 25 '23 19:07 behdad

Tentatively:

  1. "Should the Default instance or the Representative instance be used when no axis locations are specified?" The representative instance
  2. "And what if some axis locations are specified but not others? Should unspecified axes default to Default or should they default to Representative?" Again to representative, I think. Adding some data should be continuous with adding no data.

skef avatar Jul 25 '23 19:07 skef

@behdad Good point, browsers already set wght=400 (or 700 if in H1, etc.), wdth=100, opsz=<currentPxSize>, which should override whatever is in Representative Instance. So it’s only other axes that this question applies to. See https://codepen.io/lorp/pen/MWzqxej

Apps following standard browser behaviour — by applying sensible defaults for registered axes — would fix a lot of the practical problems here, in which case the Representative Instance flag would only be necessary for unregistered axes. Google’s separately maintained axis registry could conceivably also become respected by apps in terms of default behaviour.

Lorp avatar Jul 25 '23 19:07 Lorp

FWIW, I'd like to rephrase why I don't like the "representative instance" idea: we already have a perfectly good mechanism from the consumer's perspective to specify the default, which is the default axis values in fvar. With a "representative instance" flag, we add a new mechanism on top of the old mechanism for the sole reason that the implementation of the old mechanism is incapable of a desired feature. So instead of fixing the underlying problem, the "representative instance" adds another layer of complexity for the consumer.

It is adding a new API just because it is difficult to fix the implementation of the (identical) existing API.

To me this feels very much like the "name IDs 1 and 2 have implementation limits that we don't want to fix, so let's add some more name IDs" way of thinking.

I will admit that there is a possibility that the initial cost of fixing the existing implementations is higher than adding this new API, but having a more complex surface area for consumers is something that will stay forever, while implementation fixes are a one-time thing.

edit: avar2 is so close to being able to address this issue, yet we choose to make the overall VF consumer surface more complex instead?

justvanrossum avatar Jul 26 '23 05:07 justvanrossum

@justvanrossum Can you say more about how this addition would wind up being consumer-visible? It seems to me that the goal is to allow a variable font that doesn't have its format default (all axes 0) at the desired interface default/starting point to act the same way as a font that does, and the flag proposal would meet that goal. I'm not aware of how a user would even be aware of the difference short of digging into the font file.

(Or maybe by "consumer" you mean system-level font software?)

As for the meaning of "default" in the axis description in fvar, that seems more like misleading documentation to me than anything. The value put there is always interpreted as corresponding to axis point 0 and should probably be described as such. (The description, at least in the Microsoft docs, makes it sound a bit like you're picking the default location of the axis by setting the value, when in fact you're picking the numeric value to assign the location you've already picked.)

skef avatar Jul 26 '23 06:07 skef

(Or maybe by "consumer" you mean system-level font software?)

Yes.

As for the meaning of "default" in the axis description in fvar, that seems more like misleading documentation to me than anything. The value put there is always interpreted as corresponding to axis point 0 and should probably be described as such. (The description, at least in the Microsoft docs, makes it sound a bit like you're picking the default location of the axis by setting the value, when in fact you're picking the numeric value to assign the location you've already picked.)

I'm not sure I follow.

Here we should be talking about end users. What does "axis point 0" mean to an end user? fvar axes are the translations from end user visible values to the internals (normalized space), so the "default axis value" is a meaningful bit of information: "if you don't touch this dial, this is where you'll be" (with CSS overruling that idea for wght and wdth), and I think VF UIs should (and generally do) respect that information, by initializing all axis dials to the default value.

justvanrossum avatar Jul 26 '23 06:07 justvanrossum

Btw. another way to achieve "user-level default should not imply font-implementation-varition-model-default" is by lifting the ~avar2~ avar1 restriction that 0 must map to 0 in axisValueMaps.

justvanrossum avatar Jul 26 '23 06:07 justvanrossum

so the "default axis value" is a meaningful bit of information: "if you don't touch this dial, this is where you'll be" (with CSS overruling that idea for wght and wdth), and I think VF UIs should (and generally do) respect that information, by initializing all axis dials to the default value.

Sure, but that leaves open the question of what that default is for a given font. If the user has no idea that there is a format-internal 0 value for each axis then there's no particular significance to having the default correspond to 0 versus something else.

I partially sympathize with the "not repairing what's broken" argument you're making, but I think I disagree about what is broken and what the current specification is really saying. Arguably nothing is broken. The current spec puts special significance on 0 axis values. Some people want to build fonts differently because they have corner masters and interpolating an extra master to effect the desired default instance makes the file bigger. The current spec says is, in effect, "Don't do that. Interpolate an extra master for that location and accept the added size." That's the purity argument.

Some people want to build variable fonts without that restriction, because file size is relevant. So what we're exploring is how to change things to allow that. I don't personally see "specification purity" in the avar 2 answer to the question. I also think that it's a complicated answer to a simple problem, and I worry that if some font engineers are forced to use it for this purpose they'll no longer fully understand the fonts they're creating.

skef avatar Jul 26 '23 06:07 skef

So, is your main point that you don't want to have to use avar2 just for not having a master at the location implied by the fvar default axis values?

justvanrossum avatar Jul 26 '23 06:07 justvanrossum

Yes. Moving 0 with avar 2 is "graduate level" font engineering. Some folks just want to build their 2 or 4 master fonts.

skef avatar Jul 26 '23 06:07 skef

(Which, it's worth noting, actually works just fine now except for this problem of initial axis values. All the other stuff remains entirely workable with only "extreme" masters.)

skef avatar Jul 26 '23 06:07 skef

The solution (with modified avar2 or even modified avar1) would be trivial to add to fontmake, so I don't believe the font engineering difficulty argument.

justvanrossum avatar Jul 26 '23 06:07 justvanrossum

The solution (with modified avar2 or even modified avar1) would be trivial to add to fontmake, so I don't believe the font engineering difficulty argument.

I meant to concretely describe this: if a designspace file does not specify a source at the default location for all axes, fontmake will complain. It could instead pick another source as the internal default source, and apply avar mapping to fix.

justvanrossum avatar Jul 26 '23 06:07 justvanrossum

And you're saying that the font engineer will still have reasonable intuition for how the user space maps to the internal space space when debugging issues, etc?

skef avatar Jul 26 '23 07:07 skef