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

Is it unsound to perform text shaping while an OutlineBuilder exists? #72

Closed
LoganDark opened this issue Jun 14, 2022 · 9 comments
Closed
Labels

Comments

@LoganDark
Copy link
Contributor

LoganDark commented Jun 14, 2022

In order to get an OutlineBuilder, you have to do some parsing using the font's FontTableProvider. This means the OutlineBuilder borrows from the FontTableProvider, which means it borrows from the Font.

Then, if you need to shape some text, you need to... call Font::shape which takes &mut self.

This is aliasing a mutable reference with preexisting shared references.

That means the most obvious low-hanging-fruit for caching (obtaining the OutlineBuilder up-front at load time) is unsound.

I did not notice this because I have to use piles of unsafe code to store the font and related data in a pinned struct. Therefore the borrow checker is significantly inhibited. see #52

Now that I think about it, it seems like a huge API issue, as well as a performance issue. Parsing tables to get an OutlineBuilder each frame in a game for example (required not to violate the borrowing rules), sounds like a huge problem.

@wezm
Copy link
Contributor

wezm commented Jun 15, 2022

OutlineBuilder is just a trait so it has no state of it's own. It only borrows the underlying table (mutably) when visit is called. Unless I'm missing a detail?

@LoganDark
Copy link
Contributor Author

OutlineBuilder is just a trait so it has no state of it's own. It only borrows the underlying table (mutably) when visit is called. Unless I'm missing a detail?

The actual implementations of OutlineBuilder require borrowing a bunch of data obtained from the DynamicFontTableProvider

@wezm
Copy link
Contributor

wezm commented Jun 15, 2022

Right it seems that I was misunderstanding your question. Normally you would not be able to call shape while holding an OutlineBuilder impl because the borrow checker would not allow it. However if you do manage to achieve this via unsafe are you asking if it's unsound?

If so, shaping currently mutates the gsub_cache, gpos_cache, glyph_cache, and the lazy loaded GDEF table (if it's not already loaded) on Font. So it might be ok to hold onto the OutlineBuilder but I can't guarantee this will always be the case.

Now that I think about it, it seems like a huge API issue, as well as a performance issue. Parsing tables to get an OutlineBuilder each frame in a game for example (required not to violate the borrowing rules), sounds like a huge problem.

As you pointed out in #52 the library is not currently well structured for this type of use. The API evolved for our use case which is load a bunch of fonts, shape the text, and then generate a PDF. I'm not sure if/when we'll be able to dedicate the time to remedying that.

@LoganDark
Copy link
Contributor Author

LoganDark commented Jun 15, 2022

If so, shaping currently mutates the gsub_cache, gpos_cache, glyph_cache, and the lazy loaded GDEF table (if it's not already loaded) on Font. So it might be ok to hold onto the OutlineBuilder but I can't guarantee this will always be the case.

I'm saying that it's definitely unsound because there are aliasing mutable references which is forbidden.

As you pointed out in #52 the library is not currently well structured for this type of use. The API evolved for our use case which is load a bunch of fonts, shape the text, and then generate a PDF. I'm not sure if/when we'll be able to dedicate the time to remedying that.

This is kind of sad, because allsorts is actually kind of fast and extremely featureful. I'm having no problems shaping/rasterizing text every single frame in software (the rasterizer is ab_glyph_rasterizer). It's just that the code currently doing that is invoking Slightly Undefined Behavior. :(

@wezm
Copy link
Contributor

wezm commented Jun 15, 2022

This is kind of sad, because allsorts is actually kind of fast and extremely featureful. I'm having no problems shaping/rasterizing text every single frame in software (the rasterizer is ab_glyph_rasterizer). It's just that the code currently doing that is invoking Slightly Undefined Behavior. :(

Perhaps a silly idea: what if you parsed the font twice and used one instance for the OutlineBuilder and one for shaping?

@LoganDark
Copy link
Contributor Author

This is kind of sad, because allsorts is actually kind of fast and extremely featureful. I'm having no problems shaping/rasterizing text every single frame in software (the rasterizer is ab_glyph_rasterizer). It's just that the code currently doing that is invoking Slightly Undefined Behavior. :(

Perhaps a silly idea: what if you parsed the font twice and used one instance for the OutlineBuilder and one for shaping?

I had this idea once but I actually forgot to try it. This will probably work. In fact, I don't see any reason why it wouldn't. The data structures returned by a call to shape do not contain any references to the font, and therefore can just be... transferred. However you want.

This can be used to cause panics and crashes and unexpected behavior... or it can be used for performance.

I'm definitely going to try implementing that now, thank you!

@LoganDark
Copy link
Contributor Author

@wezm Is it possible for Font to implement Clone so that it can share some of the reference-counted data structures? Or would that break safety somehow?

@wezm
Copy link
Contributor

wezm commented Jun 23, 2022

If the compiler allows it then there shouldn't be any safety issues.

@LoganDark
Copy link
Contributor Author

LoganDark commented Jun 23, 2022

dang, DynamicFontTableProvider and its darn Box. probably should get rid of that one day

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

No branches or pull requests

2 participants