use super::style::{GlyphStyle, StyleClass};
use crate::{charmap::Charmap, collections::SmallVec, FontRef, GlyphId, MetadataProvider};
use core::ops::Range;
use raw::{
tables::{
gsub::{
ChainedSequenceContext, Gsub, SequenceContext, SingleSubst, SubstitutionLookupList,
SubstitutionSubtables,
},
layout::{Feature, ScriptTags},
varc::CoverageTable,
},
types::Tag,
ReadError, TableProvider,
};
const MAX_NESTING_DEPTH: usize = 64;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum ShaperMode {
Nominal,
#[allow(unused)]
BestEffort,
}
#[derive(Copy, Clone, Default, Debug)]
pub(crate) struct ShapedGlyph {
pub id: GlyphId,
pub y_offset: i32,
}
const SHAPED_CLUSTER_INLINE_SIZE: usize = 16;
pub(crate) type ShapedCluster = SmallVec<ShapedGlyph, SHAPED_CLUSTER_INLINE_SIZE>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum ShaperCoverageKind {
Script,
Default,
}
pub(crate) struct Shaper<'a> {
font: FontRef<'a>,
#[allow(unused)]
mode: ShaperMode,
charmap: Charmap<'a>,
gsub: Option<Gsub<'a>>,
}
impl<'a> Shaper<'a> {
pub fn new(font: &FontRef<'a>, mode: ShaperMode) -> Self {
let charmap = font.charmap();
let gsub = (mode != ShaperMode::Nominal)
.then(|| font.gsub().ok())
.flatten();
Self {
font: font.clone(),
mode,
charmap,
gsub,
}
}
pub fn font(&self) -> &FontRef<'a> {
&self.font
}
pub fn charmap(&self) -> &Charmap<'a> {
&self.charmap
}
pub fn lookup_count(&self) -> u16 {
self.gsub
.as_ref()
.and_then(|gsub| gsub.lookup_list().ok())
.map(|list| list.lookup_count())
.unwrap_or_default()
}
pub fn cluster_shaper(&'a self, style: &StyleClass) -> ClusterShaper<'a> {
if self.mode == ShaperMode::BestEffort {
if let Some(feature_tag) = style.feature {
if let Some((lookup_list, feature)) = self.gsub.as_ref().and_then(|gsub| {
let script_list = gsub.script_list().ok()?;
let selected_script =
script_list.select(&ScriptTags::from_unicode(style.script.tag))?;
let script = script_list.get(selected_script.index).ok()?;
let lang_sys = script.default_lang_sys()?.ok()?;
let feature_list = gsub.feature_list().ok()?;
let feature_ix = lang_sys.feature_index_for_tag(&feature_list, feature_tag)?;
let feature = feature_list.get(feature_ix).ok()?.element;
let lookup_list = gsub.lookup_list().ok()?;
Some((lookup_list, feature))
}) {
return ClusterShaper {
shaper: self,
lookup_list: Some(lookup_list),
kind: ClusterShaperKind::SingleFeature(feature),
};
}
}
}
ClusterShaper {
shaper: self,
lookup_list: None,
kind: ClusterShaperKind::Nominal,
}
}
pub(crate) fn compute_coverage(
&self,
style: &StyleClass,
coverage_kind: ShaperCoverageKind,
glyph_styles: &mut [GlyphStyle],
visited_set: &mut VisitedLookupSet<'_>,
) -> bool {
let Some(gsub) = self.gsub.as_ref() else {
return false;
};
let (Ok(script_list), Ok(feature_list), Ok(lookup_list)) =
(gsub.script_list(), gsub.feature_list(), gsub.lookup_list())
else {
return false;
};
let mut script_tags: [Option<Tag>; 3] = [None; 3];
for (a, b) in script_tags
.iter_mut()
.zip(ScriptTags::from_unicode(style.script.tag).iter())
{
*a = Some(*b);
}
const DEFAULT_SCRIPT: Tag = Tag::new(b"Dflt");
if coverage_kind == ShaperCoverageKind::Default {
if script_tags[0].is_none() {
script_tags[0] = Some(DEFAULT_SCRIPT);
} else if script_tags[1].is_none() {
script_tags[1] = Some(DEFAULT_SCRIPT);
} else if script_tags[1] != Some(DEFAULT_SCRIPT) {
script_tags[2] = Some(DEFAULT_SCRIPT);
}
} else {
const NON_STANDARD_TAGS: &[Option<Tag>] = &[
Some(Tag::new(b"Khms")),
Some(Tag::new(b"Latb")),
Some(Tag::new(b"Latp")),
];
if NON_STANDARD_TAGS.contains(&script_tags[0]) {
return false;
}
}
let mut gsub_handler = GsubHandler::new(
&self.charmap,
&lookup_list,
style,
glyph_styles,
visited_set,
);
for script in script_tags.iter().filter_map(|tag| {
tag.and_then(|tag| script_list.index_for_tag(tag))
.and_then(|ix| script_list.script_records().get(ix as usize))
.and_then(|rec| rec.script(script_list.offset_data()).ok())
}) {
for langsys in script
.lang_sys_records()
.iter()
.filter_map(|rec| rec.lang_sys(script.offset_data()).ok())
.chain(script.default_lang_sys().transpose().ok().flatten())
{
for feature_ix in langsys.feature_indices() {
let Some(feature) = feature_list
.feature_records()
.get(feature_ix.get() as usize)
.and_then(|rec| {
if style.feature == Some(rec.feature_tag()) || style.feature.is_none() {
rec.feature(feature_list.offset_data()).ok()
} else {
None
}
})
else {
continue;
};
for index in feature.lookup_list_indices().iter() {
let _ = gsub_handler.process_lookup(index.get());
}
}
}
}
if let Some(range) = gsub_handler.finish() {
let mut result = false;
for glyph_style in &mut glyph_styles[range] {
result |= glyph_style.maybe_assign_gsub_output_style(style);
}
result
} else {
false
}
}
}
pub(crate) struct ClusterShaper<'a> {
shaper: &'a Shaper<'a>,
lookup_list: Option<SubstitutionLookupList<'a>>,
kind: ClusterShaperKind<'a>,
}
impl ClusterShaper<'_> {
pub(crate) fn shape(&mut self, input: &str, output: &mut ShapedCluster) {
output.clear();
for ch in input.chars() {
output.push(ShapedGlyph {
id: self.shaper.charmap.map(ch).unwrap_or_default(),
y_offset: 0,
});
}
match self.kind.clone() {
ClusterShaperKind::Nominal => {
if self.shaper.mode == ShaperMode::Nominal && output.len() > 1 {
output.clear();
}
}
ClusterShaperKind::SingleFeature(feature) => {
let mut did_subst = false;
for lookup_ix in feature.lookup_list_indices() {
let mut glyph_ix = 0;
while glyph_ix < output.len() {
did_subst |= self.apply_lookup(lookup_ix.get(), output, glyph_ix, 0);
glyph_ix += 1;
}
}
if !did_subst {
output.clear();
}
}
}
}
fn apply_lookup(
&self,
lookup_index: u16,
cluster: &mut ShapedCluster,
glyph_ix: usize,
nesting_depth: usize,
) -> bool {
if nesting_depth > MAX_NESTING_DEPTH {
return false;
}
let Some(glyph) = cluster.get_mut(glyph_ix) else {
return false;
};
let Some(subtables) = self
.lookup_list
.as_ref()
.and_then(|list| list.lookups().get(lookup_index as usize).ok())
.and_then(|lookup| lookup.subtables().ok())
else {
return false;
};
match subtables {
SubstitutionSubtables::Single(tables) => {
for table in tables.iter().filter_map(|table| table.ok()) {
match table {
SingleSubst::Format1(table) => {
let Some(_) = table.coverage().ok().and_then(|cov| cov.get(glyph.id))
else {
continue;
};
let delta = table.delta_glyph_id() as i32;
glyph.id = GlyphId::from((glyph.id.to_u32() as i32 + delta) as u16);
return true;
}
SingleSubst::Format2(table) => {
let Some(cov_ix) =
table.coverage().ok().and_then(|cov| cov.get(glyph.id))
else {
continue;
};
let Some(subst) = table.substitute_glyph_ids().get(cov_ix as usize)
else {
continue;
};
glyph.id = subst.get().into();
return true;
}
}
}
}
SubstitutionSubtables::Multiple(_tables) => {}
SubstitutionSubtables::Ligature(_tables) => {}
SubstitutionSubtables::Alternate(_tables) => {}
SubstitutionSubtables::Contextual(_tables) => {}
SubstitutionSubtables::ChainContextual(_tables) => {}
SubstitutionSubtables::Reverse(_tables) => {}
}
false
}
}
#[derive(Clone)]
enum ClusterShaperKind<'a> {
Nominal,
SingleFeature(Feature<'a>),
}
struct GsubHandler<'a, 'b> {
charmap: &'a Charmap<'a>,
lookup_list: &'a SubstitutionLookupList<'a>,
style: &'a StyleClass,
glyph_styles: &'a mut [GlyphStyle],
need_blue_substs: bool,
min_gid: usize,
max_gid: usize,
lookup_depth: usize,
visited_set: &'a mut VisitedLookupSet<'b>,
}
impl<'a, 'b> GsubHandler<'a, 'b> {
fn new(
charmap: &'a Charmap<'a>,
lookup_list: &'a SubstitutionLookupList,
style: &'a StyleClass,
glyph_styles: &'a mut [GlyphStyle],
visited_set: &'a mut VisitedLookupSet<'b>,
) -> Self {
let min_gid = glyph_styles.len();
let need_blue_substs = style.feature.is_some();
Self {
charmap,
lookup_list,
style,
glyph_styles,
need_blue_substs,
min_gid,
max_gid: 0,
lookup_depth: 0,
visited_set,
}
}
fn process_lookup(&mut self, lookup_index: u16) -> Result<(), ProcessLookupError> {
if self.lookup_depth == MAX_NESTING_DEPTH {
return Err(ProcessLookupError::ExceededMaxDepth);
}
if !self.visited_set.insert(lookup_index) {
return Ok(());
}
self.lookup_depth += 1;
let result = self.process_lookup_inner(lookup_index);
self.lookup_depth -= 1;
result
}
#[inline(always)]
fn process_lookup_inner(&mut self, lookup_index: u16) -> Result<(), ProcessLookupError> {
let Ok(subtables) = self
.lookup_list
.lookups()
.get(lookup_index as usize)
.and_then(|lookup| lookup.subtables())
else {
return Ok(());
};
match subtables {
SubstitutionSubtables::Single(tables) => {
for table in tables.iter().filter_map(|table| table.ok()) {
match table {
SingleSubst::Format1(table) => {
let Ok(coverage) = table.coverage() else {
continue;
};
let delta = table.delta_glyph_id() as i32;
for gid in coverage.iter() {
self.capture_glyph((gid.to_u32() as i32 + delta) as u16 as u32);
}
if self.need_blue_substs && self.lookup_depth == 1 {
self.check_blue_coverage(Ok(coverage));
}
}
SingleSubst::Format2(table) => {
for gid in table.substitute_glyph_ids() {
self.capture_glyph(gid.get().to_u32());
}
if self.need_blue_substs && self.lookup_depth == 1 {
self.check_blue_coverage(table.coverage());
}
}
}
}
}
SubstitutionSubtables::Multiple(tables) => {
for table in tables.iter().filter_map(|table| table.ok()) {
for seq in table.sequences().iter().filter_map(|seq| seq.ok()) {
for gid in seq.substitute_glyph_ids() {
self.capture_glyph(gid.get().to_u32());
}
}
if self.need_blue_substs && self.lookup_depth == 1 {
self.check_blue_coverage(table.coverage());
}
}
}
SubstitutionSubtables::Ligature(tables) => {
for table in tables.iter().filter_map(|table| table.ok()) {
for set in table.ligature_sets().iter().filter_map(|set| set.ok()) {
for lig in set.ligatures().iter().filter_map(|lig| lig.ok()) {
self.capture_glyph(lig.ligature_glyph().to_u32());
}
}
}
}
SubstitutionSubtables::Alternate(tables) => {
for table in tables.iter().filter_map(|table| table.ok()) {
for set in table.alternate_sets().iter().filter_map(|set| set.ok()) {
for gid in set.alternate_glyph_ids() {
self.capture_glyph(gid.get().to_u32());
}
}
}
}
SubstitutionSubtables::Contextual(tables) => {
for table in tables.iter().filter_map(|table| table.ok()) {
match table {
SequenceContext::Format1(table) => {
for set in table
.seq_rule_sets()
.iter()
.filter_map(|set| set.transpose().ok().flatten())
{
for rule in set.seq_rules().iter().filter_map(|rule| rule.ok()) {
for rec in rule.seq_lookup_records() {
self.process_lookup(rec.lookup_list_index())?;
}
}
}
}
SequenceContext::Format2(table) => {
for set in table
.class_seq_rule_sets()
.iter()
.filter_map(|set| set.transpose().ok().flatten())
{
for rule in
set.class_seq_rules().iter().filter_map(|rule| rule.ok())
{
for rec in rule.seq_lookup_records() {
self.process_lookup(rec.lookup_list_index())?;
}
}
}
}
SequenceContext::Format3(table) => {
for rec in table.seq_lookup_records() {
self.process_lookup(rec.lookup_list_index())?;
}
}
}
}
}
SubstitutionSubtables::ChainContextual(tables) => {
for table in tables.iter().filter_map(|table| table.ok()) {
match table {
ChainedSequenceContext::Format1(table) => {
for set in table
.chained_seq_rule_sets()
.iter()
.filter_map(|set| set.transpose().ok().flatten())
{
for rule in
set.chained_seq_rules().iter().filter_map(|rule| rule.ok())
{
for rec in rule.seq_lookup_records() {
self.process_lookup(rec.lookup_list_index())?;
}
}
}
}
ChainedSequenceContext::Format2(table) => {
for set in table
.chained_class_seq_rule_sets()
.iter()
.filter_map(|set| set.transpose().ok().flatten())
{
for rule in set
.chained_class_seq_rules()
.iter()
.filter_map(|rule| rule.ok())
{
for rec in rule.seq_lookup_records() {
self.process_lookup(rec.lookup_list_index())?;
}
}
}
}
ChainedSequenceContext::Format3(table) => {
for rec in table.seq_lookup_records() {
self.process_lookup(rec.lookup_list_index())?;
}
}
}
}
}
SubstitutionSubtables::Reverse(tables) => {
for table in tables.iter().filter_map(|table| table.ok()) {
for gid in table.substitute_glyph_ids() {
self.capture_glyph(gid.get().to_u32());
}
}
}
}
Ok(())
}
fn finish(self) -> Option<Range<usize>> {
self.visited_set.clear();
if self.min_gid > self.max_gid {
return None;
}
let range = self.min_gid..self.max_gid + 1;
if self.need_blue_substs {
for glyph in &mut self.glyph_styles[range] {
glyph.clear_from_gsub();
}
None
} else {
Some(range)
}
}
fn check_blue_coverage(&mut self, coverage: Result<CoverageTable<'a>, ReadError>) {
let Ok(coverage) = coverage else {
return;
};
for (blue_str, _) in self.style.script.blues {
if blue_str
.chars()
.filter_map(|ch| self.charmap.map(ch))
.filter_map(|gid| coverage.get(gid))
.next()
.is_some()
{
self.need_blue_substs = false;
return;
}
}
}
fn capture_glyph(&mut self, gid: u32) {
let gid = gid as usize;
if let Some(style) = self.glyph_styles.get_mut(gid) {
style.set_from_gsub_output();
self.min_gid = gid.min(self.min_gid);
self.max_gid = gid.max(self.max_gid);
}
}
}
pub(crate) struct VisitedLookupSet<'a>(&'a mut [u8]);
impl<'a> VisitedLookupSet<'a> {
pub fn new(storage: &'a mut [u8]) -> Self {
Self(storage)
}
fn insert(&mut self, lookup_index: u16) -> bool {
let byte_ix = lookup_index as usize / 8;
let bit_mask = 1 << (lookup_index % 8) as u8;
if let Some(byte) = self.0.get_mut(byte_ix) {
if *byte & bit_mask == 0 {
*byte |= bit_mask;
true
} else {
false
}
} else {
false
}
}
fn clear(&mut self) {
self.0.fill(0);
}
}
#[derive(PartialEq, Debug)]
enum ProcessLookupError {
ExceededMaxDepth,
}
#[cfg(test)]
mod tests {
use super::{super::style, *};
use fontcull_font_test_data::bebuffer::BeBuffer;
use raw::{FontData, FontRead};
#[test]
fn small_caps_subst() {
let font = FontRef::new(fontcull_font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
let shaper = Shaper::new(&font, ShaperMode::BestEffort);
let style = &style::STYLE_CLASSES[style::StyleClass::LATN_C2SC];
let mut cluster_shaper = shaper.cluster_shaper(style);
let mut cluster = ShapedCluster::new();
cluster_shaper.shape("H", &mut cluster);
assert_eq!(cluster.len(), 1);
assert_eq!(cluster[0].id, GlyphId::new(8));
}
#[test]
fn small_caps_nominal() {
let font = FontRef::new(fontcull_font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
let shaper = Shaper::new(&font, ShaperMode::Nominal);
let style = &style::STYLE_CLASSES[style::StyleClass::LATN_C2SC];
let mut cluster_shaper = shaper.cluster_shaper(style);
let mut cluster = ShapedCluster::new();
cluster_shaper.shape("H", &mut cluster);
assert_eq!(cluster.len(), 1);
assert_eq!(cluster[0].id, GlyphId::new(1));
}
#[test]
fn exceed_max_depth() {
let font = FontRef::new(fontcull_font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
let shaper = Shaper::new(&font, ShaperMode::BestEffort);
let style = &style::STYLE_CLASSES[style::StyleClass::LATN];
let mut bad_lookup_builder = BadLookupBuilder::default();
for i in 0..MAX_NESTING_DEPTH {
bad_lookup_builder.lookups.push(i as u16 + 1);
}
let lookup_list_buf = bad_lookup_builder.build();
let lookup_list = SubstitutionLookupList::read(FontData::new(&lookup_list_buf)).unwrap();
let mut set_buf = [0u8; 8192];
let mut visited_set = VisitedLookupSet(&mut set_buf);
let mut gsub_handler = GsubHandler::new(
&shaper.charmap,
&lookup_list,
style,
&mut [],
&mut visited_set,
);
assert_eq!(
gsub_handler.process_lookup(0),
Err(ProcessLookupError::ExceededMaxDepth)
);
}
#[test]
fn dont_cycle_forever() {
let font = FontRef::new(fontcull_font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
let shaper = Shaper::new(&font, ShaperMode::BestEffort);
let style = &style::STYLE_CLASSES[style::StyleClass::LATN];
let mut bad_lookup_builder = BadLookupBuilder::default();
bad_lookup_builder.lookups.push(1);
bad_lookup_builder.lookups.push(0);
let lookup_list_buf = bad_lookup_builder.build();
let lookup_list = SubstitutionLookupList::read(FontData::new(&lookup_list_buf)).unwrap();
let mut set_buf = [0u8; 8192];
let mut visited_set = VisitedLookupSet(&mut set_buf);
let mut gsub_handler = GsubHandler::new(
&shaper.charmap,
&lookup_list,
style,
&mut [],
&mut visited_set,
);
gsub_handler.process_lookup(0).unwrap();
}
#[test]
fn visited_set() {
let count = 2341u16;
let n_bytes = (count as usize).div_ceil(8);
let mut set_buf = vec![0u8; n_bytes];
let mut set = VisitedLookupSet::new(&mut set_buf);
for i in 0..count {
assert!(set.insert(i));
assert!(!set.insert(i));
}
for byte in &set_buf[0..set_buf.len() - 1] {
assert_eq!(*byte, 0xFF);
}
assert_eq!(*set_buf.last().unwrap(), 0b00011111);
}
#[derive(Default)]
struct BadLookupBuilder {
lookups: Vec<u16>,
}
impl BadLookupBuilder {
fn build(&self) -> Vec<u8> {
const CONTEXT3_FULL_SIZE: usize = 18;
let mut buf = BeBuffer::default();
buf = buf.push(self.lookups.len() as u16);
let base_offset = 2 + 2 * self.lookups.len();
for i in 0..self.lookups.len() {
buf = buf.push((base_offset + i * CONTEXT3_FULL_SIZE) as u16);
}
for nested_ix in &self.lookups {
buf = buf.push(5u16);
buf = buf.push(0u16);
buf = buf.push(1u16);
buf = buf.push(8u16);
buf = buf.push(3u16);
buf = buf.push(0u16);
buf = buf.push(1u16);
buf = buf.push(0u16).push(*nested_ix);
}
buf.to_vec()
}
}
}