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

[skrifa] public color API #702

Merged
merged 3 commits into from
Nov 28, 2023
Merged

[skrifa] public color API #702

merged 3 commits into from
Nov 28, 2023

Conversation

dfrg
Copy link
Member

@dfrg dfrg commented Nov 15, 2023

#697 (comment) requests moving the color instance code from the scale module to the top level "metadata section" of the crate. This PR contains a proposal for such an API that meshes well with the current design of MetadataProvider.

This depends on #697 but is pulled out here for separate discussion.

One key point is the method for bounding box computation. It'd be nice to avoid a hard dependency on anything in the scale module so the function here takes a closure for that purpose.

@drott thoughts? There's some overlap with your ColorPainter work but I think this provides a nice entry point for that.

Expose a color outline API in top level metadata rather than in the scale module.
Copy link
Contributor

@drott drott left a comment

Choose a reason for hiding this comment

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

Thanks for this proposal, some thoughts below.

pub fn bounding_box(
&self,
location: impl Into<LocationRef<'a>>,
glyph_bounds: impl FnMut(GlyphId, &LocationRef, Transform) -> Option<BoundingBox>,
Copy link
Contributor

Choose a reason for hiding this comment

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

In trying to understand your intention, first some questions: So the difference from a "degenerate" ColorPainter implementation on the client side (to build a bounding box there) would be that the transform matrix is accumulated internally and we would call back less frequently, not for each node in the graph? Since there is no mention in the comment about what to do with compositions where the path through the graph forks, do you mean this to track the accumulated transform internally, and call back for each clip node with a fully spelled-out transform?

A previously configured, client-side transform (on the viewport for example) would be taken into account by the client multiplying that in when it receives the callback?

So the client would perform own path retrieval, combine its own possible viewport transform with the accumulated transform from the callback, and compute the bounding box of the path, with transform applied, and return that?

I do appreciate that this would disconnect the color module from the scale module - which I think make sense, as we discussed in the review of

This works as an interface, and we can shift slightly more code to the Rust side. I am trying to weigh whether this somewhat unusual API pattern is worth defining in addition to the to the paint traversal call, with which the client can compute a bounding box as well: it receives more callback and the matrix tracking would need to happen on the client side, but since we have that on the 2D graphics library side - that's doable there just as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

The answer to all of your questions is “yes” with the exception that this wouldn’t do any caching of paths internally although the client could certainly do so if they’d like to trade space for time. Seems to be a point in favor of this approach?

I hadn’t considered a client provided root transform. We can accept that as a parameter here and fold it in if you think that’d be more useful. Otherwise, yes, a client could apply it in the callback.

I have two general concerns:

  1. Handling edge cases (composition nodes, detecting unbounded subgraphs) is not exactly trivial so it would be nice to offer a correct implementation of this instead of pushing that burden to all potential clients.

  2. While not a top priority atm, I’m trying to keep the embedded case in mind wrt heap allocations. This interface would allow the caller to decide how to handle memory requirements for loading glyphs. Aside: that does leave the recursion blacklist as a potential problem— Rod and I discussed adding a cfg feature that uses a max recursion depth instead but that’s not something we need to address now.

}

/// Returns the color outline for the given glyph identifier.
pub fn get(&self, glyph_id: GlyphId) -> Option<ColorOutline<'a>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the direct access is more important than the iterator - so perhaps we can do without the maxp() access?

As discussed, in googlefonts/oxidize#48 - we need the (cheaply acquirable) information in Skia whether a glyph is COLRv0 and COLRv1. I think that's doable well with the type-based distinction of ColorOutlineKind.

Copy link
Member Author

Choose a reason for hiding this comment

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

I’d like to keep the iterator but I can implement it without maxp. Good catch.

Copy link
Member Author

Choose a reason for hiding this comment

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

Re detection: I can add a version() method to ColorOutline that returns 0 or 1

painter: &mut impl ColorPainter,
) -> Result<(), ReadError> {
let instance = instance::ColrInstance::new(self.colr.clone(), location.into().coords());
match &self.kind {
Copy link
Contributor

Choose a reason for hiding this comment

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

I am aware this is a draft - but how where does self.kind get initialized here - that would be a ColorOutlineKind retrieved for self.glyph_id, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

The kind field is initialized in ColorOutlineCollection::get. Yes, it is the outline for the associated glyph_id. I added that field (and accessor) to support iteration since the table can be sparse. It might make sense to remove that and change the iterator to yield a tuple (GlyphId, ColorOutline)

@@ -47,6 +48,11 @@ pub trait MetadataProvider<'a>: raw::TableProvider<'a> + Sized {
fn charmap(&self) -> Charmap<'a> {
Charmap::new(self)
}

/// Returns the collection of color outlines.
fn color_outlines(&self) -> ColorOutlineCollection<'a> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think this works as a good entry point. And is perhaps a bit more idiomatic than just a "do_traverse_paint" function that takes a glyph id somewhere. It also answers the "what glyph format is available" question.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. I’m trying to keep the API as consistent as possible. Fonts don’t make it easy 🤷

dfrg added 2 commits November 16, 2023 11:30
- add version() method to ColorOutline
- remove maxp lookup and change iterator to use max glyph in COLR table
- remove glyph_id from ColorOutline and change iterator to return (GlyphId, ColorOutline) tuple
@dfrg dfrg merged commit 2c9308a into colr-float Nov 28, 2023
7 checks passed
@rsheeter rsheeter deleted the colr-api branch June 10, 2024 03:44
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.

2 participants