-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #578 from googlefonts/layout-repr
Add tool for generating a normalized text representation of mark/kerning rules
- Loading branch information
Showing
14 changed files
with
2,161 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "layout-normalizer" | ||
version = "0.1.0" | ||
edition = "2021" | ||
license = "MIT/Apache-2.0" | ||
publish = false | ||
|
||
[dependencies] | ||
clap.workspace = true | ||
thiserror.workspace = true | ||
smol_str.workspace = true | ||
read-fonts.workspace = true | ||
indexmap.workspace = true | ||
|
||
# cargo-release settings | ||
[package.metadata.release] | ||
release = false |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
//! Build script to generate our glyph name lookup table. | ||
|
||
use std::{ | ||
env, | ||
fs::File, | ||
io::{BufWriter, Write}, | ||
path::Path, | ||
str::FromStr, | ||
}; | ||
|
||
const GLYPH_NAMES_FILE: &str = "glyph_names_codegen.rs"; | ||
static AGLFN: &str = include_str!("aglfn.txt"); | ||
|
||
fn main() { | ||
println!("cargo:rerun-if-changed=resources/aglfn.txt"); | ||
generate_glyph_names(); | ||
} | ||
|
||
fn generate_glyph_names() { | ||
let path = Path::new(&env::var("OUT_DIR").unwrap()).join(GLYPH_NAMES_FILE); | ||
let mut file = BufWriter::new(File::create(path).unwrap()); | ||
|
||
let mut entries = AGLFN | ||
.lines() | ||
.filter(|l| !l.starts_with('#')) | ||
.map(NameEntry::from_str) | ||
.collect::<Result<Vec<_>, _>>() | ||
.unwrap(); | ||
|
||
entries.sort_by(|a, b| a.chr.cmp(&b.chr)); | ||
let formatted = entries | ||
.iter() | ||
.map(|NameEntry { chr, name }| format!("({chr}, SmolStr::new_inline(\"{name}\"))")) | ||
.collect::<Vec<_>>(); | ||
writeln!( | ||
&mut file, | ||
"static GLYPH_NAMES: &[(u32, SmolStr)] = &[\n{}];\n", | ||
formatted.join(",\n") | ||
) | ||
.unwrap(); | ||
} | ||
|
||
struct NameEntry { | ||
chr: u32, | ||
name: String, | ||
} | ||
|
||
impl FromStr for NameEntry { | ||
type Err = String; | ||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
let mut split = s.split(';'); | ||
match (split.next(), split.next(), split.next(), split.next()) { | ||
(Some(cpoint), Some(postscript_name), Some(_unic_name), None) => { | ||
let chr = u32::from_str_radix(cpoint, 16).unwrap(); | ||
let postscript_name = postscript_name.to_string(); | ||
Ok(NameEntry { | ||
chr, | ||
name: postscript_name, | ||
}) | ||
} | ||
_ => Err(s.to_string()), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
use std::{path::PathBuf, str::FromStr}; | ||
|
||
#[derive(Clone, Debug, clap::Parser)] | ||
pub(crate) struct Args { | ||
pub font_path: PathBuf, | ||
#[arg(short, long)] | ||
/// 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>, | ||
/// Index of font to examine, if target is a font collection | ||
#[arg(short, long)] | ||
pub index: Option<u32>, | ||
} | ||
|
||
/// What table to print | ||
#[derive(Clone, Debug, Default)] | ||
pub enum Table { | ||
#[default] | ||
All, | ||
Gpos, | ||
Gsub, | ||
} | ||
|
||
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'"; | ||
match s.to_ascii_lowercase().trim() { | ||
"gpos" => Ok(Self::Gpos), | ||
"gsub" => Ok(Self::Gsub), | ||
"all" => Ok(Self::All), | ||
_ => Err(ERR_MSG), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
//! general common utilities and types | ||
|
||
use std::{collections::BTreeSet, fmt::Display}; | ||
|
||
use read_fonts::{ | ||
tables::layout::{FeatureList, ScriptList}, | ||
types::{GlyphId, Tag}, | ||
}; | ||
|
||
use crate::glyph_names::NameMap; | ||
|
||
/// A set of lookups for a specific feature and language system | ||
pub(crate) struct Feature { | ||
pub(crate) feature: Tag, | ||
pub(crate) script: Tag, | ||
pub(crate) lang: Tag, | ||
pub(crate) lookups: Vec<u16>, | ||
} | ||
|
||
/// A type to represent either one or multiple glyphs | ||
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] | ||
pub(crate) enum GlyphSet { | ||
Single(GlyphId), | ||
Multiple(BTreeSet<GlyphId>), | ||
} | ||
|
||
impl Feature { | ||
fn sort_key(&self) -> impl Ord { | ||
// make it so we always put DFLT/dflt above other tags | ||
fn tag_to_int(tag: Tag) -> u32 { | ||
if tag == Tag::new(b"DFLT") { | ||
0 | ||
} else if tag == Tag::new(b"dflt") { | ||
1 | ||
} else { | ||
u32::from_be_bytes(tag.to_be_bytes()) | ||
} | ||
} | ||
|
||
( | ||
tag_to_int(self.feature), | ||
tag_to_int(self.script), | ||
tag_to_int(self.lang), | ||
) | ||
} | ||
} | ||
|
||
pub(crate) fn get_lang_systems( | ||
script_list: &ScriptList, | ||
feature_list: &FeatureList, | ||
) -> Vec<Feature> { | ||
let data = script_list.offset_data(); | ||
|
||
let mut result = script_list | ||
.script_records() | ||
.iter() | ||
// first iterate all (script, lang, feature indices) | ||
.flat_map(|script| { | ||
let script_tag = script.script_tag(); | ||
let script = script.script(data).unwrap(); | ||
let maybe_default = script | ||
.default_lang_sys() | ||
.transpose() | ||
.unwrap() | ||
.map(|dflt| (script_tag, Tag::new(b"dflt"), dflt.feature_indices())); | ||
let lang_sys_iter = script.lang_sys_records().iter().map(move |lang_sys| { | ||
let lang_tag = lang_sys.lang_sys_tag(); | ||
let lang = lang_sys.lang_sys(script.offset_data()).unwrap(); | ||
(script_tag, lang_tag, lang.feature_indices()) | ||
}); | ||
maybe_default.into_iter().chain(lang_sys_iter) | ||
}) | ||
// then convert these into script/lang/feature/lookup indices | ||
.flat_map(|(script, lang, indices)| { | ||
indices.iter().map(move |idx| { | ||
let rec = feature_list | ||
.feature_records() | ||
.get(idx.get() as usize) | ||
.unwrap(); | ||
let feature = rec.feature(feature_list.offset_data()).unwrap(); | ||
let lookups = feature | ||
.lookup_list_indices() | ||
.iter() | ||
.map(|x| x.get()) | ||
.collect(); | ||
Feature { | ||
feature: rec.feature_tag(), | ||
script, | ||
lang, | ||
lookups, | ||
} | ||
}) | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
result.sort_unstable_by_key(|sys| sys.sort_key()); | ||
|
||
result | ||
} | ||
|
||
impl GlyphSet { | ||
pub(crate) fn make_set(&mut self) { | ||
if let GlyphSet::Single(gid) = self { | ||
*self = GlyphSet::Multiple(BTreeSet::from([*gid])) | ||
} | ||
} | ||
|
||
pub(crate) fn combine(&mut self, other: GlyphSet) { | ||
self.make_set(); | ||
let GlyphSet::Multiple(gids) = self else { | ||
unreachable!() | ||
}; | ||
match other { | ||
GlyphSet::Single(gid) => { | ||
gids.insert(gid); | ||
} | ||
GlyphSet::Multiple(mut multi) => gids.append(&mut multi), | ||
} | ||
} | ||
|
||
pub(crate) fn add(&mut self, gid: GlyphId) { | ||
// if we're a single glyph, don't turn into a set if we're adding ourselves | ||
if matches!(self, GlyphSet::Single(x) if *x == gid) { | ||
return; | ||
} | ||
self.make_set(); | ||
if let GlyphSet::Multiple(set) = self { | ||
set.insert(gid); | ||
} | ||
} | ||
|
||
pub(crate) fn printer<'a>(&'a self, names: &'a NameMap) -> impl Display + 'a { | ||
// A helper for printing one or more glyphs | ||
struct GlyphPrinter<'a> { | ||
glyphs: &'a GlyphSet, | ||
names: &'a NameMap, | ||
} | ||
|
||
impl Display for GlyphPrinter<'_> { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self.glyphs { | ||
GlyphSet::Single(single) => { | ||
let name = self.names.get(*single); | ||
f.write_str(name) | ||
} | ||
GlyphSet::Multiple(glyphs) => { | ||
f.write_str("[")?; | ||
let mut first = true; | ||
for gid in glyphs { | ||
let name = self.names.get(*gid); | ||
if !first { | ||
f.write_str(",")?; | ||
} | ||
f.write_str(name)?; | ||
first = false; | ||
} | ||
f.write_str("]") | ||
} | ||
} | ||
} | ||
} | ||
|
||
GlyphPrinter { | ||
glyphs: self, | ||
names, | ||
} | ||
} | ||
} | ||
|
||
impl From<GlyphId> for GlyphSet { | ||
fn from(src: GlyphId) -> GlyphSet { | ||
GlyphSet::Single(src) | ||
} | ||
} | ||
|
||
impl FromIterator<GlyphId> for GlyphSet { | ||
fn from_iter<T: IntoIterator<Item = GlyphId>>(iter: T) -> Self { | ||
GlyphSet::Multiple(iter.into_iter().collect()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use std::path::PathBuf; | ||
|
||
use read_fonts::{types::Tag, ReadError}; | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
pub(crate) enum Error { | ||
#[error("could not read path '{path}': '{inner}'")] | ||
Load { | ||
path: PathBuf, | ||
inner: std::io::Error, | ||
}, | ||
#[error("could not create file '{path}': '{inner}'")] | ||
FileWrite { | ||
path: PathBuf, | ||
inner: std::io::Error, | ||
}, | ||
#[error("write error: '{0}'")] | ||
Write(#[from] std::io::Error), | ||
#[error("could not read font data: '{0}")] | ||
FontRead(ReadError), | ||
#[error("missing table '{0}'")] | ||
MissingTable(Tag), | ||
} |
Oops, something went wrong.