use log::debug;
use crate::error::{ComplexScriptError, ParseError, ShapingError};
use crate::gsub::{self, FeatureMask, GlyphData, GlyphOrigin, RawGlyph, RawGlyphFlags};
use crate::layout::{FeatureTableSubstitution, GDEFTable, LayoutCache, LayoutTable, GSUB};
use crate::scripts::syllable::*;
use crate::tinyvec::tiny_vec;
use crate::{tag, DOTTED_CIRCLE};
const MAX_CLUSTER_LEN: usize = 31;
const MAX_REPEAT: usize = MAX_CLUSTER_LEN / 3;
#[derive(Copy, Clone, Debug, PartialEq)]
enum BasicFeature {
Locl,
Ccmp,
Rphf,
Pref,
Blwf,
Pstf,
}
impl BasicFeature {
const ALL: &'static [BasicFeature] = &[
BasicFeature::Locl,
BasicFeature::Ccmp,
BasicFeature::Rphf,
BasicFeature::Pref,
BasicFeature::Blwf,
BasicFeature::Pstf,
];
fn mask(self) -> FeatureMask {
match self {
BasicFeature::Locl => FeatureMask::LOCL,
BasicFeature::Ccmp => FeatureMask::CCMP,
BasicFeature::Rphf => FeatureMask::RPHF,
BasicFeature::Pref => FeatureMask::PREF,
BasicFeature::Blwf => FeatureMask::BLWF,
BasicFeature::Pstf => FeatureMask::PSTF,
}
}
fn is_global(self) -> bool {
match self {
BasicFeature::Locl => true,
BasicFeature::Ccmp => true,
BasicFeature::Rphf => true,
BasicFeature::Pref => true,
BasicFeature::Blwf => true,
BasicFeature::Pstf => true,
}
}
}
#[allow(unused)]
#[derive(Copy, Clone, Debug, PartialEq)]
enum ShapingClass {
Bindu,
Visarga,
PureKiller,
Consonant,
VowelIndependent,
VowelDependent,
ConsonantMedial,
ConsonantPlaceholder,
Number,
Symbol,
ToneMarker,
InvisibleStacker,
ConsonantWithStacker,
Placeholder,
Joiner,
NonJoiner,
DottedCircle,
}
#[derive(Copy, Clone, Debug)]
enum MarkPlacementSubclass {
TopPosition,
RightPosition,
BottomPosition,
LeftPosition,
TopLeftAndBottomPosition,
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
enum Pos {
PrebaseMatra,
PrebaseConsonant,
SyllableBase,
AfterMain,
BeforeSubjoined,
BelowbaseConsonant,
AfterSubjoined,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Syllable {
Valid,
Broken,
}
fn shaping_class(ch: char) -> Option<ShapingClass> {
let (shaping, _) = myanmar_character(ch);
shaping
}
fn consonant(ch: char) -> bool {
match shaping_class(ch) {
Some(ShapingClass::Consonant | ShapingClass::ConsonantPlaceholder) => true,
_ => false,
}
}
fn vowel(ch: char) -> bool {
matches!(shaping_class(ch), Some(ShapingClass::VowelIndependent))
}
fn digit(ch: char) -> bool {
shaping_class(ch) == Some(ShapingClass::Number)
}
fn generic_base(ch: char) -> bool {
matches!(
ch,
'\u{002D}'
| '\u{00A0}'
| '\u{00D7}'
| '\u{2012}'
| '\u{2013}'
| '\u{2014}'
| '\u{2015}'
| '\u{2022}'
| '\u{25CC}'
| '\u{25FB}'
| '\u{25FC}'
| '\u{25FD}'
| '\u{25FE}'
)
}
fn standalone(ch: char) -> bool {
let class = shaping_class(ch);
matches!(ch,
'\u{1000}'..='\u{109f}' | '\u{AA60}' ..= '\u{AA7F}' | '\u{A9E0}' ..= '\u{A9FF}'
) && (class.is_none() || class == Some(ShapingClass::Placeholder))
}
fn variation_selector(ch: char) -> bool {
ch == '\u{FE00}'
}
fn halant(ch: char) -> bool {
shaping_class(ch) == Some(ShapingClass::InvisibleStacker)
}
fn zwj(ch: char) -> bool {
matches!(shaping_class(ch), Some(ShapingClass::Joiner))
}
fn zwnj(ch: char) -> bool {
matches!(shaping_class(ch), Some(ShapingClass::NonJoiner))
}
fn joiner(ch: char) -> bool {
zwj(ch) || zwnj(ch)
}
fn ra(ch: char) -> bool {
match ch {
'\u{101B}' => true, '\u{1004}' => true, '\u{105A}' => true, _ => false,
}
}
fn asat(ch: char) -> bool {
ch == '\u{103A}' }
fn consonant_with_stacker(ch: char) -> bool {
matches!(shaping_class(ch), Some(ShapingClass::ConsonantWithStacker))
}
fn matra_pre(ch: char) -> bool {
matches!(
myanmar_character(ch),
(
Some(ShapingClass::VowelDependent),
Some(MarkPlacementSubclass::LeftPosition)
)
)
}
fn matra_post(ch: char) -> bool {
matches!(
myanmar_character(ch),
(
Some(ShapingClass::VowelDependent),
Some(MarkPlacementSubclass::RightPosition)
)
)
}
fn a(ch: char) -> bool {
ch == '\u{1036}' || ch == '\u{1032}'
}
fn dot_below(ch: char) -> bool {
ch == '\u{1037}'
}
fn matra_above(ch: char) -> bool {
!a(ch)
&& matches!(
myanmar_character(ch),
(
Some(ShapingClass::VowelDependent),
Some(MarkPlacementSubclass::TopPosition)
)
)
}
fn matra_below(ch: char) -> bool {
matches!(
myanmar_character(ch),
(
Some(ShapingClass::VowelDependent),
Some(MarkPlacementSubclass::BottomPosition)
)
)
}
fn medial_ha(ch: char) -> bool {
ch == '\u{103E}'
}
fn medial_la(ch: char) -> bool {
ch == '\u{1060}'
}
fn medial_ra(ch: char) -> bool {
ch == '\u{103C}'
}
fn medial_wa(ch: char) -> bool {
ch == '\u{103D}' || ch == '\u{1082}'
}
fn medial_ya(ch: char) -> bool {
ch == '\u{103B}' || ch == '\u{105E}' || ch == '\u{105F}'
}
fn pt(ch: char) -> bool {
match ch {
'\u{1063}' | '\u{1064}' => true,
'\u{1069}'..='\u{106D}' => true,
'\u{AA7B}' => true,
_ => false,
}
}
fn punc(ch: char) -> bool {
matches!(ch, '\u{104a}'..='\u{104f}')
}
fn g(ch: char) -> bool {
generic_base(ch) || digit(ch) || punc(ch)
}
fn initial_group(ch: char) -> bool {
consonant(ch) || vowel(ch) || g(ch)
}
fn match_kinzi<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_seq(match_one(ra), match_seq(match_one(asat), match_one(halant)))(cs)
}
fn match_z<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_one(joiner)(cs)
}
fn match_vmain<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_repeat_upto(
MAX_REPEAT,
match_one(matra_pre),
match_repeat_upto(
4,
match_one(matra_above),
match_repeat_upto(
4,
match_one(matra_below),
match_repeat_upto(
4,
match_one(a),
match_optional(match_seq(
match_one(dot_below),
match_optional(match_one(asat)),
)),
),
),
),
)(cs)
}
fn match_vpost<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_seq(
match_one(matra_post),
match_repeat_upto(
4,
match_optional(match_one(medial_ha)),
match_repeat_upto(
4,
match_one(asat),
match_repeat_upto(
4,
match_one(matra_above),
match_repeat_upto(
4,
match_one(a),
match_optional(match_seq(
match_one(dot_below),
match_optional(match_one(asat)),
)),
),
),
),
),
)(cs)
}
fn match_pwo<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_seq(
match_one(pt),
match_repeat_upto(
MAX_REPEAT,
match_one(a),
match_seq(
match_optional(match_one(dot_below)),
match_optional(match_one(asat)),
),
),
)(cs)
}
fn visarga(ch: char) -> bool {
shaping_class(ch) == Some(ShapingClass::Visarga)
}
fn sm(ch: char) -> bool {
match ch {
'\u{1087}'..='\u{108D}' => true,
'\u{108F}' => true,
'\u{109A}'..='\u{109C}' => true,
_ if visarga(ch) => true,
_ => false,
}
}
fn match_t_complex<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_repeat_upto(
MAX_REPEAT,
match_one(asat),
match_seq(
match_medial_group,
match_seq(
match_vmain,
match_repeat_upto(
MAX_REPEAT,
match_vpost,
match_repeat_upto(
MAX_REPEAT,
match_pwo,
match_repeat_upto(MAX_REPEAT, match_one(sm), match_optional(match_z)),
),
),
),
),
)(cs)
}
fn match_syllable_tail<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_either(match_one(halant), match_t_complex)(cs)
}
fn match_halant_group<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_seq(
match_seq(
match_one(halant),
match_either(match_one(consonant), match_one(vowel)),
),
match_optional(match_one(variation_selector)),
)(cs)
}
fn match_medial_group<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_optional_seq(
match_one(medial_ya),
match_optional_seq(
match_one(asat),
match_optional_seq(match_one(medial_ra), match_optional(match_medial_group2)),
),
)(cs)
}
fn match_medial_group2<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_seq(
match_either(
match_medial_group2a,
match_either(match_medial_group2b, match_one(medial_la)),
),
match_optional(match_one(asat)),
)(cs)
}
fn match_medial_group2a<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_seq(
match_one(medial_wa),
match_optional_seq(match_one(medial_ha), match_optional(match_one(medial_la))),
)(cs)
}
fn match_medial_group2b<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_seq(match_one(medial_ha), match_optional(match_one(medial_la)))(cs)
}
fn match_initial_group<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_one(initial_group)(cs)
}
fn match_consonant_syllable<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_optional_seq(
match_either(match_kinzi, match_one(consonant_with_stacker)),
match_seq(
match_initial_group,
match_optional_seq(
match_one(variation_selector),
match_repeat_upto(MAX_REPEAT, match_halant_group, match_syllable_tail),
),
),
)(cs)
}
fn match_standalone<T: SyllableChar>(cs: &[T]) -> Option<usize> {
match_one(standalone)(cs)
}
fn match_syllable<T: SyllableChar>(cs: &[T]) -> Option<(usize, Syllable)> {
match match_consonant_syllable(cs) {
Some(len) => Some((len, Syllable::Valid)),
None => match_standalone(cs).map(|len| (len, Syllable::Broken)),
}
}
#[derive(Clone, Debug)]
struct MyanmarData {
pos: Option<Pos>,
mask: FeatureMask,
}
impl GlyphData for MyanmarData {
fn merge(data1: MyanmarData, data2: MyanmarData) -> MyanmarData {
match (data1.pos, data2.pos) {
(Some(Pos::SyllableBase), _) => data1,
(_, Some(Pos::SyllableBase)) => data2,
(Some(Pos::PrebaseConsonant), _) => data1,
(_, Some(Pos::PrebaseConsonant)) => data2,
(_, None) => data1,
(None, _) => data2,
_ => data1, }
}
}
type RawGlyphMyanmar = RawGlyph<MyanmarData>;
impl RawGlyphMyanmar {
fn is(&self, pred: impl FnOnce(char) -> bool) -> bool {
match self.glyph_origin {
GlyphOrigin::Char(c) => pred(c),
GlyphOrigin::Direct => false,
}
}
fn set_pos(&mut self, pos: Option<Pos>) {
self.extra_data.pos = pos
}
fn pos(&self) -> Option<Pos> {
self.extra_data.pos
}
fn has_mask(&self, mask: FeatureMask) -> bool {
self.extra_data.mask.contains(mask)
}
}
struct MyanmarShapingData<'tables> {
gsub_cache: &'tables LayoutCache<GSUB>,
gsub_table: &'tables LayoutTable<GSUB>,
gdef_table: Option<&'tables GDEFTable>,
script_tag: u32,
lang_tag: Option<u32>,
feature_variations: Option<&'tables FeatureTableSubstitution<'tables>>,
}
impl MyanmarShapingData<'_> {
fn get_lookups_cache_index(&self, mask: FeatureMask) -> Result<usize, ParseError> {
gsub::get_lookups_cache_index(
self.gsub_cache,
self.script_tag,
self.lang_tag,
self.feature_variations,
mask,
)
}
fn apply_lookup(
&self,
lookup_index: usize,
feature_tag: u32,
glyphs: &mut Vec<RawGlyphMyanmar>,
max_glyphs: usize,
pred: impl Fn(&RawGlyphMyanmar) -> bool,
) -> Result<(), ParseError> {
gsub::gsub_apply_lookup(
self.gsub_cache,
self.gsub_table,
self.gdef_table,
lookup_index,
feature_tag,
None,
glyphs,
max_glyphs,
0,
glyphs.len(),
pred,
)?;
Ok(())
}
}
pub fn gsub_apply_myanmar<'a>(
dotted_circle_index: u16,
gsub_cache: &'a LayoutCache<GSUB>,
gsub_table: &'a LayoutTable<GSUB>,
gdef_table: Option<&'a GDEFTable>,
lang_tag: Option<u32>,
feature_variations: Option<&'a FeatureTableSubstitution<'a>>,
glyphs: &mut Vec<RawGlyph<()>>,
) -> Result<(), ShapingError> {
if glyphs.is_empty() {
return Err(ComplexScriptError::EmptyBuffer.into());
}
let script_tag = tag::MYM2;
let mut syllables = to_myanmar_syllables(glyphs);
let shaping_data = MyanmarShapingData {
gsub_cache,
gsub_table,
gdef_table,
script_tag,
lang_tag,
feature_variations,
};
for i in 0..syllables.len() {
let (syllable, syllable_type) = &mut syllables[i];
if let Err(err) =
shape_syllable(dotted_circle_index, &shaping_data, syllable, *syllable_type)
{
debug!("gsub apply myanmar: {}", err);
}
}
*glyphs = syllables
.into_iter()
.flat_map(|(s, _)| s.into_iter())
.map(from_raw_glyph_myanmar)
.collect();
Ok(())
}
fn shape_syllable(
dotted_circle_index: u16,
shaping_data: &MyanmarShapingData<'_>,
syllable: &mut Vec<RawGlyphMyanmar>,
syllable_type: Syllable,
) -> Result<(), ShapingError> {
let max_glyphs = syllable.len().saturating_mul(gsub::MAX_GLYPHS_FACTOR);
if syllable_type == Syllable::Broken {
insert_dotted_circle(dotted_circle_index, syllable)?;
}
match syllable_type {
Syllable::Valid | Syllable::Broken => {
initial_reorder_consonant_syllable(shaping_data, syllable)?;
apply_basic_features(shaping_data, syllable, max_glyphs)?;
apply_presentation_features(shaping_data, syllable, max_glyphs)?;
}
}
Ok(())
}
fn insert_dotted_circle(
dotted_circle_index: u16,
glyphs: &mut Vec<RawGlyphMyanmar>,
) -> Result<(), ComplexScriptError> {
if dotted_circle_index == 0 {
return Err(ComplexScriptError::MissingDottedCircle);
}
let dotted_circle = RawGlyphMyanmar {
unicodes: tiny_vec![[char; 1] => DOTTED_CIRCLE],
glyph_index: dotted_circle_index,
liga_component_pos: 0,
glyph_origin: GlyphOrigin::Char(DOTTED_CIRCLE),
flags: RawGlyphFlags::empty(),
variation: None,
extra_data: MyanmarData {
pos: None,
mask: FeatureMask::empty(),
},
};
glyphs.insert(0, dotted_circle);
Ok(())
}
fn to_myanmar_syllables(mut glyphs: &[RawGlyph<()>]) -> Vec<(Vec<RawGlyphMyanmar>, Syllable)> {
let mut syllables: Vec<(Vec<RawGlyphMyanmar>, Syllable)> = Vec::new();
while !glyphs.is_empty() {
let len = match match_syllable(glyphs) {
Some((len, syllable_type)) => {
assert_ne!(len, 0);
let syllable = glyphs[..len].iter().map(to_raw_glyph_myanmar).collect();
syllables.push((syllable, syllable_type));
len
}
None => {
let invalid_glyph = to_raw_glyph_myanmar(&glyphs[0]);
match syllables.last_mut() {
Some((invalid_syllable, Syllable::Broken)) => {
invalid_syllable.push(invalid_glyph)
}
_ => syllables.push((vec![invalid_glyph], Syllable::Broken)),
}
1
}
};
glyphs = &glyphs[len..];
}
syllables
}
fn initial_reorder_consonant_syllable(
shaping_data: &MyanmarShapingData<'_>,
glyphs: &mut [RawGlyphMyanmar],
) -> Result<(), ShapingError> {
let _base_index = tag_syllable(shaping_data, glyphs)?;
if glyphs.iter().any(|g| g.pos().is_none()) {
return Err(ComplexScriptError::MissingTags.into());
} else {
glyphs.sort_by_key(|g| g.pos());
}
Ok(())
}
fn tag_syllable(
_shaping_data: &MyanmarShapingData<'_>,
glyphs: &mut [RawGlyphMyanmar],
) -> Result<Option<usize>, ShapingError> {
let mut base_index = None;
let mut i = 0;
let start;
if let Some(len) = match_kinzi(glyphs) {
glyphs[..len]
.iter_mut()
.for_each(|glyph| glyph.set_pos(Some(Pos::AfterMain)));
i += len;
start = i;
} else {
start = 0;
}
while i < glyphs.len() {
let glyph = &glyphs[i];
if glyph.is(initial_group) {
let glyph = &mut glyphs[i];
glyph.set_pos(Some(Pos::SyllableBase));
base_index = Some(i);
break;
}
i += 1;
}
let base = base_index.unwrap_or(start);
glyphs[start..base]
.iter_mut()
.for_each(|glyph| glyph.set_pos(Some(Pos::PrebaseConsonant)));
let mut pos = Pos::AfterMain;
for i in (base..glyphs.len()).skip(1) {
let (before_i, rest) = glyphs.split_at_mut(i);
let glyph = &mut rest[0];
if glyph.is(medial_ra) {
glyph.set_pos(Some(Pos::PrebaseConsonant))
}
else if glyph.is(a)
&& prev_glyph_skip(before_i, a)
.is_some_and(|prev| prev.pos() == Some(Pos::BelowbaseConsonant))
{
glyph.set_pos(Some(Pos::BeforeSubjoined))
}
else if glyph.is(variation_selector) {
if let Some(prev) = i.checked_sub(1) {
glyph.set_pos(before_i[prev].pos())
}
}
else if pos == Pos::AfterMain && glyph.pos() == Some(Pos::BelowbaseConsonant) {
pos = Pos::BelowbaseConsonant
} else if pos == Pos::BelowbaseConsonant && !glyph.is(a) {
pos = Pos::AfterSubjoined;
if glyph.pos() != Some(Pos::BelowbaseConsonant) {
glyph.set_pos(Some(pos))
}
} else if glyph.pos().is_none() {
glyph.set_pos(Some(pos))
}
}
Ok(base_index)
}
fn prev_glyph_skip(
glyphs: &[RawGlyphMyanmar],
pred: impl Fn(char) -> bool,
) -> Option<&RawGlyphMyanmar> {
glyphs.iter().rev().find(|g| !g.is(&pred))
}
fn apply_basic_features(
shaping_data: &MyanmarShapingData<'_>,
glyphs: &mut Vec<RawGlyphMyanmar>,
max_glyphs: usize,
) -> Result<(), ParseError> {
for feature in BasicFeature::ALL {
let index = shaping_data.get_lookups_cache_index(feature.mask())?;
let lookups = &shaping_data.gsub_cache.cached_lookups.lock().unwrap()[index];
for &(lookup_index, feature_tag) in lookups {
shaping_data.apply_lookup(lookup_index, feature_tag, glyphs, max_glyphs, |g| {
feature.is_global() || g.has_mask(feature.mask())
})?;
}
}
Ok(())
}
fn apply_presentation_features(
shaping_data: &MyanmarShapingData<'_>,
glyphs: &mut Vec<RawGlyphMyanmar>,
max_glyphs: usize,
) -> Result<(), ParseError> {
let features = FeatureMask::PRES
| FeatureMask::ABVS
| FeatureMask::BLWS
| FeatureMask::PSTS
| FeatureMask::LIGA
| FeatureMask::RLIG;
let index = shaping_data.get_lookups_cache_index(features)?;
let lookups = &shaping_data.gsub_cache.cached_lookups.lock().unwrap()[index];
for &(lookup_index, feature_tag) in lookups {
shaping_data.apply_lookup(lookup_index, feature_tag, glyphs, max_glyphs, |_g| true)?;
}
Ok(())
}
fn to_raw_glyph_myanmar(glyph: &RawGlyph<()>) -> RawGlyphMyanmar {
let pos = match myanmar_character(glyph.char()) {
(Some(ShapingClass::VowelDependent), Some(placement)) => match placement {
MarkPlacementSubclass::BottomPosition => Some(Pos::BelowbaseConsonant),
MarkPlacementSubclass::LeftPosition => Some(Pos::PrebaseMatra),
MarkPlacementSubclass::TopLeftAndBottomPosition
| MarkPlacementSubclass::RightPosition
| MarkPlacementSubclass::TopPosition => None,
},
_ => None,
};
RawGlyphMyanmar {
unicodes: glyph.unicodes.clone(),
glyph_index: glyph.glyph_index,
liga_component_pos: glyph.liga_component_pos,
glyph_origin: glyph.glyph_origin,
flags: glyph.flags,
variation: glyph.variation,
extra_data: MyanmarData {
pos,
mask: FeatureMask::empty(),
},
}
}
fn from_raw_glyph_myanmar(glyph: RawGlyphMyanmar) -> RawGlyph<()> {
RawGlyph {
unicodes: glyph.unicodes,
glyph_index: glyph.glyph_index,
liga_component_pos: glyph.liga_component_pos,
glyph_origin: glyph.glyph_origin,
flags: glyph.flags,
variation: glyph.variation,
extra_data: (),
}
}
fn myanmar_character(ch: char) -> (Option<ShapingClass>, Option<MarkPlacementSubclass>) {
use MarkPlacementSubclass::*;
use ShapingClass::*;
match ch as u32 {
0x1000 => (Some(Consonant), None), 0x1001 => (Some(Consonant), None), 0x1002 => (Some(Consonant), None), 0x1003 => (Some(Consonant), None), 0x1004 => (Some(Consonant), None), 0x1005 => (Some(Consonant), None), 0x1006 => (Some(Consonant), None), 0x1007 => (Some(Consonant), None), 0x1008 => (Some(Consonant), None), 0x1009 => (Some(Consonant), None), 0x100A => (Some(Consonant), None), 0x100B => (Some(Consonant), None), 0x100C => (Some(Consonant), None), 0x100D => (Some(Consonant), None), 0x100E => (Some(Consonant), None), 0x100F => (Some(Consonant), None), 0x1010 => (Some(Consonant), None), 0x1011 => (Some(Consonant), None), 0x1012 => (Some(Consonant), None), 0x1013 => (Some(Consonant), None), 0x1014 => (Some(Consonant), None), 0x1015 => (Some(Consonant), None), 0x1016 => (Some(Consonant), None), 0x1017 => (Some(Consonant), None), 0x1018 => (Some(Consonant), None), 0x1019 => (Some(Consonant), None), 0x101A => (Some(Consonant), None), 0x101B => (Some(Consonant), None), 0x101C => (Some(Consonant), None), 0x101D => (Some(Consonant), None), 0x101E => (Some(Consonant), None), 0x101F => (Some(Consonant), None), 0x1020 => (Some(Consonant), None), 0x1021 => (Some(VowelIndependent), None), 0x1022 => (Some(VowelIndependent), None), 0x1023 => (Some(VowelIndependent), None), 0x1024 => (Some(VowelIndependent), None), 0x1025 => (Some(VowelIndependent), None), 0x1026 => (Some(VowelIndependent), None), 0x1027 => (Some(VowelIndependent), None), 0x1028 => (Some(VowelIndependent), None), 0x1029 => (Some(VowelIndependent), None), 0x102A => (Some(VowelIndependent), None), 0x102B => (Some(VowelDependent), Some(RightPosition)), 0x102C => (Some(VowelDependent), Some(RightPosition)), 0x102D => (Some(VowelDependent), Some(TopPosition)), 0x102E => (Some(VowelDependent), Some(TopPosition)), 0x102F => (Some(VowelDependent), Some(BottomPosition)), 0x1030 => (Some(VowelDependent), Some(BottomPosition)), 0x1031 => (Some(VowelDependent), Some(LeftPosition)), 0x1032 => (Some(VowelDependent), Some(TopPosition)), 0x1033 => (Some(VowelDependent), Some(TopPosition)), 0x1034 => (Some(VowelDependent), Some(TopPosition)), 0x1035 => (Some(VowelDependent), Some(TopPosition)), 0x1036 => (Some(Bindu), Some(TopPosition)), 0x1037 => (Some(ToneMarker), Some(BottomPosition)), 0x1038 => (Some(Visarga), Some(RightPosition)), 0x1039 => (Some(InvisibleStacker), None), 0x103A => (Some(PureKiller), Some(TopPosition)), 0x103B => (Some(ConsonantMedial), Some(RightPosition)), 0x103C => (Some(ConsonantMedial), Some(TopLeftAndBottomPosition)), 0x103D => (Some(ConsonantMedial), Some(BottomPosition)), 0x103E => (Some(ConsonantMedial), Some(BottomPosition)), 0x103F => (Some(Consonant), None), 0x1040 => (Some(Number), None), 0x1041 => (Some(Number), None), 0x1042 => (Some(Number), None), 0x1043 => (Some(Number), None), 0x1044 => (Some(Number), None), 0x1045 => (Some(Number), None), 0x1046 => (Some(Number), None), 0x1047 => (Some(Number), None), 0x1048 => (Some(Number), None), 0x1049 => (Some(Number), None), 0x104A => (None, None), 0x104B => (None, None), 0x104C => (None, None), 0x104D => (None, None), 0x104E => (Some(ConsonantPlaceholder), None), 0x104F => (None, None), 0x1050 => (Some(Consonant), None), 0x1051 => (Some(Consonant), None), 0x1052 => (Some(VowelIndependent), None), 0x1053 => (Some(VowelIndependent), None), 0x1054 => (Some(VowelIndependent), None), 0x1055 => (Some(VowelIndependent), None), 0x1056 => (Some(VowelDependent), Some(RightPosition)), 0x1057 => (Some(VowelDependent), Some(RightPosition)), 0x1058 => (Some(VowelDependent), Some(BottomPosition)), 0x1059 => (Some(VowelDependent), Some(BottomPosition)), 0x105A => (Some(Consonant), None), 0x105B => (Some(Consonant), None), 0x105C => (Some(Consonant), None), 0x105D => (Some(Consonant), None), 0x105E => (Some(ConsonantMedial), Some(BottomPosition)), 0x105F => (Some(ConsonantMedial), Some(BottomPosition)), 0x1060 => (Some(ConsonantMedial), Some(BottomPosition)), 0x1061 => (Some(Consonant), None), 0x1062 => (Some(VowelDependent), Some(RightPosition)), 0x1063 => (Some(ToneMarker), Some(RightPosition)), 0x1064 => (Some(ToneMarker), Some(RightPosition)), 0x1065 => (Some(Consonant), None), 0x1066 => (Some(Consonant), None), 0x1067 => (Some(VowelDependent), Some(RightPosition)), 0x1068 => (Some(VowelDependent), Some(RightPosition)), 0x1069 => (Some(ToneMarker), Some(RightPosition)), 0x106A => (Some(ToneMarker), Some(RightPosition)), 0x106B => (Some(ToneMarker), Some(RightPosition)), 0x106C => (Some(ToneMarker), Some(RightPosition)), 0x106D => (Some(ToneMarker), Some(RightPosition)), 0x106E => (Some(Consonant), None), 0x106F => (Some(Consonant), None), 0x1070 => (Some(Consonant), None), 0x1071 => (Some(VowelDependent), Some(TopPosition)), 0x1072 => (Some(VowelDependent), Some(TopPosition)), 0x1073 => (Some(VowelDependent), Some(TopPosition)), 0x1074 => (Some(VowelDependent), Some(TopPosition)), 0x1075 => (Some(Consonant), None), 0x1076 => (Some(Consonant), None), 0x1077 => (Some(Consonant), None), 0x1078 => (Some(Consonant), None), 0x1079 => (Some(Consonant), None), 0x107A => (Some(Consonant), None), 0x107B => (Some(Consonant), None), 0x107C => (Some(Consonant), None), 0x107D => (Some(Consonant), None), 0x107E => (Some(Consonant), None), 0x107F => (Some(Consonant), None), 0x1080 => (Some(Consonant), None), 0x1081 => (Some(Consonant), None), 0x1082 => (Some(ConsonantMedial), Some(BottomPosition)), 0x1083 => (Some(VowelDependent), Some(RightPosition)), 0x1084 => (Some(VowelDependent), Some(LeftPosition)), 0x1085 => (Some(VowelDependent), Some(TopPosition)), 0x1086 => (Some(VowelDependent), Some(TopPosition)), 0x1087 => (Some(ToneMarker), Some(RightPosition)), 0x1088 => (Some(ToneMarker), Some(RightPosition)), 0x1089 => (Some(ToneMarker), Some(RightPosition)), 0x108A => (Some(ToneMarker), Some(RightPosition)), 0x108B => (Some(ToneMarker), Some(RightPosition)), 0x108C => (Some(ToneMarker), Some(RightPosition)), 0x108D => (Some(ToneMarker), Some(BottomPosition)), 0x108E => (Some(Consonant), None), 0x108F => (Some(ToneMarker), Some(RightPosition)), 0x1090 => (Some(Number), None), 0x1091 => (Some(Number), None), 0x1092 => (Some(Number), None), 0x1093 => (Some(Number), None), 0x1094 => (Some(Number), None), 0x1095 => (Some(Number), None), 0x1096 => (Some(Number), None), 0x1097 => (Some(Number), None), 0x1098 => (Some(Number), None), 0x1099 => (Some(Number), None), 0x109A => (Some(ToneMarker), Some(RightPosition)), 0x109B => (Some(ToneMarker), Some(RightPosition)), 0x109C => (Some(VowelDependent), Some(RightPosition)), 0x109D => (Some(VowelDependent), Some(TopPosition)), 0x109E => (Some(Symbol), None), 0x109F => (Some(Symbol), None),
0xAA60 => (Some(Consonant), None), 0xAA61 => (Some(Consonant), None), 0xAA62 => (Some(Consonant), None), 0xAA63 => (Some(Consonant), None), 0xAA64 => (Some(Consonant), None), 0xAA65 => (Some(Consonant), None), 0xAA66 => (Some(Consonant), None), 0xAA67 => (Some(Consonant), None), 0xAA68 => (Some(Consonant), None), 0xAA69 => (Some(Consonant), None), 0xAA6A => (Some(Consonant), None), 0xAA6B => (Some(Consonant), None), 0xAA6C => (Some(Consonant), None), 0xAA6D => (Some(Consonant), None), 0xAA6E => (Some(Consonant), None), 0xAA6F => (Some(Consonant), None), 0xAA70 => (None, None), 0xAA71 => (Some(Consonant), None), 0xAA72 => (Some(Consonant), None), 0xAA73 => (Some(Consonant), None), 0xAA74 => (Some(ConsonantPlaceholder), None), 0xAA75 => (Some(ConsonantPlaceholder), None), 0xAA76 => (Some(ConsonantPlaceholder), None), 0xAA77 => (Some(Symbol), None), 0xAA78 => (Some(Symbol), None), 0xAA79 => (Some(Symbol), None), 0xAA7A => (Some(Consonant), None), 0xAA7B => (Some(ToneMarker), Some(RightPosition)), 0xAA7C => (Some(ToneMarker), Some(TopPosition)), 0xAA7D => (Some(ToneMarker), Some(RightPosition)), 0xAA7E => (Some(Consonant), None), 0xAA7F => (Some(Consonant), None),
0xA9E0 => (Some(Consonant), None), 0xA9E1 => (Some(Consonant), None), 0xA9E2 => (Some(Consonant), None), 0xA9E3 => (Some(Consonant), None), 0xA9E4 => (Some(Consonant), None), 0xA9E5 => (Some(VowelDependent), Some(TopPosition)), 0xA9E6 => (None, None), 0xA9E7 => (Some(Consonant), None), 0xA9E8 => (Some(Consonant), None), 0xA9E9 => (Some(Consonant), None), 0xA9EA => (Some(Consonant), None), 0xA9EB => (Some(Consonant), None), 0xA9EC => (Some(Consonant), None), 0xA9ED => (Some(Consonant), None), 0xA9EE => (Some(Consonant), None), 0xA9EF => (Some(Consonant), None), 0xA9F0 => (Some(Number), None), 0xA9F1 => (Some(Number), None), 0xA9F2 => (Some(Number), None), 0xA9F3 => (Some(Number), None), 0xA9F4 => (Some(Number), None), 0xA9F5 => (Some(Number), None), 0xA9F6 => (Some(Number), None), 0xA9F7 => (Some(Number), None), 0xA9F8 => (Some(Number), None), 0xA9F9 => (Some(Number), None), 0xA9FA => (Some(Consonant), None), 0xA9FB => (Some(Consonant), None), 0xA9FC => (Some(Consonant), None), 0xA9FD => (Some(Consonant), None), 0xA9FE => (Some(Consonant), None),
0x116D0 => (Some(Number), None), 0x116D1 => (Some(Number), None), 0x116D2 => (Some(Number), None), 0x116D3 => (Some(Number), None), 0x116D4 => (Some(Number), None), 0x116D5 => (Some(Number), None), 0x116D6 => (Some(Number), None), 0x116D7 => (Some(Number), None), 0x116D8 => (Some(Number), None), 0x116D9 => (Some(Number), None), 0x116DA => (Some(Number), None), 0x116DB => (Some(Number), None), 0x116DC => (Some(Number), None), 0x116DD => (Some(Number), None), 0x116DE => (Some(Number), None), 0x116DF => (Some(Number), None), 0x116E0 => (Some(Number), None), 0x116E1 => (Some(Number), None), 0x116E2 => (Some(Number), None), 0x116E3 => (Some(Number), None),
0x00A0 => (Some(Placeholder), None), 0x200C => (Some(NonJoiner), None), 0x200D => (Some(Joiner), None), 0x2010 => (Some(Placeholder), None), 0x2011 => (Some(Placeholder), None), 0x2012 => (Some(Placeholder), None), 0x2013 => (Some(Placeholder), None), 0x2014 => (Some(Placeholder), None), 0x25CC => (Some(DottedCircle), None),
_ => (None, None),
}
}
#[cfg(test)]
mod tests {
use crate::{
binary::read::ReadScope,
font::read_cmap_subtable,
layout::new_layout_cache,
tables::{
cmap::{Cmap, CmapSubtable},
OffsetTable, OpenTypeData, OpenTypeFont,
},
tests::read_fixture_font,
};
use super::*;
macro_rules! assert_eq_hex {
($left:expr, $right:expr $(,)?) => ({
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
panic!(r#"assertion `left == right` failed
left: {:#x?}
right: {:#x?}"#, &*left_val, &*right_val)
}
}
}
});
($left:expr, $right:expr, $($arg:tt)+) => ({
match (&($left), &($right)) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
panic!(r#"assertion `left == right` failed: {}
left: {:#x?}
right: {:#x?}"#, format_args!($($arg)+), &*left_val, &*right_val)
}
}
}
});
}
fn map_glyph(cmap_subtable: &CmapSubtable<'_>, ch: char) -> Result<RawGlyph<()>, ParseError> {
let glyph_index = cmap_subtable.map_glyph(ch as u32)?.unwrap_or(0);
let glyph = RawGlyph {
unicodes: tiny_vec![[char; 1] => ch],
glyph_index,
liga_component_pos: 0,
glyph_origin: GlyphOrigin::Char(ch),
flags: RawGlyphFlags::empty(),
variation: None,
extra_data: (),
};
Ok(glyph)
}
fn apply_gsub<'a>(
scope: &ReadScope<'a>,
ttf: OffsetTable<'a>,
lang_tag: Option<u32>,
syllable: &str,
) -> Result<Vec<RawGlyph<()>>, ShapingError> {
let cmap = if let Some(cmap_scope) = ttf.read_table(&scope, tag::CMAP)? {
cmap_scope.read::<Cmap<'_>>()?
} else {
panic!("no cmap table");
};
let (_, cmap_subtable) = if let Some(cmap_subtable) = read_cmap_subtable(&cmap)? {
cmap_subtable
} else {
panic!("no suitable cmap subtable");
};
let mut glyphs = syllable
.chars()
.map(|ch| map_glyph(&cmap_subtable, ch))
.collect::<Result<Vec<_>, _>>()
.unwrap();
let Some(gsub_record) = ttf.find_table_record(tag::GSUB) else {
panic!("no GSUB table record");
};
let gsub_table = gsub_record
.read_table(&scope)?
.read::<LayoutTable<GSUB>>()?;
let gdef_table = match ttf.find_table_record(tag::GDEF) {
Some(gdef_record) => Some(gdef_record.read_table(&scope)?.read::<GDEFTable>()?),
None => None,
};
let gsub_cache = new_layout_cache(gsub_table);
let gsub_table = &gsub_cache.layout_table;
let dotted_circle_index = cmap_subtable.map_glyph(DOTTED_CIRCLE as u32)?.unwrap_or(0);
let feature_variations = None;
gsub_apply_myanmar(
dotted_circle_index,
&gsub_cache,
&gsub_table,
gdef_table.as_ref(),
lang_tag,
feature_variations,
&mut glyphs,
)?;
Ok(glyphs)
}
mod syllables {
use super::*;
impl SyllableChar for char {
fn char(&self) -> char {
*self
}
}
fn syllable_clusters(input: &str) -> Vec<(Vec<char>, Option<Syllable>)> {
let input = input.chars().collect::<Vec<_>>();
let mut input = input.as_slice();
let mut syllables: Vec<(Vec<_>, Option<Syllable>)> = Vec::new();
while !input.is_empty() {
let len = match match_syllable(input) {
Some((len, syllable_type)) => {
assert_ne!(len, 0);
let syllable = input[..len].iter().copied().collect();
syllables.push((syllable, Some(syllable_type)));
len
}
None => {
let invalid_glyph = input[0];
match syllables.last_mut() {
Some((invalid_syllable, None)) => invalid_syllable.push(invalid_glyph),
_ => syllables.push((vec![invalid_glyph], None)),
}
1
}
};
input = &input[len..];
}
syllables
}
#[test]
fn one() {
let input = "အကြွေးပေး";
let expected = ["အ", "ကြွေး", "ပေး"];
let syllables = syllable_clusters(input)
.into_iter()
.filter_map(|(chars, syllable_ty)| {
syllable_ty.map(|_| chars.into_iter().collect::<String>())
})
.collect::<Vec<_>>();
assert_eq!(syllables, expected);
}
#[test]
fn two() {
let input = "ကံမဆရာတော်ဘုရားကြီး";
let expected = ["ကံ", "မ", "ဆ", "ရာ", "တော်", "ဘု", "ရား", "ကြီး"];
let syllables = syllable_clusters(input)
.into_iter()
.filter_map(|(chars, syllable_ty)| {
syllable_ty.map(|_| chars.into_iter().collect::<String>())
})
.collect::<Vec<_>>();
assert_eq!(syllables, expected);
}
#[test]
fn three() {
let input = "ပို၍စောစီးစွာပေးပါက";
let expected = ["ပို", "၍", "စော", "စီး", "စွာ", "ပေး", "ပါ", "က"];
let syllables = syllable_clusters(input)
.into_iter()
.filter_map(|(chars, syllable_ty)| {
syllable_ty.map(|_| chars.into_iter().collect::<String>())
})
.collect::<Vec<_>>();
assert_eq!(syllables, expected);
}
#[test]
fn four() {
let input = "ကင်းေ၀းသော";
let expected = ["က", "င်း", "ေ", "၀း", "သော"];
let syllables = syllable_clusters(input)
.into_iter()
.map(|(chars, _syllable_ty)| chars.into_iter().collect::<String>())
.collect::<Vec<_>>();
assert_eq!(syllables, expected);
}
#[test]
fn complex_cluster() {
let input = "င်္က္ကျြွှေို့်ာှီ့ၤဲံ့းႍ";
let expected = [input];
let syllables = syllable_clusters(input)
.into_iter()
.map(|(chars, _syllable_ty)| chars.into_iter().collect::<String>())
.collect::<Vec<_>>();
assert_eq!(syllables, expected);
}
}
mod dotted_circle {
use super::*;
#[test]
fn em_dash() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let x = apply_gsub(&fontfile.scope, ttf, None, "—့").unwrap();
assert_eq!(x.len(), 2);
}
#[test]
fn visagara() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let x = apply_gsub(&fontfile.scope, ttf, None, "းႍ").unwrap();
assert_eq!(x.len(), 3);
assert_eq!(x[0].char(), '◌');
}
#[test]
fn nbsp() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let x = apply_gsub(
&fontfile.scope,
ttf,
None,
"\u{00a0}\u{102d}\u{102f}\u{1037}",
)
.unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(gids, [98, 386, 394, 410]);
}
#[test]
fn punc() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let x = apply_gsub(&fontfile.scope, ttf, None, "\u{104f}").unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(gids, [485]);
}
}
mod reorder {
use super::*;
fn do_reorder<'a>(
scope: &ReadScope<'a>,
ttf: OffsetTable<'a>,
lang_tag: Option<u32>,
syllable: &[char],
) -> Result<Vec<RawGlyphMyanmar>, ShapingError> {
let cmap = if let Some(cmap_scope) = ttf.read_table(&scope, tag::CMAP)? {
cmap_scope.read::<Cmap<'_>>()?
} else {
panic!("no cmap table");
};
let (_, cmap_subtable) = if let Some(cmap_subtable) = read_cmap_subtable(&cmap)? {
cmap_subtable
} else {
panic!("no suitable cmap subtable");
};
let glyphs = syllable
.iter()
.copied()
.map(|ch| map_glyph(&cmap_subtable, ch))
.collect::<Result<Vec<_>, _>>()
.unwrap();
let Some(gsub_record) = ttf.find_table_record(tag::GSUB) else {
panic!("no GSUB table record");
};
let gsub_table = gsub_record
.read_table(&scope)?
.read::<LayoutTable<GSUB>>()?;
let gdef_table = match ttf.find_table_record(tag::GDEF) {
Some(gdef_record) => Some(gdef_record.read_table(&scope)?.read::<GDEFTable>()?),
None => None,
};
let gsub_cache = new_layout_cache(gsub_table);
let gsub_table = &gsub_cache.layout_table;
let feature_variations = None;
let script_tag = tag::MYM2;
let syllables = to_myanmar_syllables(&glyphs);
let shaping_data = MyanmarShapingData {
gsub_cache: &gsub_cache,
gsub_table,
gdef_table: gdef_table.as_ref(),
script_tag,
lang_tag,
feature_variations,
};
assert_eq!(syllables.len(), 1);
let mut syllable = syllables.into_iter().next().unwrap().0;
initial_reorder_consonant_syllable(&shaping_data, &mut syllable)?;
Ok(syllable)
}
#[test]
fn pathological() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let chars = [
'\u{1004}', '\u{103A}', '\u{1039}', '\u{1000}', '\u{1039}', '\u{1000}', '\u{103B}', '\u{103C}', '\u{103D}', '\u{1031}', '\u{1031}', '\u{102D}', '\u{102F}', '\u{1036}', '\u{102C}', '\u{1036}', ];
let reordered =
do_reorder(&fontfile.scope, ttf, None, &chars).expect("failed to reorder syllable");
let chars = reordered
.into_iter()
.map(|glyph| glyph.char() as u32)
.collect::<Vec<_>>();
assert_eq_hex!(
&chars,
&[
0x1031, 0x1031, 0x103C, 0x1000, 0x1004, 0x103A, 0x1039, 0x1039, 0x1000, 0x103B,
0x103D, 0x102D, 0x1036, 0x102F, 0x102C, 0x1036,
]
)
}
#[test]
fn sign_aa() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let input = [
'\u{1075}', '\u{102c}', '\u{1038}', ];
let reordered =
do_reorder(&fontfile.scope, ttf, None, &input).expect("failed to reorder syllable");
let output = reordered
.into_iter()
.map(|glyph| glyph.char() as u32)
.collect::<Vec<_>>();
assert_eq_hex!(
&output,
&input.iter().copied().map(|c| c as u32).collect::<Vec<_>>()
);
}
#[test]
fn shan_final_y() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let input = [
'\u{1078}', '\u{1062}', '\u{1086}', '\u{1038}', ];
let reordered =
do_reorder(&fontfile.scope, ttf, None, &input).expect("failed to reorder syllable");
let output = reordered
.into_iter()
.map(|glyph| glyph.char() as u32)
.collect::<Vec<_>>();
assert_eq_hex!(
&output,
&input.iter().copied().map(|c| c as u32).collect::<Vec<_>>()
);
}
#[test]
fn shan_digit_zero() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let input = [
'\u{1090}', '\u{102f}', '\u{1036}', ];
let reordered =
do_reorder(&fontfile.scope, ttf, None, &input).expect("failed to reorder syllable");
let output = reordered
.into_iter()
.map(|glyph| glyph.char() as u32)
.collect::<Vec<_>>();
let expected = [
'\u{1090}' as u32, '\u{1036}' as u32, '\u{102f}' as u32, ];
assert_eq_hex!(&output, &expected);
}
#[test]
fn digit5() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let input = [
'\u{1045}', '\u{103c}', ];
let reordered =
do_reorder(&fontfile.scope, ttf, None, &input).expect("failed to reorder syllable");
let output = reordered
.into_iter()
.map(|glyph| glyph.char() as u32)
.collect::<Vec<_>>();
let expected = [
'\u{103c}' as u32, '\u{1045}' as u32, ];
assert_eq_hex!(&output, &expected);
}
#[test]
fn dual_below_base_consonant() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let syllable = [
'\u{1001}', '\u{103C}', '\u{102F}', '\u{102F}', '\u{1036}', ];
let reordered = do_reorder(&fontfile.scope, ttf, None, &syllable)
.expect("failed to reorder syllable");
let output = reordered
.into_iter()
.map(|glyph| glyph.char() as u32)
.collect::<Vec<_>>();
let expected = [
'\u{103c}' as u32, '\u{1001}' as u32, '\u{1036}' as u32, '\u{102f}' as u32, '\u{102f}' as u32, ];
assert_eq_hex!(&output, &expected);
}
#[test]
fn punctuation() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let syllable = [
'\u{104b}', '\u{1031}', '\u{1031}', ];
let reordered = do_reorder(&fontfile.scope, ttf, None, &syllable)
.expect("failed to reorder syllable");
let output = reordered
.into_iter()
.map(|glyph| glyph.char() as u32)
.collect::<Vec<_>>();
let expected = [
'\u{1031}' as u32, '\u{1031}' as u32, '\u{104b}' as u32, ];
assert_eq_hex!(&output, &expected);
}
}
mod gsub {
use super::*;
#[test]
fn gsub1() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let x = apply_gsub(&fontfile.scope, ttf, None, "\u{1045}\u{103c}").unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(gids, [423, 472]);
}
#[test]
fn gsub2() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let x = apply_gsub(&fontfile.scope, ttf, None, "\u{1029}\u{200c}").unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(gids, [430, 354, 707]);
}
#[test]
fn mark_filtering_set() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let syllable = IntoIterator::into_iter([
'\u{101C}', '\u{103E}', '\u{102F}', '\u{1036}', '\u{1037}', ])
.collect::<String>();
let x = apply_gsub(&fontfile.scope, ttf, None, &syllable).unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(gids, [346, 458, 408, 410]);
}
#[test]
#[ignore = "ordering occurs prior to shaping in HB"]
fn ordering_pre_shaping() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let syllable = IntoIterator::into_iter([
'\u{1004}', '\u{103A}', '\u{1037}', ])
.collect::<String>();
let x = apply_gsub(&fontfile.scope, ttf, None, &syllable).unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(gids, [231, 410, 414]);
}
#[test]
fn multiple_anusvara() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let syllable = IntoIterator::into_iter([
'\u{1006}', '\u{102F}', '\u{1036}', '\u{1036}', ])
.collect::<String>();
let x = apply_gsub(&fontfile.scope, ttf, None, &syllable).unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(gids, [247, 408, 395, 408]);
}
#[test]
fn zwnj() {
let font = read_fixture_font("myanmar/Padauk-Regular.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let syllable = IntoIterator::into_iter([
'\u{1026}', '\u{1038}', '\u{200C}', ])
.collect::<String>();
let lang_tag = tag::from_string("BRM").unwrap(); let x = apply_gsub(&fontfile.scope, ttf, Some(lang_tag), &syllable).unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(gids, [377, 390, 411, 707]);
}
}
#[test]
#[cfg(feature = "prince")]
fn complex_cluster() {
let font = read_fixture_font("myanmar/MMRTEXT.ttf");
let fontfile = ReadScope::new(&font).read::<OpenTypeFont<'_>>().unwrap();
let ttf = match fontfile.data {
OpenTypeData::Single(ttf) => ttf,
OpenTypeData::Collection(_ttc) => unreachable!(),
};
let x = apply_gsub(&fontfile.scope, ttf, None, "င်္က္ကျြွှေို့်ာှီ့ၤဲံ့းႍ").unwrap();
let gids = x.iter().map(|glyph| glyph.glyph_index).collect::<Vec<_>>();
assert_eq!(
gids,
[
344, 476, 235, 734, 615, 715, 511, 762, 370, 339, 506, 341, 367, 372, 345, 366,
367, 368, 384
]
);
}
}