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

Add support for reusable ShapePlans. #88

Merged
merged 4 commits into from
Nov 29, 2023

Conversation

vorporeal
Copy link
Contributor

This exposes ShapePlan outside of the rustybuzz crate, and provides a new shape_with_plan function that takes in a ShapePlan instead of a list of user features.

Callers are expected to properly cache ShapePlans to avoid incorrect rendering due to mismatches between the ShapePlan and the provided buffer. To help avoid bugs due to mismatches, I added two debug_assert!() lines to verify that the plan and buffer use the same direction and script.

Given that ShapePlan is now public outside of the crate, I changed a number of pub specifiers to pub(crate) to maintain the same effective visibility.

Comment on lines +50 to +51
buffer.script.unwrap_or(script::UNKNOWN),
plan.script.unwrap_or(script::UNKNOWN)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The need for .unwrap_or(script::UNKNOWN) here is due to the fact that the UnicodeBuffer::script() function returns a Script (instead of an Option<Script>), and performs the same unwrapping logic.

The shape plan caching I implemented in cosmic-text to test this invoked UnicodeBuffer::script() to get the script value to pass to the ShapePlan constructor, so the assertion would fail if buffer.0.script is None, because the ShapePlan would have been constructed with Some(script::UNKNOWN).

@vorporeal vorporeal marked this pull request as ready for review November 28, 2023 19:55
@vorporeal vorporeal mentioned this pull request Nov 28, 2023
src/shape.rs Outdated Show resolved Hide resolved
src/plan.rs Outdated
@@ -67,6 +73,7 @@ pub struct ShapePlanner<'a> {
pub script_zero_marks: bool,
pub script_fallback_mark_positioning: bool,
pub shaper: &'static ComplexShaper,
pub user_features: Vec<Feature>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we have to allocate it again?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can pass user_features directly to compile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both collect_features() and compile() require the list of features. We could change the signature for compile() to also take the list of features, though that opens the (unlikely) possibility that different lists of features are passed.

That said, this doesn't allocate again - constructing an empty Vec performs no heap allocations, so there's no allocation in new(). We only allocate once, in collect_features(), when we call user_features.to_vec().

Given the fact that there isn't actually any performance cost here, and the fact that collect_features() is a more natural place to set self.user_features, this seems better to me.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But compile is a private method. It cannot be misused.

As for allocation, yes, my bad. There is none. But the code is still confusing. It confused me. I would prefer to avoid storing user_features in the ShapePlanner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, done. I'm used to working on codebases with many more contributors (and PR approvers), where there's more value in protecting even private functions from misuse within the codebase. :)


/// Shapes the buffer content using provided font and features.
///
/// Consumes the buffer. You can then run `GlyphBuffer::clear` to get the `UnicodeBuffer` back
/// without allocating a new one.
pub fn shape(face: &Face, features: &[Feature], buffer: UnicodeBuffer) -> GlyphBuffer {
pub fn shape(face: &Face, features: &[Feature], mut buffer: UnicodeBuffer) -> GlyphBuffer {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would add something along the lines to the doc here:

If you plan to shape multiple strings using the same [`Face`] prefer [`shape_with_plan`].
This is because [`ShapePlan`] initialization is pretty slow and should preferably be called once for each [`Face`].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call; done.

@vorporeal vorporeal requested a review from RazrFalcon November 28, 2023 20:46
@RazrFalcon RazrFalcon merged commit 3ea11c8 into harfbuzz:master Nov 29, 2023
1 check passed
@RazrFalcon
Copy link
Collaborator

Thanks! I will publish a new version soon.

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