Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize Ligature Caret List #1227

Merged
merged 2 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions otl-normalizer/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ pub struct Args {
/// Optional destination path for writing output. Default is stdout.
pub out: Option<PathBuf>,
/// Target table to print, one of gpos/gsub/all (case insensitive)
#[arg(short, long)]
pub table: Option<Table>,
#[arg(short, long, default_value_t)]
pub table: Table,
/// Index of font to examine, if target is a font collection
#[arg(short, long)]
pub index: Option<u32>,
Expand All @@ -21,16 +21,29 @@ pub enum Table {
All,
Gpos,
Gsub,
Gdef,
}

impl std::fmt::Display for Table {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Table::All => f.write_str("all"),
Table::Gpos => f.write_str("gpos"),
Table::Gsub => f.write_str("gsub"),
Table::Gdef => f.write_str("gdef"),
}
}
}

impl FromStr for Table {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
static ERR_MSG: &str = "expected one of 'gsub', 'gpos', 'all'";
static ERR_MSG: &str = "expected one of 'gsub', 'gpos', 'gdef', 'all'";
match s.to_ascii_lowercase().trim() {
"gpos" => Ok(Self::Gpos),
"gsub" => Ok(Self::Gsub),
"gdef" => Ok(Self::Gdef),
"all" => Ok(Self::All),
_ => Err(ERR_MSG),
}
Expand Down
69 changes: 67 additions & 2 deletions otl-normalizer/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ use std::{
};

use write_fonts::{
read::tables::layout::{FeatureList, ScriptList},
read::{
tables::{
gpos::DeviceOrVariationIndex,
layout::{FeatureList, ScriptList},
},
ReadError,
},
tables::layout::LookupFlag,
types::{GlyphId16, Tag},
};

use crate::glyph_names::NameMap;
use crate::{glyph_names::NameMap, variations::DeltaComputer};

pub(crate) struct LanguageSystem {
script: Tag,
Expand Down Expand Up @@ -71,6 +77,37 @@ where
filter_set: Option<u16>,
}

/// Resolved device or delta values
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum DeviceOrDeltas {
Device {
start: u16,
end: u16,
values: Vec<i8>,
},
Deltas(Vec<i32>),
}

impl DeviceOrDeltas {
pub fn new(
default_value: i16,
device: DeviceOrVariationIndex,
ivs: Option<&DeltaComputer>,
) -> Result<Self, ReadError> {
match device {
DeviceOrVariationIndex::Device(device) => Ok(Self::Device {
start: device.start_size(),
end: device.end_size(),
values: device.iter().collect(),
}),
DeviceOrVariationIndex::VariationIndex(idx) => ivs
.unwrap()
.master_values(default_value as _, idx)
.map(Self::Deltas),
}
}
}

impl LanguageSystem {
fn sort_key(&self) -> impl Ord {
(tag_to_int(self.script), tag_to_int(self.lang))
Expand Down Expand Up @@ -377,3 +414,31 @@ impl FromIterator<GlyphId16> for GlyphSet {
GlyphSet::Multiple(iter.into_iter().collect())
}
}

impl Display for DeviceOrDeltas {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeviceOrDeltas::Device { start, end, values } => {
write!(f, " [({start})")?;
for (i, adj) in values.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{adj}")?;
}
write!(f, "({end})]")?;
}
DeviceOrDeltas::Deltas(deltas) => {
write!(f, " {{")?;
for (i, var) in deltas.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{var}")?;
}
write!(f, "}}")?;
}
}
Ok(())
}
}
2 changes: 1 addition & 1 deletion otl-normalizer/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub enum Error {
#[error("write error: '{0}'")]
Write(#[from] std::io::Error),
#[error("could not read font data: '{0}")]
FontRead(ReadError),
FontRead(#[from] ReadError),
#[error("missing table '{0}'")]
MissingTable(Tag),
}
104 changes: 104 additions & 0 deletions otl-normalizer/src/gdef.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! Normalizing the ligature caret table

use std::{fmt::Display, io};

use fontdrasil::types::GlyphName;
use write_fonts::read::{
tables::gdef::{CaretValue, Gdef, LigGlyph},
ReadError,
};

use crate::{common::DeviceOrDeltas, variations::DeltaComputer, Error, NameMap};

/// Print normalized GDEF ligature carets
pub fn print(f: &mut dyn io::Write, table: &Gdef, names: &NameMap) -> Result<(), Error> {
let var_store = table
.item_var_store()
.map(|ivs| ivs.and_then(DeltaComputer::new))
.transpose()
.unwrap();

// so this is relatively simple; we're just looking at the ligature caret list.
// - realistically, we only care if this has variations? but I think it's simpler
// if we just always normalize, variations or no.

let Some(lig_carets) = table.lig_caret_list().transpose().unwrap() else {
return Ok(());
};

let coverage = lig_carets.coverage()?;
for (gid, lig_glyph) in coverage.iter().zip(lig_carets.lig_glyphs().iter()) {
let lig_glyph = lig_glyph?;
let name = names.get(gid);
print_lig_carets(f, name, lig_glyph, var_store.as_ref())?;
}

Ok(())
}

enum ResolvedCaret {
Coordinate {
pos: i16,
device_or_deltas: Option<DeviceOrDeltas>,
},
// basically never used?
ContourPoint {
idx: u16,
},
}

impl ResolvedCaret {
fn new(raw: CaretValue, computer: Option<&DeltaComputer>) -> Result<Self, ReadError> {
match raw {
CaretValue::Format1(table_ref) => Ok(Self::Coordinate {
pos: table_ref.coordinate(),
device_or_deltas: None,
}),
CaretValue::Format2(table_ref) => Ok(Self::ContourPoint {
idx: table_ref.caret_value_point_index(),
}),
CaretValue::Format3(table_ref) => {
let pos = table_ref.coordinate();
let device = table_ref.device()?;
let device_or_deltas = DeviceOrDeltas::new(pos, device, computer)?;
Ok(Self::Coordinate {
pos,
device_or_deltas: Some(device_or_deltas),
})
}
}
}
}

fn print_lig_carets(
f: &mut dyn io::Write,
gname: &GlyphName,
lig_glyph: LigGlyph,
computer: Option<&DeltaComputer>,
) -> Result<(), Error> {
writeln!(f, "{gname}")?;
for (i, caret) in lig_glyph.caret_values().iter().enumerate() {
let resolved = caret.and_then(|caret| ResolvedCaret::new(caret, computer))?;
writeln!(f, " {i}: {resolved}")?;
}

Ok(())
}

impl Display for ResolvedCaret {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResolvedCaret::Coordinate {
pos,
device_or_deltas,
} => {
write!(f, "coord {pos}")?;
if let Some(device_or_deltas) = device_or_deltas {
write!(f, "{device_or_deltas}")?;
}
}
ResolvedCaret::ContourPoint { idx } => write!(f, "idx {idx}")?,
}
Ok(())
}
}
50 changes: 6 additions & 44 deletions otl-normalizer/src/gpos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use write_fonts::{
};

use crate::{
common::{self, GlyphSet, Lookup, PrintNames, SingleRule},
common::{self, DeviceOrDeltas, GlyphSet, Lookup, PrintNames, SingleRule},
error::Error,
glyph_names::NameMap,
variations::DeltaComputer,
Expand Down Expand Up @@ -116,16 +116,6 @@ fn print_rules<T: PrintNames + Clone>(
Ok(())
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum DeviceOrDeltas {
Device {
start: u16,
end: u16,
values: Vec<i8>,
},
Deltas(Vec<i32>),
}

/// A value plus an optional device table or set of deltas
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct ResolvedValue {
Expand Down Expand Up @@ -223,17 +213,9 @@ impl ResolvedValue {
ivs: Option<&DeltaComputer>,
) -> Result<Self, ReadError> {
let default = default.unwrap_or_default();
let device_or_deltas = device.transpose()?.map(|device| match device {
DeviceOrVariationIndex::Device(device) => Ok(DeviceOrDeltas::Device {
start: device.start_size(),
end: device.end_size(),
values: device.iter().collect(),
}),
DeviceOrVariationIndex::VariationIndex(idx) => ivs
.unwrap()
.master_values(default as _, idx)
.map(DeviceOrDeltas::Deltas),
});
let device_or_deltas = device
.transpose()?
.map(|device| DeviceOrDeltas::new(default, device, ivs));
device_or_deltas
.transpose()
.map(|device_or_deltas| ResolvedValue {
Expand Down Expand Up @@ -490,28 +472,8 @@ impl Display for ResolvedValueRecord {
impl Display for ResolvedValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.default)?;
match &self.device_or_deltas {
Some(DeviceOrDeltas::Device { start, end, values }) => {
write!(f, " [({start})")?;
for (i, adj) in values.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{adj}")?;
}
write!(f, "({end})]")?;
}
Some(DeviceOrDeltas::Deltas(deltas)) => {
write!(f, " {{")?;
for (i, var) in deltas.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{var}")?;
}
write!(f, "}}")?;
}
None => (),
if let Some(device_or_deltas) = self.device_or_deltas.as_ref() {
write!(f, "{device_or_deltas}")?;
}
Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions otl-normalizer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
pub mod args;
mod common;
mod error;
mod gdef;
mod glyph_names;
mod gpos;
mod gsub;
mod variations;

pub use error::Error;
pub use glyph_names::NameMap;

pub use gdef::print as print_gdef;
pub use gpos::print as print_gpos;
pub use gsub::print as print_gsub;
30 changes: 28 additions & 2 deletions otl-normalizer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ fn main() -> Result<(), Error> {
inner,
})?;

let font = get_font(&data, args.index)?;
// exit early if there's no work, so we don't bother creating an empty file
if !is_there_something_to_do(&font, &args) {
return Ok(());
}

let mut write_target: Box<dyn Write> = match args.out.as_ref() {
Some(path) => File::create(path)
.map_err(|inner| Error::FileWrite {
Expand All @@ -26,10 +32,17 @@ fn main() -> Result<(), Error> {
None => Box::new(std::io::stdout()),
};

let font = get_font(&data, args.index)?;
let name_map = NameMap::from_font(&font)?;
let to_print = args.table.unwrap_or_default();
let to_print = args.table;
let gdef = font.gdef().ok();

if matches!(to_print, args::Table::All | args::Table::Gdef) {
if let Some(gdef) = gdef.as_ref().filter(|gdef| gdef.lig_caret_list().is_some()) {
writeln!(&mut write_target, "# GDEF #")?;
otl_normalizer::print_gdef(&mut write_target, gdef, &name_map)?;
}
}

if matches!(to_print, args::Table::All | args::Table::Gpos) {
if let Ok(gpos) = font.gpos() {
writeln!(&mut write_target, "# GPOS #")?;
Expand Down Expand Up @@ -57,3 +70,16 @@ fn get_font(bytes: &[u8], idx: Option<u32>) -> Result<FontRef, Error> {
(FileRef::Collection(collection), idx) => collection.get(idx).map_err(Error::FontRead),
}
}

fn is_there_something_to_do(font: &FontRef, args: &args::Args) -> bool {
match args.table {
// gdef is meaningless without one of these two
args::Table::All => font.gpos().is_ok() || font.gsub().is_ok(),
args::Table::Gpos => font.gpos().is_ok(),
args::Table::Gsub => font.gsub().is_ok(),
args::Table::Gdef => font
.gdef()
.map(|gdef| gdef.lig_caret_list().is_some())
.unwrap_or(false),
}
}
Loading