pub fn shape(
font: &ShaperFont<'_, '_>,
buffer: &mut Buffer,
options: ShapeOptions<'_>,
) -> Result<(), ShapeError> {
if buffer.content_type == Some(ContentType::Glyphs) {
return Err(ShapeError::AlreadyShaped);
}
if let Some(plan) = options.plan {
shape_with_font(plan, font, buffer, options.features, options.point_size)
} else {
if buffer.direction == Direction::Invalid {
return Err(ShapeError::DirectionUnset);
}
let plan = ShapePlan::from_layout(
font.layout(),
buffer.direction,
buffer.script,
buffer.language.as_ref(),
options.features,
);
shape_with_font(&plan, font, buffer, options.features, options.point_size)
}
}
use crate::aat;
use crate::buffer::GlyphFlags;
use crate::buffer::*;
use crate::ot::gpos;
use crate::ot::layout::*;
use crate::ot::shaper::*;
use crate::ot::*;
use crate::plan::ShapePlan;
use crate::unicode::{CharExt, GeneralCategory};
use crate::{fallback, normalize};
use crate::{Advances, ShapeError, ShapeOptions, ShaperFont};
use crate::{BufferFlags, ContentType, Direction, Feature, Mask, Script};
pub(crate) fn shape_with_font(
plan: &ShapePlan,
font: &ShaperFont<'_, '_>,
buffer: &mut Buffer,
features: &[Feature],
point_size: Option<f32>,
) -> Result<(), ShapeError> {
if buffer.content_type == Some(ContentType::Glyphs) {
return Err(ShapeError::AlreadyShaped);
}
if buffer.direction != plan.direction {
return Err(ShapeError::DirectionMismatch {
plan: plan.direction,
buffer: buffer.direction,
});
}
let plan_script = plan.script.unwrap_or(Script::UNKNOWN);
let buffer_script = buffer.script.unwrap_or(Script::UNKNOWN);
if buffer_script != plan_script {
return Err(ShapeError::ScriptMismatch {
plan: plan_script,
buffer: buffer_script,
});
}
buffer.enter();
if buffer.len > 0 {
let target_direction = buffer.direction;
OtShapeContext {
plan,
font,
buffer,
features,
target_direction,
point_size,
}
.shape_internal();
}
buffer.leave();
buffer.content_type = Some(ContentType::Glyphs);
Ok(())
}
struct OtShapeContext<'a, 'c, 'd> {
plan: &'a ShapePlan,
font: &'a ShaperFont<'c, 'd>,
buffer: &'a mut Buffer,
features: &'a [Feature],
target_direction: Direction,
point_size: Option<f32>,
}
impl OtShapeContext<'_, '_, '_> {
fn glyph_h_advances(&mut self) {
if self.buffer.len == 0 {
return;
}
let batched_advances = Advances::new(self.buffer);
self.font.glyph_h_advances(batched_advances);
}
fn shape_internal(&mut self) {
self.buffer.allocate_unicode_vars();
if self
.set_unicode_props(self.plan.ot_map.get_global_mask())
.is_none()
{
self.buffer.index_failed();
}
if self.insert_dotted_circle().is_none() {
self.buffer.index_failed();
}
form_clusters(self.buffer);
ensure_native_direction(self.buffer);
if let Some(func) = self.plan.shaper.preprocess_text {
if buffer_message!(self.buffer, self.font, "start preprocess-text") {
if func(self.plan, self.font, self.buffer).is_none() {
self.buffer.index_failed();
}
message!(self, "end preprocess-text");
}
}
self.substitute_pre();
self.position();
self.substitute_post();
propagate_flags(self.buffer);
self.buffer.deallocate_unicode_vars();
self.buffer.direction = self.target_direction;
}
fn substitute_pre(&mut self) {
self.substitute_default();
self.buffer.allocate_gsubgpos_vars();
self.substitute_plan();
if self.plan.apply_morx && self.plan.apply_gpos {
aat::layout::remove_deleted_glyphs(self.buffer);
}
}
fn substitute_post(&mut self) {
if self.plan.apply_morx && !self.plan.apply_gpos {
aat::layout::remove_deleted_glyphs(self.buffer);
}
deal_with_variation_selectors(self.buffer);
hide_default_ignorables(self.buffer, self.font);
if let Some(func) = self.plan.shaper.postprocess_glyphs {
if buffer_message!(self.buffer, self.font, "start postprocess-glyphs") {
func(self.plan, self.font, self.buffer);
message!(self, "end postprocess-glyphs");
}
}
}
fn substitute_default(&mut self) {
self.rotate_chars();
self.buffer
.allocate_var(GlyphInfo::NORMALIZER_GLYPH_INDEX_VAR);
if normalize::normalize(self.plan, self.buffer, self.font).is_none() {
self.buffer.index_failed();
}
self.setup_masks();
if self.plan.fallback_mark_positioning {
fallback::recategorize_marks(self.buffer);
}
map_glyphs_fast(self.buffer);
self.buffer.content_type = Some(ContentType::Glyphs);
self.buffer
.deallocate_var(GlyphInfo::NORMALIZER_GLYPH_INDEX_VAR);
}
fn substitute_plan(&mut self) {
if self.plan.apply_morx {
substitute_start(self.font.layout().ot, self.buffer);
if self.plan.fallback_glyph_classes {
synthesize_glyph_classes(self.buffer);
}
aat::layout::substitute(
self.plan,
self.font.layout(),
self.buffer,
self.features,
#[cfg(feature = "tracing")]
self.font,
);
if self.plan.apply_gpos {
self.buffer.update_digest();
}
} else {
substitute_start_with_digest(self.font.layout().ot, self.buffer);
if self.plan.fallback_glyph_classes {
synthesize_glyph_classes(self.buffer);
}
gsub::substitute(self.plan, self.font, self.buffer);
}
}
fn position(&mut self) {
self.buffer.clear_positions();
self.position_default();
self.position_plan();
if self.buffer.direction.is_backward() {
self.buffer.reverse();
}
self.buffer.deallocate_gsubgpos_vars();
}
fn position_default(&mut self) {
if self.buffer.direction.is_horizontal() {
self.glyph_h_advances();
} else {
let (infos, positions) = self.buffer.infos_and_positions_mut();
for (info, pos) in infos.iter().zip(positions) {
let glyph = info.as_glyph();
pos.y_advance = self.font.glyph_v_advance(glyph);
let (x, y) = self.font.glyph_v_origin(glyph);
pos.x_offset = pos.x_offset.saturating_sub(x);
pos.y_offset = pos.y_offset.saturating_sub(y);
}
}
if self.buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_SPACE_FALLBACK != 0 {
fallback::fallback_spaces(self.font, self.buffer);
}
}
fn position_plan(&mut self) {
let adjust_offsets_when_zeroing =
self.plan.adjust_mark_positioning_when_zeroing && self.buffer.direction.is_forward();
gpos::position_start(self.buffer);
if self.plan.zero_marks && self.plan.shaper.zero_width_marks == ZeroWidthMarks::ByGdefEarly
{
zero_mark_widths_by_gdef(self.buffer, adjust_offsets_when_zeroing);
}
self.position_by_plan();
if self.plan.zero_marks && self.plan.shaper.zero_width_marks == ZeroWidthMarks::ByGdefLate {
zero_mark_widths_by_gdef(self.buffer, adjust_offsets_when_zeroing);
}
gpos::position_finish_advances(self.buffer);
zero_width_default_ignorables(self.buffer);
gpos::position_finish_offsets(self.buffer);
if self.plan.fallback_mark_positioning {
fallback::position_marks(
self.plan,
self.font,
self.buffer,
adjust_offsets_when_zeroing,
);
}
}
fn position_by_plan(&mut self) {
let plan = self.plan;
let layout = self.font.layout();
let buffer = &mut *self.buffer;
if plan.apply_gpos {
gpos::position(plan, self.font, buffer);
} else if plan.apply_kerx {
aat::layout::position(
plan,
layout,
self.font.scale,
buffer,
#[cfg(feature = "tracing")]
self.font,
);
}
if plan.apply_kern {
aat::kern::apply(
plan,
layout,
self.font.scale,
buffer,
#[cfg(feature = "tracing")]
self.font,
);
} else if plan.apply_fallback_kern {
fallback::fallback_kern(plan, buffer);
}
if plan.apply_trak {
aat::layout::track(plan, layout, self.font.scale, self.point_size, buffer);
}
}
fn setup_masks(&mut self) {
if self.setup_masks_fraction().is_none() {
self.buffer.index_failed();
}
if let Some(func) = self.plan.shaper.setup_masks {
func(self.plan, self.font, self.buffer);
}
for feature in self.features {
if !feature.is_global() {
let (mask, shift) = self.plan.ot_map.get_mask(feature.tag);
self.buffer
.set_masks(feature.value << shift, mask, feature.start, feature.end);
}
}
}
fn setup_masks_fraction(&mut self) -> Option<()> {
let buffer = &mut *self.buffer;
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_FRACTION_SLASH == 0
|| !self.plan.has_frac
{
return Some(());
}
let (pre_mask, post_mask) = if buffer.direction.is_forward() {
(
self.plan.numr_mask | self.plan.frac_mask,
self.plan.frac_mask | self.plan.dnom_mask,
)
} else {
(
self.plan.frac_mask | self.plan.dnom_mask,
self.plan.numr_mask | self.plan.frac_mask,
)
};
let len = buffer.len;
let mut i = 0;
while i < len {
if buffer.info.get(i)?.glyph_id == 0x2044 {
let mut start = i;
while start > 0
&& buffer.info.get(start - 1)?.general_category()
== GeneralCategory::DECIMAL_NUMBER
{
start -= 1;
}
let mut end = i + 1;
while end < len
&& buffer.info.get(end)?.general_category() == GeneralCategory::DECIMAL_NUMBER
{
end += 1;
}
if start == i || end == i + 1 {
if start == i {
buffer.unsafe_to_concat(Some(start), Some(start + 1));
}
if end == i + 1 {
buffer.unsafe_to_concat(Some(end - 1), Some(end));
}
i += 1;
continue;
}
buffer.unsafe_to_break(Some(start), Some(end));
for info in buffer.info.get_mut(start..i)? {
info.mask |= pre_mask;
}
buffer.info.get_mut(i)?.mask |= self.plan.frac_mask;
for info in buffer.info.get_mut(i + 1..end)? {
info.mask |= post_mask;
}
i = end;
} else {
i += 1;
}
}
Some(())
}
fn set_unicode_props(&mut self, global_mask: Mask) -> Option<()> {
let buffer = &mut *self.buffer;
let len = buffer.len;
let mut i = 0;
while i < len {
let info = buffer.info.get_mut(i)?;
info.mask = global_mask;
info.init_unicode_props(&mut buffer.scratch_flags);
if info.glyph_id < 0x80 {
i += 1;
continue;
}
let gen_cat = info.general_category();
if gen_cat.flag_unsafe()
& (GeneralCategory::LOWERCASE_LETTER.flag()
| GeneralCategory::UPPERCASE_LETTER.flag()
| GeneralCategory::TITLECASE_LETTER.flag()
| GeneralCategory::OTHER_LETTER.flag()
| GeneralCategory::SPACE_SEPARATOR.flag())
!= 0
{
i += 1;
continue;
}
let (prior, later) = buffer.info.split_at_mut_checked(i)?;
let info = later.first_mut()?;
if gen_cat == GeneralCategory::MODIFIER_SYMBOL
&& matches!(info.glyph_id, 0x1F3FB..=0x1F3FF)
{
info.set_continuation(&mut buffer.scratch_flags);
} else if i != 0 && matches!(info.glyph_id, 0x1F1E6..=0x1F1FF) {
let prev = prior.last()?;
if matches!(prev.glyph_id, 0x1F1E6..=0x1F1FF) && !prev.is_continuation() {
info.set_continuation(&mut buffer.scratch_flags);
}
} else if info.is_zwj() {
info.set_continuation(&mut buffer.scratch_flags);
if let Some(next) = buffer.info.get_mut(..len)?.get_mut(i + 1) {
if next.as_codepoint().is_emoji_extended_pictographic() {
next.mask = global_mask;
next.init_unicode_props(&mut buffer.scratch_flags);
next.set_continuation(&mut buffer.scratch_flags);
i += 1;
}
}
} else if matches!(info.glyph_id, 0xFF9E..=0xFF9F | 0xE0020..=0xE007F) {
info.set_continuation(&mut buffer.scratch_flags);
} else if info.glyph_id == 0x2044
{
buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_FRACTION_SLASH;
}
i += 1;
}
Some(())
}
fn insert_dotted_circle(&mut self) -> Option<()> {
let should_insert = {
let buffer = &*self.buffer;
!buffer
.flags
.contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE)
&& buffer.flags.contains(BufferFlags::BEGINNING_OF_TEXT)
&& buffer.context_len[0] == 0
&& buffer.info.first().is_some_and(GlyphInfo::is_unicode_mark)
};
if should_insert && self.font.nominal_glyph(0x25CC).is_some() {
let mask = self.buffer.cur(0)?.mask;
let cluster = self.buffer.cur(0)?.cluster;
let buffer = &mut *self.buffer;
let mut info = GlyphInfo {
glyph_id: 0x25CC,
mask,
cluster,
..GlyphInfo::default()
};
info.init_unicode_props(&mut buffer.scratch_flags);
buffer.clear_output();
buffer.output_info(info);
buffer.sync();
}
Some(())
}
fn rotate_chars(&mut self) {
if self.target_direction.is_backward() {
let rtlm_mask = self.plan.rtlm_mask;
for info in self.buffer.info_slice_mut() {
if let Some(c) = info.as_codepoint().mirroring() {
if self.font.nominal_glyph(c).is_some() {
info.glyph_id = c;
continue;
}
}
info.mask |= rtlm_mask;
}
}
if self.target_direction.is_vertical() && !self.plan.has_vert {
for info in self.buffer.info_slice_mut() {
if let Some(c) = info.as_codepoint().vertical() {
if self.font.nominal_glyph(c).is_some() {
info.glyph_id = c;
}
}
}
}
}
}
fn form_clusters(buffer: &mut Buffer) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_CONTINUATIONS != 0 {
foreach_grapheme!(buffer, start, end, {
buffer.merge_grapheme_clusters(start, end);
});
}
}
fn ensure_native_direction(buffer: &mut Buffer) {
let dir = buffer.direction;
let mut hor = buffer
.script
.map_or(Some(Direction::LeftToRight), Direction::from_script)
.unwrap_or_default();
if hor == Direction::RightToLeft && dir == Direction::LeftToRight {
let mut found_number = false;
let mut found_letter = false;
let mut found_ri = false;
for info in &buffer.info {
let gc = info.general_category();
if gc == GeneralCategory::DECIMAL_NUMBER {
found_number = true;
} else if gc.is_letter() {
found_letter = true;
break;
} else if matches!(info.glyph_id, 0x1F1E6..=0x1F1FF) {
found_ri = true;
}
}
if (found_number || found_ri) && !found_letter {
hor = Direction::LeftToRight;
}
}
if (dir.is_horizontal() && dir != hor && hor != Direction::Invalid)
|| (dir.is_vertical() && dir != Direction::TopToBottom)
{
reverse_graphemes(buffer);
buffer.direction = buffer.direction.reverse();
}
}
fn map_glyphs_fast(buffer: &mut Buffer) {
let len = buffer.len;
for info in buffer.info_slice_mut() {
info.glyph_id = info.normalizer_glyph_index();
}
for info in buffer.out_info_mut().get_mut(..len).unwrap_or_default() {
info.glyph_id = info.normalizer_glyph_index();
}
}
fn synthesize_glyph_classes(buffer: &mut Buffer) {
for info in buffer.info_slice_mut() {
let class = if info.general_category() != GeneralCategory::NON_SPACING_MARK
|| info.is_default_ignorable()
{
GlyphPropsFlags::BASE_GLYPH
} else {
GlyphPropsFlags::MARK
};
info.set_glyph_props(class.bits());
}
}
fn zero_width_default_ignorables(buffer: &mut Buffer) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES != 0
&& !buffer
.flags
.contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES)
&& !buffer
.flags
.contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES)
{
let horizontal = buffer.direction.is_horizontal();
let (infos, positions) = buffer.infos_and_positions_mut();
for (info, pos) in infos.iter().zip(positions) {
if info.is_default_ignorable() {
pos.x_advance = 0;
pos.y_advance = 0;
if horizontal {
pos.x_offset = 0;
} else {
pos.y_offset = 0;
}
}
}
}
}
fn deal_with_variation_selectors(buffer: &mut Buffer) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_VARIATION_SELECTOR_FALLBACK == 0 {
return;
}
let Some(nf) = buffer.not_found_variation_selector else {
return;
};
let (infos, positions) = buffer.infos_and_positions_mut();
for (info, pos) in infos.iter_mut().zip(positions) {
if info.is_variation_selector() {
info.glyph_id = nf;
pos.x_advance = 0;
pos.y_advance = 0;
pos.x_offset = 0;
pos.y_offset = 0;
info.set_variation_selector(false);
}
}
}
fn zero_mark_widths_by_gdef(buffer: &mut Buffer, adjust_offsets: bool) {
let (infos, positions) = buffer.infos_and_positions_mut();
for (info, pos) in infos.iter().zip(positions) {
if info.is_mark() {
if adjust_offsets {
pos.x_offset = pos.x_offset.saturating_sub(pos.x_advance);
pos.y_offset = pos.y_offset.saturating_sub(pos.y_advance);
}
pos.x_advance = 0;
pos.y_advance = 0;
}
}
}
fn hide_default_ignorables(buffer: &mut Buffer, font: &ShaperFont) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES != 0
&& !buffer
.flags
.contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES)
{
if !buffer
.flags
.contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES)
{
if let Some(invisible) = buffer
.invisible
.or_else(|| font.nominal_glyph(u32::from(' ')))
{
for info in buffer.info_slice_mut() {
if info.is_default_ignorable() {
info.glyph_id = invisible.to_u32();
}
}
return;
}
}
buffer.delete_glyphs_inplace(GlyphInfo::is_default_ignorable);
}
}
fn propagate_flags(buffer: &mut Buffer) {
let mut and_mask = GlyphFlags::DEFINED_BITS;
if !buffer.flags.contains(BufferFlags::PRODUCE_UNSAFE_TO_CONCAT) {
and_mask &= !GlyphFlags::UNSAFE_TO_CONCAT.0;
}
if !buffer
.flags
.contains(BufferFlags::PRODUCE_SAFE_TO_INSERT_TATWEEL)
{
foreach_cluster!(buffer, start, end, {
if end - start == 1 {
if let Some(info) = buffer.info.get_mut(start) {
info.mask &= and_mask;
}
} else if let Some(infos) = buffer.info.get_mut(start..end) {
let mask = infos.iter().fold(0, |mask, info| mask | info.mask) & and_mask;
for info in infos {
info.mask = mask;
}
}
});
return;
}
foreach_cluster!(buffer, start, end, {
if let Some(infos) = buffer.info.get_mut(start..end) {
let mut mask = infos.iter().fold(0, |mask, info| mask | info.mask);
mask &= GlyphFlags::DEFINED_BITS;
if mask & GlyphFlags::UNSAFE_TO_BREAK.0 != 0 {
mask &= !GlyphFlags::SAFE_TO_INSERT_TATWEEL.0;
}
if mask & GlyphFlags::SAFE_TO_INSERT_TATWEEL.0 != 0 {
mask |= GlyphFlags::UNSAFE_TO_BREAK.0 | GlyphFlags::UNSAFE_TO_CONCAT.0;
}
mask &= and_mask;
for info in infos {
info.mask = mask;
}
}
});
}
#[cfg(test)]
mod native_direction_tests {
use super::*;
#[test]
fn unspecified_script_keeps_emoji_graphemes_in_native_order() {
for script in [None, Some(Script::COMMON), Some(Script::UNKNOWN)] {
let mut buffer = Buffer::new();
buffer.push(0x1F44D, 0);
buffer.push(0x1F3FD, 4);
buffer.push(0x41, 8);
buffer.info[1].set_continuation(&mut buffer.scratch_flags);
buffer.script = script;
buffer.direction = Direction::RightToLeft;
ensure_native_direction(&mut buffer);
assert_eq!(buffer.direction, Direction::LeftToRight);
assert_eq!(buffer.info[0].glyph_id, 0x41);
assert_eq!(buffer.info[1].glyph_id, 0x1F44D);
assert_eq!(buffer.info[2].glyph_id, 0x1F3FD);
}
}
#[test]
fn explicitly_directionless_scripts_keep_requested_direction() {
for script in [
Script::OLD_HUNGARIAN,
Script::OLD_ITALIC,
Script::RUNIC,
Script::TIFINAGH,
] {
let mut buffer = Buffer::new();
buffer.push_str("ab");
buffer.script = Some(script);
buffer.direction = Direction::RightToLeft;
ensure_native_direction(&mut buffer);
assert_eq!(buffer.direction, Direction::RightToLeft);
assert_eq!(buffer.info[0].glyph_id, 0x61);
assert_eq!(buffer.info[1].glyph_id, 0x62);
}
}
}
#[cfg(test)]
mod variation_selector_tests {
use super::*;
const NOT_FOUND_GLYPH: u32 = 64000;
const SELECTORS: [usize; 2] = [1, 3];
const BASES: [usize; 2] = [0, 2];
fn buffer_with_unsupported_selectors(not_found: Option<u32>) -> Buffer {
let mut buffer = Buffer::new();
buffer.push_str("x\u{FE00}y\u{E0100}");
buffer.not_found_variation_selector = not_found;
let mut flags = buffer.scratch_flags;
for info in &mut buffer.info[..buffer.len] {
info.init_unicode_props(&mut flags);
}
buffer.scratch_flags = flags | HB_BUFFER_SCRATCH_FLAG_HAS_VARIATION_SELECTOR_FALLBACK;
for i in SELECTORS {
buffer.info[i].set_variation_selector(true);
if not_found.is_some() {
buffer.info[i].clear_default_ignorable();
}
buffer.pos[i] = GlyphPosition {
x_advance: 500,
y_advance: 500,
x_offset: 50,
y_offset: 50,
..GlyphPosition::default()
};
}
buffer
}
#[test]
fn unsupported_selectors_use_not_found_glyph() {
let mut buffer = buffer_with_unsupported_selectors(Some(NOT_FOUND_GLYPH));
let base_props = BASES.map(|i| buffer.info[i].unicode_props());
deal_with_variation_selectors(&mut buffer);
for i in SELECTORS {
let info = &buffer.info[i];
assert_eq!(info.glyph_id, NOT_FOUND_GLYPH);
assert!(!info.is_variation_selector());
assert!(!info.is_default_ignorable());
assert_eq!(info.general_category(), GeneralCategory::NON_SPACING_MARK);
let pos = &buffer.pos[i];
assert_eq!(
(pos.x_advance, pos.y_advance, pos.x_offset, pos.y_offset),
(0, 0, 0, 0)
);
}
assert_eq!(BASES.map(|i| buffer.info[i].unicode_props()), base_props);
}
#[test]
fn unsupported_selectors_are_kept_without_not_found_glyph() {
let mut buffer = buffer_with_unsupported_selectors(None);
let props: [_; 4] = core::array::from_fn(|i| buffer.info[i].unicode_props());
deal_with_variation_selectors(&mut buffer);
for (i, codepoint) in SELECTORS.into_iter().zip([0xFE00, 0xE0100]) {
assert_eq!(buffer.info[i].glyph_id, codepoint);
assert!(buffer.info[i].is_variation_selector());
assert_eq!(buffer.pos[i].x_advance, 500);
}
assert_eq!(
core::array::from_fn(|i| buffer.info[i].unicode_props()),
props
);
}
}