Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variable GPOS feature for non-variable glyph? (invalid gvar axisCount) #815

Closed
justvanrossum opened this issue May 18, 2024 · 26 comments · Fixed by googlefonts/fontations#919 or #833

Comments

@justvanrossum
Copy link

I was trying to find out whether fontc supports variable features, and while I think the error I'm getting proves that it does, I am getting an error, in the following case:

  • A .designspace project with a Weight axis
  • There is only one source: Regular (at Weight=400)
  • The one source contains one glyph A (a square of 500x500)
  • The following feature code:
feature test {
    pos A <100 0 (wght=400:200 wght=900:400) 0>;
} test;

fontmake compiles this into a variable font with a working variable GPOS feature.

fontc errors like this:

[2024-05-18T13:38:38.008202Z main fontc::workload ERROR] Be(Features) failed Fea compilation failure Compilation failed with 1 errors
error: failed to compute deltas: 'No variation model for 'Normalized {wght: 1.00}''
in varpos_Regular.ufo/features.fea at 2:17
  | 
2 |     pos A <100 0 (wght=400:200 wght=900:400) 0>;
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^

Tasks failed: [(Be(Features), "Fea compilation failure Compilation failed with 1 errors\n\u{1b}[31merror: \u{1b}[0mfailed to compute deltas: 'No variation model for 'Normalized {wght: 1.00}''\n\u{1b}[3;34min\u{1b}[0m varpos_Regular.ufo/features.fea \u{1b}[3;34mat\u{1b}[0m 2:17\n\u{1b}[34m  |\u{1b}[0m \n\u{1b}[34m2 |\u{1b}[0m     pos A <100 0 (wght=400:200 wght=900:400) 0>;\n\u{1b}[34m  |\u{1b}[0m                  \u{1b}[31m^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[0m\n")]

.designspace + .ufo:
varpos.zip

@justvanrossum
Copy link
Author

Sparsify all the things!

@anthrotype
Copy link
Member

anthrotype commented May 18, 2024

Can you try with #779 ?

@justvanrossum
Copy link
Author

Sorry, not instantly, as I'm running fontc from PyPI, not sources.

@anthrotype
Copy link
Member

I believe that should fix it, but cannot double check as I’m typing from my mobile. That PR is basically done, only needs a test to ratify it works as intended. I’ll try to complete it next week

@anthrotype
Copy link
Member

anthrotype commented May 20, 2024

I tried to build your test font with the fix-incorrect-feature-deltas-when-sparse branch from PR #779, and at least it did not crash this time. When I decompile with ttx I can see there's fvar table and the GDEF has item variation store with some deltas referenced from a variable GPOS table.

However the gvar table has some issues because ttx cannot decompile it:

  <gvar ERROR="decompilation error" raw="True">
    <!-- An error occurred during the decompilation of this table -->
    <!-- Traceback (most recent call last):
           File "/Users/clupo/oss/fontc/.venv/lib/python3.11/site-packages/fontTools/ttLib/ttFont.py", line 467, in _readTable
             table.decompile(data, self)
           File "/Users/clupo/oss/fontc/.venv/lib/python3.11/site-packages/fontTools/ttLib/tables/_g_v_a_r.py", line 118, in decompile
             assert len(axisTags) == self.axisCount
         AssertionError
          -->
    <hexdata>
      00010000 00000000 0000001a 00020000
      0000001a 00000000 0000 
    </hexdata>
  </gvar>


here's fontc-built font.ttf.zip

@justvanrossum
Copy link
Author

justvanrossum commented May 20, 2024

fontmake also produces a 26-byte length gvar table, but ttx decompiles that as an empty gvar table. Manually extracted, that empty gvar table contains these 26 bytes:

00010000 00010000 0000001a 00020000
0000001a 00000000 0000

The differing bytes are the "axisCount" field, so that does explain the error with the fontc version.

The spec says this about the axisCount field in gvar:

The number of variation axes for this font. This must be the same number as axisCount in the 'fvar' table.

So ttx's assert is reasonable.

@justvanrossum
Copy link
Author

(One could argue that the gvar table is entirely redundant in this case.)

@anthrotype
Copy link
Member

thanks! looks like an easy fix

@anthrotype anthrotype changed the title Variable GPOS feature for non-variable glyph? Variable GPOS feature for non-variable glyph? (invalid gvar axisCount) May 20, 2024
@anthrotype
Copy link
Member

the issue seems to be in fontations/write-fonts/src/tables/gvar.rs where it computes the axis_count for the new Gvar struct here:

https://github.com/googlefonts/fontations/blob/b397a31889336ddc6f02c0a8a3cdea6cc6dd5228/write-fonts/src/tables/gvar.rs#L77-L80

since in the case of an empty, non-variable gvar, all the GlyphVariations have axis_count None, the unwrap_or_default will fall back to 0 for the Gvar.axis_count as a whole.

Not sure how best to fix this. Should fontc simply mutate the Gvar and set the correct axis_count when it doesn't match the fvar axes.len()?

@justvanrossum
Copy link
Author

Maybe omit the gvar table completely in that case?

@anthrotype
Copy link
Member

is it not required?
what does fontmake do?

@justvanrossum
Copy link
Author

If I remember correctly, it is fvar that makes a TTF variable, and gvar shouldn't be required.

fontmake builds an empty gvar table.

@anthrotype
Copy link
Member

looks like the least risky action would be to emit an empty gvar too. I wonder what OTS say

@justvanrossum
Copy link
Author

https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview#variation-data-tables-and-miscellaneous-requirements says gvar is not required.

@anthrotype
Copy link
Member

anthrotype commented May 20, 2024

Ok, then I think we should fix both compilers to omit empty gvars

@cmyr
Copy link
Member

cmyr commented May 20, 2024

And just for information purposes: we did do initial work to support the variable FEA syntax, but then realized that it was much slower for us to generate variable FEA from "feature writers" and then send that to the FEA compiler than it would be to just generate lookups directly for kerning/marks, so we're currently doing that. The intention is to support variable FEA, though.

@justvanrossum
Copy link
Author

That's great to hear. The best .fea is no .fea.

@anthrotype
Copy link
Member

I just realised that fonttools deliberately adds the empty gvar because of some bug or old/bogus requirement in Adobe apps, see fonttools/fonttools#1855

I wonder if that's still relevant nowadays. Probably yes for the next 5-10 years until old software gets retired..

@anthrotype
Copy link
Member

@rsheeter by the way, it appears that gvar is treated specially by fontbe orchestration Context, it's set to Bytes whereas most of the other tables are wrapped by a BeValue, which allows them to be optional. I'd like to do something like

            context.gvar.set_unconditionally(None);

or equivalent (that is, skip writing gvar altogether) whenever all the GlyphVariations' axis_count turn out to be None (because no glyph deltas).

Why the context for gvar can't be BeValue? I did try but rustc complains something about Gvar not implementing some required traits (FontRead or Persistable, I haven't dug deeper).
Also setting to Option doesn't seem to work.

@anthrotype
Copy link
Member

anthrotype commented May 23, 2024

and yes, OTS does not complain when gvar is not present but fvar is (which is what makes a VF a VF)

@anthrotype
Copy link
Member

anthrotype commented May 23, 2024

looks like this would be sufficient to not emit gvar when all glyph variations are empty -- iff we want to go that way

diff --git a/fontbe/src/gvar.rs b/fontbe/src/gvar.rs
index a080938a..50071c0e 100644
--- a/fontbe/src/gvar.rs
+++ b/fontbe/src/gvar.rs
@@ -60,6 +60,12 @@ impl Work<Context, AnyWorkId, Error> for GvarWork {
                 .get(&WorkId::GvarFragment(glyph_name.clone()).into())
                 .to_deltas(&axis_order)
         });
+
+        // if no glyph variation contains any deltas, we can skip the gvar table entirely
+        if variations.iter().all(|v| v.axis_count().is_none()) {
+            return Ok(());
+        }
+
         let gvar = Gvar::new(variations).map_err(Error::GvarError)?;

         let raw_gvar = dump_table(&gvar)

But i'm a bit confused because other tables that are also optional, e.g. avar or MVAR, don't just return Ok() but they still do

        context.avar.set_unconditionally(avar.into());

where avar is an Option<Avar>, that's turned into a BeValue<Avar> IIUC

@anthrotype
Copy link
Member

so my question for @rsheeter is, is there a difference, within a fontbe Work's exec method, if one does call context.avar.set_unconditionally with a None value or does not call it at all?

@anthrotype
Copy link
Member

All in all, I'd be inclined to still emit the empty no-op gvar (20 bytes or so) if that makes some Adobe apps happier, however I don't have a way to test this. I'll ask in the font engineering chat

@anthrotype
Copy link
Member

this is the font compiled from varpos.designspace, without a gvar table, in case one wants to try this inside Adobe Indesign and confirm whether it works or not

font.ttf.zip

there's only one glyph "A" and one wght axis, the glyph itself contains no glyph variations but there's a GPOS feature called test (not an official registered tag.. I wonder if Adobe would like that) which, when activted, changes its advance width as the wght increases.

@rsheeter
Copy link
Contributor

rsheeter commented May 28, 2024

Based on f2f I think we should probably pass axis_count to https://github.com/googlefonts/fontations/blob/b397a31889336ddc6f02c0a8a3cdea6cc6dd5228/write-fonts/src/tables/gvar.rs#L70 because it cannot always be determined from the input.

EDIT: I'm assuming we have to emit gvar, if we can just not emit it that would be nice but IIUC that's bad for Adobe apps.

anthrotype added a commit to googlefonts/fontations that referenced this issue May 29, 2024
this ensures that we (e.g. fontc) can make valid empty gvar tables where the axis_count matches the expected fvar's axis_count

Will fix googlefonts/fontc#815 once fontc is updated to use the new API.

This is a breaking changes which warrants minor write-fonts version bump.
@anthrotype
Copy link
Member

@justvanrossum If you don't mind I'll use that varpos.designspace of yours as a test font in #833

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment