use std::ops::Range;
use fea_rs::typed::{AstNode as _, GlyphOrClass};
use smol_str::SmolStr;
use crate::AsFea;
const FEA_KEYWORDS: [&str; 52] = [
"anchor",
"anchordef",
"anon",
"anonymous",
"by",
"contour",
"cursive",
"device",
"enum",
"enumerate",
"excludedflt",
"exclude_dflt",
"feature",
"from",
"ignore",
"ignorebaseglyphs",
"ignoreligatures",
"ignoremarks",
"include",
"includedflt",
"include_dflt",
"language",
"languagesystem",
"lookup",
"lookupflag",
"mark",
"markattachmenttype",
"markclass",
"nameid",
"null",
"parameters",
"pos",
"position",
"required",
"righttoleft",
"reversesub",
"rsub",
"script",
"sub",
"substitute",
"subtable",
"table",
"usemarkfilteringset",
"useextension",
"valuerecorddef",
"base",
"gdef",
"head",
"hhea",
"name",
"vhea",
"vmtx",
];
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct GlyphName {
pub name: SmolStr,
}
impl std::fmt::Debug for GlyphName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GlyphName({})", self.name)
}
}
impl GlyphName {
pub fn new(name: &str) -> Self {
Self {
name: SmolStr::new(name),
}
}
pub fn glyphset(&self) -> impl Iterator<Item = &SmolStr> {
std::iter::once(&self.name)
}
}
impl AsFea for GlyphName {
fn as_fea(&self, _indent: &str) -> String {
if FEA_KEYWORDS.contains(&self.name.as_str()) {
format!("\\{}", self.name)
} else {
self.name.to_string()
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GlyphClass {
pub glyphs: Vec<GlyphContainer>,
#[cfg_attr(
feature = "serde",
serde(
default = "crate::default_range",
skip_serializing_if = "crate::is_default_range"
)
)]
pub location: Range<usize>,
}
impl GlyphClass {
pub fn new(glyphs: Vec<GlyphContainer>, location: Range<usize>) -> Self {
Self { glyphs, location }
}
}
impl AsFea for GlyphClass {
fn as_fea(&self, _indent: &str) -> String {
let inner: Vec<String> = self.glyphs.iter().map(|g| g.as_fea("")).collect();
format!("[{}]", inner.join(" "))
}
}
impl From<fea_rs::typed::GlyphClass> for GlyphClass {
fn from(val: fea_rs::typed::GlyphClass) -> Self {
match val {
fea_rs::typed::GlyphClass::Named(glyph_class_name) => {
let members = vec![GlyphContainer::GlyphClassName(SmolStr::new(
glyph_class_name.text(),
))];
GlyphClass::new(members, glyph_class_name.range())
}
fea_rs::typed::GlyphClass::Literal(glyph_class_literal) => glyph_class_literal.into(),
}
}
}
impl From<fea_rs::typed::GlyphClassLiteral> for GlyphClass {
fn from(val: fea_rs::typed::GlyphClassLiteral) -> Self {
let members: Vec<GlyphContainer> = val
.node()
.iter_children()
.flat_map(|child| {
if let Some(gc) = fea_rs::typed::GlyphOrClass::cast(child) {
Some(gc.into())
} else if let Some(gr) = fea_rs::typed::GlyphRange::cast(child) {
let start = gr
.iter()
.find_map(fea_rs::typed::GlyphName::cast)
.map(|gn| SmolStr::new(gn.text()))
.unwrap();
let end = gr
.iter()
.skip_while(|t| t.kind() != fea_rs::Kind::Hyphen)
.find_map(fea_rs::typed::GlyphName::cast)
.map(|gn| SmolStr::new(gn.text()))
.unwrap();
Some(GlyphContainer::GlyphRange(GlyphRange::new(start, end)))
} else if child.kind() == fea_rs::Kind::GlyphNameOrRange {
Some(GlyphContainer::GlyphNameOrRange(
child.token_text().unwrap().into(),
))
} else {
None
}
})
.collect();
GlyphClass::new(members, val.node().range())
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GlyphRange {
pub start: SmolStr,
pub end: SmolStr,
}
impl GlyphRange {
pub fn new(start: SmolStr, end: SmolStr) -> Self {
Self { start, end }
}
pub fn glyphset(&self) -> impl Iterator<Item = SmolStr> {
let start_bytes = self.start.as_bytes();
let end_bytes = self.end.as_bytes();
let mut prefix_len = 0;
let mut suffix_len = 0;
for (start_byte, end_byte) in start_bytes.iter().zip(end_bytes.iter()) {
if start_byte == end_byte {
prefix_len += 1;
} else {
break;
}
}
for (start_byte, end_byte) in start_bytes.iter().rev().zip(end_bytes.iter().rev()) {
if start_byte == end_byte {
suffix_len += 1;
} else {
break;
}
}
let start_core = &self.start[prefix_len..self.start.len() - suffix_len];
let end_core = &self.end[prefix_len..self.end.len() - suffix_len];
if start_core.len() != end_core.len() {
return vec![self.start.clone(), self.end.clone()].into_iter();
}
if start_core.len() == 1 {
let start_char = start_core.chars().next().unwrap();
let end_char = end_core.chars().next().unwrap();
if (start_char.is_ascii_lowercase() && end_char.is_ascii_lowercase())
|| (start_char.is_ascii_uppercase() && end_char.is_ascii_uppercase())
{
let range = start_char as u8..=end_char as u8;
let glyphs: Vec<SmolStr> = range
.map(|b| {
SmolStr::new(format!(
"{}{}{}",
&self.start[..prefix_len],
b as char,
&self.start[self.start.len() - suffix_len..]
))
})
.collect();
return glyphs.into_iter();
} else {
return vec![self.start.clone(), self.end.clone()].into_iter();
}
}
let start_num: Option<usize> = start_core.parse().ok();
let end_num: Option<usize> = end_core.parse().ok();
if let (Some(start_num), Some(end_num)) = (start_num, end_num) {
let range = start_num..=end_num;
let glyphs: Vec<SmolStr> = range
.map(|n| {
let mut s = String::new();
s.push_str(&self.start[..prefix_len]);
s.push_str(&format!("{:0width$}", n, width = start_core.len()));
s.push_str(&self.start[self.start.len() - suffix_len..]);
SmolStr::new(s)
})
.collect();
return glyphs.into_iter();
}
vec![self.start.clone(), self.end.clone()].into_iter()
}
}
impl From<Range<SmolStr>> for GlyphRange {
fn from(val: Range<SmolStr>) -> Self {
GlyphRange::new(val.start, val.end)
}
}
impl AsFea for GlyphRange {
fn as_fea(&self, _indent: &str) -> String {
format!("{} - {}", self.start.as_str(), self.end.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum GlyphContainer {
GlyphName(GlyphName),
GlyphClass(GlyphClass),
GlyphClassName(SmolStr),
GlyphRange(GlyphRange),
GlyphNameOrRange(SmolStr),
}
impl AsFea for GlyphContainer {
fn as_fea(&self, _indent: &str) -> String {
match self {
GlyphContainer::GlyphName(gn) => gn.as_fea(_indent),
GlyphContainer::GlyphClass(gcs) => {
let inner: Vec<String> = gcs.glyphs.iter().map(|g| g.as_fea("")).collect();
format!("[{}]", inner.join(" "))
}
GlyphContainer::GlyphClassName(name) => {
if FEA_KEYWORDS.contains(&name.as_str()) {
format!("\\{}", name)
} else {
name.to_string()
}
}
GlyphContainer::GlyphRange(range) => range.as_fea(""),
GlyphContainer::GlyphNameOrRange(name_or_range) => name_or_range.to_string(),
}
}
}
impl From<fea_rs::typed::GlyphOrClass> for GlyphContainer {
fn from(val: fea_rs::typed::GlyphOrClass) -> Self {
match val {
GlyphOrClass::Glyph(glyph) => GlyphContainer::GlyphName(GlyphName::new(glyph.text())),
GlyphOrClass::Class(glyph_class_literal) => {
GlyphContainer::GlyphClass(glyph_class_literal.into())
}
GlyphOrClass::Cid(_cid) => todo!(),
GlyphOrClass::NamedClass(glyph_class_name) => {
GlyphContainer::GlyphClassName(SmolStr::new(glyph_class_name.text()))
}
GlyphOrClass::Null(_) => GlyphContainer::GlyphName(GlyphName::new("NULL")),
}
}
}
impl From<fea_rs::typed::GlyphClass> for GlyphContainer {
fn from(val: fea_rs::typed::GlyphClass) -> Self {
match val {
fea_rs::typed::GlyphClass::Named(glyph_class_name) => {
GlyphContainer::GlyphClassName(SmolStr::new(glyph_class_name.text()))
}
fea_rs::typed::GlyphClass::Literal(glyph_class_literal) => {
GlyphContainer::GlyphClass(glyph_class_literal.into())
}
}
}
}
impl GlyphContainer {
pub fn new_class(glyph_names: &[&str]) -> Self {
let members: Vec<GlyphContainer> = glyph_names
.iter()
.map(|name| GlyphContainer::GlyphName(GlyphName::new(name)))
.collect();
GlyphContainer::GlyphClass(GlyphClass::new(
members,
0..0, ))
}
pub fn is_empty(&self) -> bool {
match self {
GlyphContainer::GlyphClass(gcs) => gcs.glyphs.is_empty(),
_ => false,
}
}
}
#[cfg(feature = "serde")]
mod glyph_container_serde {
use super::*;
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
impl Serialize for super::GlyphContainer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
super::GlyphContainer::GlyphName(gn) => {
serializer.serialize_str(&gn.name)
}
super::GlyphContainer::GlyphClass(gc) => gc.serialize(serializer),
super::GlyphContainer::GlyphClassName(name) => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("GlyphClassName", name)?;
map.end()
}
super::GlyphContainer::GlyphRange(gr) => gr.serialize(serializer),
super::GlyphContainer::GlyphNameOrRange(name) => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("GlyphNameOrRange", name)?;
map.end()
}
}
}
}
impl<'de> Deserialize<'de> for super::GlyphContainer {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum GlyphContainerEnum {
String(String),
GlyphClass(GlyphClass),
GlyphClassName {
#[serde(rename = "GlyphClassName")]
glyph_class_name: SmolStr,
},
GlyphRange(GlyphRange),
GlyphNameOrRange {
#[serde(rename = "GlyphNameOrRange")]
glyph_name_or_range: SmolStr,
},
}
match GlyphContainerEnum::deserialize(deserializer)? {
GlyphContainerEnum::String(s) => {
Ok(super::GlyphContainer::GlyphName(GlyphName::new(&s)))
}
GlyphContainerEnum::GlyphClass(gc) => Ok(super::GlyphContainer::GlyphClass(gc)),
GlyphContainerEnum::GlyphClassName {
glyph_class_name: cn,
} => Ok(super::GlyphContainer::GlyphClassName(cn)),
GlyphContainerEnum::GlyphRange(gr) => Ok(super::GlyphContainer::GlyphRange(gr)),
GlyphContainerEnum::GlyphNameOrRange {
glyph_name_or_range: nor,
} => Ok(super::GlyphContainer::GlyphNameOrRange(nor)),
}
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MarkClass {
pub name: SmolStr,
}
impl MarkClass {
pub fn new(name: &str) -> Self {
Self {
name: SmolStr::new(name),
}
}
}
impl From<fea_rs::typed::GlyphClassDef> for MarkClass {
fn from(val: fea_rs::typed::GlyphClassDef) -> Self {
let label = val
.iter()
.find_map(fea_rs::typed::GlyphClassName::cast)
.unwrap();
MarkClass::new(label.text().trim_start_matches('@'))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glyphclass() {
const FEA: &str = "@foo = [a-b c d-e @bar];";
let (parsed, _) = fea_rs::parse::parse_string(FEA);
let definition = parsed
.root()
.iter_children()
.find_map(fea_rs::typed::GlyphClassDef::cast)
.unwrap();
let literal = definition
.node()
.iter_children()
.find_map(fea_rs::typed::GlyphClassLiteral::cast)
.unwrap();
println!("{:#?}", literal);
let glyphs: GlyphClass = literal.into();
assert_eq!(glyphs.glyphs.len(), 4);
}
#[test]
fn test_glyphrange() {
let range = GlyphRange::new(SmolStr::new("a01"), SmolStr::new("a05"));
let glyphs: Vec<SmolStr> = range.glyphset().collect();
assert_eq!(
glyphs,
vec![
SmolStr::new("a01"),
SmolStr::new("a02"),
SmolStr::new("a03"),
SmolStr::new("a04"),
SmolStr::new("a05"),
]
);
let range = GlyphRange::new(SmolStr::new("B"), SmolStr::new("F"));
let glyphs: Vec<SmolStr> = range.glyphset().collect();
assert_eq!(
glyphs,
vec![
SmolStr::new("B"),
SmolStr::new("C"),
SmolStr::new("D"),
SmolStr::new("E"),
SmolStr::new("F"),
]
);
let range = GlyphRange::new(SmolStr::new("cat"), SmolStr::new("cet"));
let glyphs: Vec<SmolStr> = range.glyphset().collect();
assert_eq!(
glyphs,
vec![
SmolStr::new("cat"),
SmolStr::new("cbt"),
SmolStr::new("cct"),
SmolStr::new("cdt"),
SmolStr::new("cet"),
]
);
}
}