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

Only write explicit tuple min/maxes if there is an intermediate tent #1345

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

Hoolean
Copy link
Contributor

@Hoolean Hoolean commented Feb 6, 2025

Moved from original implementation in googlefonts/fontc#1225.

Motivation for moving:

I've hit a road block adding tests: fontations' GlyphDeltas is public but its fields are not, and so intermediate_region cannot be inspected after construction.

We could make this field public, but I am tempted to argue that we make the wider change of moving the abstraction boundary lower into fontations, and treat this as serialisation logic.

e.g. give GlyphDeltas a Vec<(F2Dot14, F2Dot14, F2Dot14)> (or custom struct equivalent) and determine whether explicit intermediate coordinates are needed to be written at serialisation time.

I think this optimisation is higher-level than delta set optimisation but lower-level than IUP optimisation -- it is lossless, ttx does not expose it, and there has never been motivation to toggle it -- and so it may be more ergonomic there. We could avoid unzipping until serialisation time, ditch the assert that the tuple lengths are the same, and gain a bit more immutability for example.


This PR roughly sketches how the optimisation could look in a new home here instead. In general I think it has reduced the complexity of the code in the original PR, at the cost of some additional Vecs that could be targeted later if we end up in the hot path, and some boilerplate when we already know we do not have intermediate regions in the test cases.

If we are happy with this direction then we can polish the types and tidy the PR, and I will look to furnish it with unit tests too.

Copy link
Member

@cmyr cmyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good direction, would be happy to get this in after a bit of polish.

let required_intermediates = axis_coordinates
.iter()
.map(|coords| coords.required_intermediates())
.collect::<Option<Vec<_>>>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be able to get rid of this allocation and do this all in one iter chain? I think? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This did indeed turn out to be elegant and possible, with this neat new addition: https://users.rust-lang.org/t/unzip-with-error-handling/110250/2

...but I then realised that when reimplementing in fontations I had accidentally switched the semantics from any(...) to all(...), and so unfortunately it all had to be scrapped again ☔

With the intention corrected, there doesn't seem to be a better semantic match than a mutable fold, which @RickyDaMa and I have now implemented, although it does at least stay readable and keep allocations to a minimum.

In general, representing a combination of collect(...) and any(...) in an iter chain has turned out to be a bit of an abstraction tar-pit; each time you follow an improvement, you get bumped back into the same narrow path :(

write-fonts/src/tables/gvar.rs Show resolved Hide resolved
Hoolean and others added 5 commits February 11, 2025 17:35
Moved from original implementation in googlefonts/fontc#1225, see
discussion.

This optimisation is also implemented in fontTools, and reduced `gvar`
table size by ~50% when testing locally on a large variable font.
Unfortunately we lose some of the simplicity of the prior approach, as
there is no Option `collect()` for `any()`, and we need to make the
implied intermediates available outside of the coordinates struct.
@Hoolean
Copy link
Contributor Author

Hoolean commented Feb 11, 2025

I've now added an upstream fontc PR to follow the new API, and unit tests for the new logic; we'll probably want to feed a font all the way through a ttx-diff pipeline before merging

@Hoolean Hoolean marked this pull request as ready for review February 11, 2025 19:19
@Hoolean
Copy link
Contributor Author

Hoolean commented Feb 11, 2025

We should be good to undraft here - ready for review!

}
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so I'm trying to think through this, and understand if this is ultimately the API that makes the most sense.

Thinking out loud:

  • I like the term 'tent', which although it was not immediately intuitive to me has the advantage that it is used elsewhere in the font ecosystem, and is fairly easy to understand.
  • having an explicit Intermediate type feels maybe too verbose. If I look at AxisCoordinates::new, it even takes Option<Intermediate>, so if I don't need intermediates am I supposed to pass None, or am I supposed to call implied_by_peak?

My instinct, then, would be to keep using the name Tent, instead of AxisCoordinate, and so I'd have something like,

.... okay so I started sketching out what I would've expected to see, and then it just ended up easier for me to push a commit. Check out e61f2cf and see if it makes sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants