Skip to content

Commit

Permalink
Redesign the features
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanUkhov committed May 24, 2024
1 parent 51c3762 commit 79a95e4
Show file tree
Hide file tree
Showing 15 changed files with 288 additions and 225 deletions.
36 changes: 18 additions & 18 deletions src/formats/opentype/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::rc::Rc;

use opentype;

use crate::formats::opentype::characters::{Mapping, ReverseMapping};
use crate::formats::opentype::mapping;
use crate::formats::opentype::metrics::Metrics;

pub type Reference<T> = Rc<RefCell<T>>;
Expand All @@ -21,9 +21,9 @@ macro_rules! cache(
tape,
backend,

mapping: Default::default(),
metrics: Default::default(),
forward_mapping: Default::default(),
reverse_mapping: Default::default(),
metrics: Default::default(),

$($field: Default::default(),)+
}
Expand All @@ -37,9 +37,9 @@ macro_rules! cache(
pub tape: Reference<T>,
pub backend: opentype::Font,

mapping: Option<Rc<Mapping>>,
forward_mapping: Option<Rc<mapping::Forward>>,
reverse_mapping: Option<Rc<mapping::Reverse>>,
metrics: Option<Rc<Metrics>>,
reverse_mapping: Option<Rc<ReverseMapping>>,

$(pub $field: Option<Reference<$type>>,)+
}
Expand Down Expand Up @@ -178,12 +178,20 @@ cache! {
}

impl<T: crate::Read> Cache<T> {
pub fn mapping(&mut self) -> Result<&Rc<Mapping>> {
if self.mapping.is_none() {
let value = Mapping::new(&self.character_mapping()?.borrow())?;
self.mapping = Some(Rc::new(value));
pub fn forward_mapping(&mut self) -> Result<&Rc<mapping::Forward>> {
if self.forward_mapping.is_none() {
let value = mapping::Forward::new(&self.character_mapping()?.borrow())?;
self.forward_mapping = Some(Rc::new(value));
}
Ok(self.forward_mapping.as_ref().unwrap())
}

pub fn reverse_mapping(&mut self) -> Result<&Rc<mapping::Reverse>> {
if self.reverse_mapping.is_none() {
let value = mapping::Reverse::new(&self.forward_mapping()?.clone());
self.reverse_mapping = Some(Rc::new(value));
}
Ok(self.mapping.as_ref().unwrap())
Ok(self.reverse_mapping.as_ref().unwrap())
}

pub fn metrics(&mut self) -> Result<&Rc<Metrics>> {
Expand All @@ -193,12 +201,4 @@ impl<T: crate::Read> Cache<T> {
}
Ok(self.metrics.as_ref().unwrap())
}

pub fn reverse_mapping(&mut self) -> Result<&Rc<ReverseMapping>> {
if self.reverse_mapping.is_none() {
let value = ReverseMapping::new(&self.mapping()?.clone());
self.reverse_mapping = Some(Rc::new(value));
}
Ok(self.reverse_mapping.as_ref().unwrap())
}
}
112 changes: 17 additions & 95 deletions src/formats/opentype/characters.rs
Original file line number Diff line number Diff line change
@@ -1,93 +1,21 @@
//! Unicode code points.
//! Unicode characters.

use std::collections::{BTreeSet, HashMap};
use std::io::Result;

use opentype::truetype::tables::character_mapping::{CharacterMapping, Encoding};
use opentype::truetype::GlyphID;
use opentype::truetype::tables::character_mapping::Encoding;

use crate::formats::opentype::cache::Cache;

/// A character.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
/// A Unicode character.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Character {
Scalar(char),
Single(char),
Range(char, char),
Inline(char, char),
List(Vec<Character>),
}

/// Characters.
/// Unicode characters.
pub type Characters = Vec<Character>;

pub(crate) struct Mapping(HashMap<u32, GlyphID>);

pub(crate) struct ReverseMapping(HashMap<GlyphID, BTreeSet<u32>>);

impl Character {
fn first(&self) -> Option<char> {
match self {
Self::Scalar(value) => Some(*value),
Self::Range(value, _) => Some(*value),
Self::Inline(value, _) => Some(*value),
Self::List(value) => value.first().and_then(Character::first),
}
}
}

impl std::cmp::Ord for Character {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.first().cmp(&other.first())
}
}

impl std::cmp::PartialOrd for Character {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Mapping {
pub fn new(character_mapping: &CharacterMapping) -> Result<Self> {
for encoding in character_mapping.encodings.iter() {
match encoding {
Encoding::Format0(encoding) => return Ok(Self(encoding.mapping())),
Encoding::Format4(encoding) => return Ok(Self(encoding.mapping())),
Encoding::Format6(encoding) => return Ok(Self(encoding.mapping())),
Encoding::Format12(encoding) => return Ok(Self(encoding.mapping())),
_ => {}
}
}
raise!("found no known character-to-glyph encoding")
}

#[inline]
pub fn get(&self, character: char) -> Option<GlyphID> {
self.0.get(&(character as u32)).copied()
}
}

impl ReverseMapping {
pub fn new(mapping: &Mapping) -> Self {
let mut values = HashMap::<_, BTreeSet<_>>::default();
for (character_id, glyph_id) in &mapping.0 {
values.entry(*glyph_id).or_default().insert(*character_id);
}
Self(values)
}

#[inline]
pub fn get(&self, glyph_id: GlyphID) -> Option<char> {
self.0
.get(&glyph_id)
.and_then(BTreeSet::first)
.cloned()
.and_then(char::from_u32)
}
}

pub(crate) fn read<T: crate::Read>(cache: &mut Cache<T>) -> Result<Vec<Character>> {
for encoding in cache.character_mapping()?.borrow().encodings.iter() {
let ranges = match encoding {
Expand All @@ -108,29 +36,23 @@ fn compress(ranges: Vec<(u32, u32)>) -> Result<Vec<Character>> {
if let (Some(start), Some(end)) = (char::from_u32(range.0), char::from_u32(range.1)) {
if let Some(value) = values.last_mut() {
let (first, last) = match value {
Character::Scalar(first) => (*first, *first),
Character::Inline(first, last) => (*first, *last),
_ => unreachable!(),
Character::Single(first) => (*first, *first),
Character::Range(first, last) => (*first, *last),
};
if last as usize + 1 == start as usize {
*value = Character::Inline(first, end);
*value = Character::Range(first, end);
continue;
}
}
inline(&mut values, (start, end));
if start == end {
values.push(Character::Single(start));
} else if start as usize + 1 == end as usize {
values.push(Character::Single(start));
values.push(Character::Single(end));
} else {
values.push(Character::Range(start, end));
}
}
}
Ok(values)
}

#[inline]
fn inline(values: &mut Vec<Character>, (start, end): (char, char)) {
if start == end {
values.push(Character::Scalar(start));
} else if start as usize + 1 == end as usize {
values.push(Character::Scalar(start));
values.push(Character::Scalar(end));
} else {
values.push(Character::Inline(start, end));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ use opentype::truetype::GlyphID;

#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
pub enum Glyph {
Scalar(GlyphID),
Single(GlyphID),
Range(GlyphID, GlyphID),
Ranges(Vec<(GlyphID, GlyphID)>),
List(Vec<GlyphID>),
}

pub trait Glyphs: Sized {
pub type Graph = BTreeMap<Vec<Glyph>, Vec<Glyph>>;

pub trait Table: Sized {
#[inline]
fn extract(&self, _: &Directory<Self>) -> BTreeMap<Vec<Glyph>, Vec<Glyph>>
fn extract(&self, _: &Directory<Self>) -> Graph
where
Self: Sized,
{
Expand All @@ -34,7 +36,7 @@ pub trait Glyphs: Sized {
impl From<GlyphID> for Glyph {
#[inline]
fn from(value: GlyphID) -> Self {
Self::Scalar(value)
Self::Single(value)
}
}

Expand Down Expand Up @@ -73,10 +75,10 @@ impl From<Coverage> for Glyph {
}
}

impl Glyphs for opentype::tables::glyph_positioning::Type {}
impl Table for opentype::tables::glyph_positioning::Type {}

impl Glyphs for opentype::tables::glyph_substitution::Type {
fn extract(&self, directory: &Directory<Self>) -> BTreeMap<Vec<Glyph>, Vec<Glyph>> {
impl Table for opentype::tables::glyph_substitution::Type {
fn extract(&self, directory: &Directory<Self>) -> Graph {
use opentype::layout::{ChainedContext, Context};
use opentype::tables::glyph_substitution::{SingleSubstitution, Type};

Expand Down
59 changes: 34 additions & 25 deletions src/formats/opentype/features/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
//! Layout features.

mod characters;
mod glyphs;
mod graph;
mod sequence;
mod transform;

pub use opentype::layout::{Language, Script};

pub use sequence::{Position, Sequence};

use std::collections::{BTreeMap, BTreeSet};
use std::io::Result;

use opentype::layout::{Directory, Feature};

use crate::formats::opentype::cache::Cache;
use crate::formats::opentype::characters::Character;
use crate::formats::opentype::features::characters::Characters;
use crate::formats::opentype::features::glyphs::{Glyph, Glyphs};
use crate::formats::opentype::features::graph::{Graph, Table};
use crate::formats::opentype::features::transform::Transform;

/// Layout features.
pub type Features = BTreeMap<Type, Value>;
Expand All @@ -22,7 +24,7 @@ pub type Features = BTreeMap<Type, Value>;
pub type Type = Feature;

/// A value.
pub type Value = BTreeMap<Script, BTreeMap<Language, BTreeSet<Character>>>;
pub type Value = BTreeMap<Script, BTreeMap<Language, BTreeSet<Sequence>>>;

pub(crate) fn read<T: crate::Read>(cache: &mut Cache<T>) -> Result<Features> {
let mut values = Default::default();
Expand All @@ -33,35 +35,35 @@ pub(crate) fn read<T: crate::Read>(cache: &mut Cache<T>) -> Result<Features> {
populate(&mut values, &table.borrow());
}
let mapping = cache.reverse_mapping()?.clone();
Ok(values.characters(&mapping, ()))
Ok(values.transform(&mapping, ()))
}

#[allow(clippy::type_complexity)]
fn populate<T>(
values: &mut BTreeMap<
Feature,
BTreeMap<Script, BTreeMap<Language, BTreeMap<Vec<Glyph>, Vec<Glyph>>>>,
>,
table: &Directory<T>,
values: &mut BTreeMap<Feature, BTreeMap<Script, BTreeMap<Language, Graph>>>,
directory: &Directory<T>,
) where
T: Glyphs,
T: Table,
{
for (i, header) in table.scripts.headers.iter().enumerate() {
for (i, header) in directory.scripts.headers.iter().enumerate() {
let script = Script::from_tag(&header.tag);
if let Some(record) = table.scripts.records[i].default_language.as_ref() {
if let Some(record) = directory.scripts.records[i].default_language.as_ref() {
for index in record.feature_indices.iter().cloned().map(usize::from) {
if let (Some(header), Some(record)) = (
table.features.headers.get(index),
table.features.records.get(index),
directory.features.headers.get(index),
directory.features.records.get(index),
) {
let feature = Feature::from_tag(&header.tag);
let glyphs = record
.lookup_indices
.iter()
.cloned()
.filter_map(|index| table.lookups.records.get(index as usize))
.filter_map(|index| directory.lookups.records.get(index as usize))
.flat_map(|record| {
record.tables.iter().flat_map(|other| other.extract(table))
record
.tables
.iter()
.flat_map(|table| table.extract(directory))
})
.collect::<BTreeMap<_, _>>();
values
Expand All @@ -73,22 +75,29 @@ fn populate<T>(
}
}
}
for (j, header) in table.scripts.records[i].language_headers.iter().enumerate() {
for (j, header) in directory.scripts.records[i]
.language_headers
.iter()
.enumerate()
{
let language = Language::from_tag(&header.tag);
let record = &table.scripts.records[i].language_records[j];
let record = &directory.scripts.records[i].language_records[j];
for index in record.feature_indices.iter().cloned().map(usize::from) {
if let (Some(header), Some(record)) = (
table.features.headers.get(index),
table.features.records.get(index),
directory.features.headers.get(index),
directory.features.records.get(index),
) {
let feature = Feature::from_tag(&header.tag);
let glyphs = record
.lookup_indices
.iter()
.cloned()
.filter_map(|index| table.lookups.records.get(index as usize))
.filter_map(|index| directory.lookups.records.get(index as usize))
.flat_map(|record| {
record.tables.iter().flat_map(|other| other.extract(table))
record
.tables
.iter()
.flat_map(|table| table.extract(directory))
})
.collect::<BTreeMap<_, _>>();
values
Expand Down
Loading

0 comments on commit 79a95e4

Please sign in to comment.