Skip to content

Commit

Permalink
Add support for reusable ShapePlans.
Browse files Browse the repository at this point in the history
  • Loading branch information
vorporeal authored Nov 29, 2023
1 parent d6c45ab commit 3ea11c8
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 48 deletions.
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ pub use crate::buffer::{
};
pub use crate::common::{script, Direction, Feature, Language, Script, Variation};
pub use crate::face::Face;
pub use crate::shape::shape;
pub use crate::plan::ShapePlan;
pub use crate::shape::{shape, shape_with_plan};

type Mask = u32;

Expand Down
70 changes: 38 additions & 32 deletions src/plan.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,50 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::any::Any;

use crate::complex::{complex_categorize, ComplexShaper, DEFAULT_SHAPER, DUMBER_SHAPER};
use crate::ot::{self, feature, FeatureFlags, TableIndex};
use crate::{aat, Direction, Face, Feature, Language, Mask, Script, Tag};

/// A reusable plan for shaping a text buffer.
pub struct ShapePlan {
pub direction: Direction,
pub script: Option<Script>,
pub shaper: &'static ComplexShaper,
pub ot_map: ot::Map,
pub aat_map: aat::Map,
pub(crate) direction: Direction,
pub(crate) script: Option<Script>,
pub(crate) shaper: &'static ComplexShaper,
pub(crate) ot_map: ot::Map,
pub(crate) aat_map: aat::Map,
data: Option<Box<dyn Any>>,

pub frac_mask: Mask,
pub numr_mask: Mask,
pub dnom_mask: Mask,
pub rtlm_mask: Mask,
pub kern_mask: Mask,
pub trak_mask: Mask,

pub requested_kerning: bool,
pub requested_tracking: bool,
pub has_frac: bool,
pub has_vert: bool,
pub has_gpos_mark: bool,
pub zero_marks: bool,
pub fallback_glyph_classes: bool,
pub fallback_mark_positioning: bool,
pub adjust_mark_positioning_when_zeroing: bool,

pub apply_gpos: bool,
pub apply_fallback_kern: bool,
pub apply_kern: bool,
pub apply_kerx: bool,
pub apply_morx: bool,
pub apply_trak: bool,
pub(crate) frac_mask: Mask,
pub(crate) numr_mask: Mask,
pub(crate) dnom_mask: Mask,
pub(crate) rtlm_mask: Mask,
pub(crate) kern_mask: Mask,
pub(crate) trak_mask: Mask,

pub(crate) requested_kerning: bool,
pub(crate) requested_tracking: bool,
pub(crate) has_frac: bool,
pub(crate) has_vert: bool,
pub(crate) has_gpos_mark: bool,
pub(crate) zero_marks: bool,
pub(crate) fallback_glyph_classes: bool,
pub(crate) fallback_mark_positioning: bool,
pub(crate) adjust_mark_positioning_when_zeroing: bool,

pub(crate) apply_gpos: bool,
pub(crate) apply_fallback_kern: bool,
pub(crate) apply_kern: bool,
pub(crate) apply_kerx: bool,
pub(crate) apply_morx: bool,
pub(crate) apply_trak: bool,

pub(crate) user_features: Vec<Feature>,
}

impl ShapePlan {
/// Returns a plan that can be used for shaping any buffer with the
/// provided properties.
pub fn new(
face: &Face,
direction: Direction,
Expand All @@ -49,10 +55,10 @@ impl ShapePlan {
assert_ne!(direction, Direction::Invalid);
let mut planner = ShapePlanner::new(face, direction, script, language);
planner.collect_features(user_features);
planner.compile()
planner.compile(user_features)
}

pub fn data<T: 'static>(&self) -> &T {
pub(crate) fn data<T: 'static>(&self) -> &T {
self.data.as_ref().unwrap().downcast_ref().unwrap()
}
}
Expand Down Expand Up @@ -233,8 +239,7 @@ impl<'a> ShapePlanner<'a> {
}
}

// TODO: to just self
fn compile(&mut self) -> ShapePlan {
fn compile(mut self, user_features: &[Feature]) -> ShapePlan {
let ot_map = self.ot_map.compile();

let aat_map = if self.apply_morx {
Expand Down Expand Up @@ -350,6 +355,7 @@ impl<'a> ShapePlanner<'a> {
apply_kerx,
apply_morx,
apply_trak,
user_features: user_features.to_vec(),
};

if let Some(func) = self.shaper.create_data {
Expand Down
56 changes: 41 additions & 15 deletions src/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,59 @@ use crate::buffer::{
use crate::complex::ZeroWidthMarksMode;
use crate::plan::ShapePlan;
use crate::unicode::{CharExt, GeneralCategory, GeneralCategoryExt};
use crate::{aat, fallback, normalize, ot, Direction, Face, Feature, GlyphBuffer, UnicodeBuffer};
use crate::{
aat, fallback, normalize, ot, script, Direction, Face, Feature, GlyphBuffer, UnicodeBuffer,
};

/// Shapes the buffer content using provided font and features.
///
/// Consumes the buffer. You can then run `GlyphBuffer::clear` to get the `UnicodeBuffer` back
/// Consumes the buffer. You can then run [`GlyphBuffer::clear`] to get the [`UnicodeBuffer`] back
/// without allocating a new one.
///
/// 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`].
pub fn shape(face: &Face, features: &[Feature], mut buffer: UnicodeBuffer) -> GlyphBuffer {
buffer.0.guess_segment_properties();
let plan = ShapePlan::new(
face,
buffer.0.direction,
buffer.0.script,
buffer.0.language.as_ref(),
features,
);
shape_with_plan(face, &plan, buffer)
}

/// Shapes the buffer content using the provided font and plan.
///
/// 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 {
///
/// It is up to the caller to ensure that the shape plan matches the properties of the provided
/// buffer, otherwise the shaping result will likely be incorrect.
///
/// # Panics
///
/// Will panic when debugging assertions are enabled if the buffer and plan have mismatched
/// properties.
pub fn shape_with_plan(face: &Face, plan: &ShapePlan, buffer: UnicodeBuffer) -> GlyphBuffer {
let mut buffer = buffer.0;
buffer.guess_segment_properties();

if buffer.len > 0 {
let plan = ShapePlan::new(
face,
buffer.direction,
buffer.script,
buffer.language.as_ref(),
features,
);
debug_assert_eq!(buffer.direction, plan.direction);
debug_assert_eq!(
buffer.script.unwrap_or(script::UNKNOWN),
plan.script.unwrap_or(script::UNKNOWN)
);

if buffer.len > 0 {
// Save the original direction, we use it later.
let target_direction = buffer.direction;
shape_internal(&mut ShapeContext {
plan: &plan,
plan,
face,
buffer: &mut buffer,
user_features: features,
target_direction,
});
}
Expand All @@ -44,7 +71,6 @@ struct ShapeContext<'a> {
plan: &'a ShapePlan,
face: &'a Face<'a>,
buffer: &'a mut Buffer,
user_features: &'a [Feature],
// Transient stuff
target_direction: Direction,
}
Expand Down Expand Up @@ -253,7 +279,7 @@ fn setup_masks(ctx: &mut ShapeContext) {
func(ctx.plan, ctx.face, ctx.buffer);
}

for feature in ctx.user_features {
for feature in &ctx.plan.user_features {
if !feature.is_global() {
let (mask, shift) = ctx.plan.ot_map.mask(feature.tag);
ctx.buffer
Expand Down

0 comments on commit 3ea11c8

Please sign in to comment.