Skip to content

Commit 0a79aa7

Browse files
committed
Add components
1 parent 08643ee commit 0a79aa7

File tree

18 files changed

+1258
-683
lines changed

18 files changed

+1258
-683
lines changed

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
name = "glifparser"
33
version = "0.0.0"
44
authors = ["Fredrick Brennan <[email protected]>"]
5+
edition = "2018"
56

67
[dependencies]
7-
xmltree = "0.10.1"
8-
log = "0.4.11"
8+
xmltree = "0.10.3"
9+
log = "0.4"
10+
kurbo = "0.8"
11+
trees = "0.4"
12+
13+
# Our submodules
14+
integer_or_float = { git = "https://github.com/MFEK/integer_or_float.rlib" }

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ Skia-friendly points. A cubic B&eacute;zier spline has two handles, `a` and
88
with this. (A quadratic B&eacute;zier spline uses the same `Point` type but
99
will always have handle `b` set to `Handle::Colocated`.)
1010

11-
**NOTE**: This `.glif` parser is at the moment _unversioned_ (0.0.0); it has an
12-
unstable API. Don't use it in your own projects yet; if you insist, tell Cargo
13-
to use a `rev`. Right now it just panics instead of returning a `Result<Glif,
14-
E>` type and has no `Error` enum, which will be fixed in the final
15-
crates.io-friendly API.
11+
Another difference is that it supports glyph components more fully, and allows
12+
you to flatten the components to another Glif representing the outlines of all
13+
the components, plus the outlines in the original glyph.
14+
15+
Yet a third difference is the support for private libraries, stored in
16+
comments. This is for support of the `<MFEK>` comment.
17+
18+
Since this library considers .glif files as detached from .ufo files, its
19+
approach is much different from Norad's as well. This is because MFEKglif, the
20+
first software this was written for, is a detached UFO .glif editor.

src/anchor.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use std::fmt::Debug;
2+
3+
#[derive(Clone, Debug, PartialEq)]
4+
pub struct Anchor {
5+
pub x: f32,
6+
pub y: f32,
7+
pub class: String,
8+
pub r#type: AnchorType,
9+
}
10+
11+
#[derive(Debug, Copy, Clone, PartialEq)]
12+
pub enum AnchorType {
13+
Undefined,
14+
Mark,
15+
Base,
16+
MarkMark,
17+
MarkBase,
18+
} // Undefined used everywhere for now as getting type requires parsing OpenType features, which we will be using nom to do since I have experience w/it.
19+
20+
impl Anchor {
21+
pub fn new() -> Anchor {
22+
Anchor {
23+
x: 0.,
24+
y: 0.,
25+
r#type: AnchorType::Undefined,
26+
class: String::new(),
27+
}
28+
}
29+
}
30+

src/codepoint.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub trait Codepoint {
2+
fn display(&self) -> String;
3+
}
4+
5+
impl Codepoint for char {
6+
fn display(&self) -> String {
7+
format!("{:x}", *self as u32)
8+
}
9+
}

src/component.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
use crate::error::GlifParserError;
2+
use crate::glif::{self, Glif};
3+
use crate::point::{Handle, PointData, WhichHandle};
4+
use crate::outline::Outline;
5+
6+
use integer_or_float::IntegerOrFloat;
7+
8+
#[allow(non_snake_case)] // to match UFO spec https://unifiedfontobject.org/versions/ufo3/glyphs/glif/#component
9+
#[derive(Clone, Debug, PartialEq)]
10+
pub struct GlifComponent {
11+
pub base: String,
12+
pub xScale: IntegerOrFloat,
13+
pub xyScale: IntegerOrFloat,
14+
pub yxScale: IntegerOrFloat,
15+
pub yScale: IntegerOrFloat,
16+
pub xOffset: IntegerOrFloat,
17+
pub yOffset: IntegerOrFloat,
18+
pub identifier: Option<String>
19+
}
20+
21+
impl GlifComponent {
22+
pub fn new() -> Self {
23+
Self {
24+
base: String::new(),
25+
xScale: IntegerOrFloat::Integer(1),
26+
xyScale: IntegerOrFloat::Integer(0),
27+
yxScale: IntegerOrFloat::Integer(0),
28+
yScale: IntegerOrFloat::Integer(1),
29+
xOffset: IntegerOrFloat::Integer(0),
30+
yOffset: IntegerOrFloat::Integer(0),
31+
identifier: None
32+
}
33+
}
34+
}
35+
36+
type ComponentMatrix = [IntegerOrFloat; 6];
37+
38+
impl GlifComponent {
39+
fn matrix(&self) -> ComponentMatrix {
40+
[self.xScale, self.xyScale, self.yxScale, self.yScale, self.xOffset, self.yOffset]
41+
}
42+
}
43+
44+
trait FromComponentMatrix {
45+
fn from_component_matrix(cm: &ComponentMatrix) -> Self;
46+
}
47+
48+
use kurbo::Affine;
49+
impl FromComponentMatrix for Affine {
50+
fn from_component_matrix(cm: &ComponentMatrix) -> Self {
51+
Affine::new([cm[0].into(), cm[1].into(), cm[2].into(), cm[3].into(), cm[4].into(), cm[5].into()])
52+
}
53+
}
54+
55+
#[derive(Clone, Debug, PartialEq)]
56+
pub struct Component<PD: PointData> {
57+
pub glif: Glif<PD>,
58+
pub matrix: Affine
59+
}
60+
61+
impl<PD: PointData> Component<PD> {
62+
pub fn new() -> Self {
63+
Component {
64+
glif: Glif::new(),
65+
matrix: Affine::IDENTITY
66+
}
67+
}
68+
}
69+
70+
use std::fs;
71+
impl GlifComponent {
72+
pub fn to_component_of<PD: PointData>(&self, glif: &Glif<PD>) -> Result<Component<PD>, GlifParserError> {
73+
let gliffn = &glif.filename.as_ref().ok_or(GlifParserError::GlifFilenameNotSet(glif.name.clone()))?;
74+
75+
let mut ret = Component::new();
76+
ret.matrix = Affine::from_component_matrix(&self.matrix());
77+
ret.glif.name = self.base.clone();
78+
let mut retglifname = gliffn.to_path_buf();
79+
retglifname.set_file_name(ret.glif.name_to_filename());
80+
let component_xml = fs::read_to_string(&retglifname).unwrap();
81+
ret.glif.filename = Some(retglifname);
82+
let newglif: Glif<PD> = glif::read(&component_xml)?;
83+
ret.glif.components = newglif.components;
84+
ret.glif.anchors = newglif.anchors;
85+
ret.glif.outline = newglif.outline;
86+
Ok(ret)
87+
}
88+
89+
pub fn refers_to<PD: PointData>(&self, glif: &Glif<PD>) -> bool {
90+
self.base == glif.name
91+
}
92+
}
93+
94+
use kurbo::Point as KurboPoint;
95+
impl<PD: PointData> Glif<PD> {
96+
/// Flatten a UFO .glif with components.
97+
///
98+
/// Can fail if the .glif's components form an infinite loop.
99+
// How this works is we start at the bottom of the tree, take all of the Affine matrices which
100+
// describe the transformation of the glyph's points, and continuously apply them until we run
101+
// out of nodes of the tree. Finally, we set our outline to be the final transformed outline,
102+
// and consider ourselves as no longer being made up of components.
103+
pub fn flatten(mut self) -> Result<Self, GlifParserError> {
104+
let components_r: Result<Forest<Component<PD>>, _> = (&self).into();
105+
let components = components_r?;
106+
let mut final_outline: Outline<PD> = Outline::new();
107+
108+
for mut component in components {
109+
while let Some(last) = component.back_mut() {
110+
let mut matrices = vec![];
111+
matrices.push((*last).data().matrix);
112+
113+
// Climb the tree, building a Vec of matrices for this component
114+
let mut pt = last.parent();
115+
while let Some(parent) = pt {
116+
matrices.push(parent.data().matrix);
117+
pt = parent.parent();
118+
}
119+
120+
match (*last).data().glif.outline {
121+
Some(ref o) => {
122+
let mut to_transform = o.clone();
123+
for i in 0..to_transform.len() {
124+
for j in 0..to_transform[i].len() {
125+
let mut p = to_transform[i][j].clone();
126+
let kbp = matrices.iter().fold(KurboPoint::new(p.x as f64, p.y as f64), |p, m| *m * p);
127+
p.x = kbp.x as f32;
128+
p.y = kbp.y as f32;
129+
130+
if p.a != Handle::Colocated {
131+
let (ax, ay) = p.handle_or_colocated(WhichHandle::A, |f|f, |f|f);
132+
let kbpa = matrices.iter().fold(KurboPoint::new(ax as f64, ay as f64), |p, m| *m * p);
133+
p.a = Handle::At(kbpa.x as f32, kbpa.y as f32);
134+
}
135+
136+
if p.b != Handle::Colocated {
137+
let (bx, by) = p.handle_or_colocated(WhichHandle::B, |f|f, |f|f);
138+
let kbpb = matrices.iter().fold(KurboPoint::new(bx as f64, by as f64), |p, m| *m * p);
139+
p.b = Handle::At(kbpb.x as f32, kbpb.y as f32);
140+
}
141+
142+
to_transform[i][j] = p;
143+
}
144+
}
145+
final_outline.extend(to_transform);
146+
},
147+
None => {}
148+
}
149+
150+
component.pop_back();
151+
}
152+
}
153+
154+
self.outline = Some(final_outline);
155+
156+
// If we were to leave this here, then API consumers would potentially draw component outlines on top of components.
157+
self.components = vec![];
158+
159+
Ok(self)
160+
}
161+
}
162+
163+
use std::collections::HashSet;
164+
use trees::{Forest, Tree};
165+
// This impl builds up a forest of trees for a glyph's components. Imagine a hungarumlaut (˝).
166+
//
167+
// This character may be built of glyph components, as such:
168+
//
169+
// hungarumlaut
170+
// / \
171+
// / \
172+
// grave grave
173+
// | |
174+
// acute acute
175+
//
176+
// This function will give you a Forest of both of the sub-trees. (Forest<Component>). The elements
177+
// of a Forest are Tree<Component>. For safety reasons, this function cannot always return a
178+
// Forest, however. Sometimes, .glif files can be malformed, containing components which refer to
179+
// themselves, or to components higher up the tree. Therefore, the inner recursive function
180+
// `component_to_tree` receives a Vec of `uniques`, calculated for each sub-tree, and also a global
181+
// mutable `unique_found` flag, for the entire Forest.
182+
//
183+
// If a loop is found in the tree (for example, grave refers to grave), `unique_found` is set,
184+
// poisoning the function, returning an error. unique_found is (String, String) for error formatting;
185+
// however, should be considered basically equivalent to a boolean.
186+
impl<PD: PointData> From<&Glif<PD>> for Result<Forest<Component<PD>>, GlifParserError> {
187+
fn from(glif: &Glif<PD>) -> Self {
188+
let mut unique_found = None;
189+
190+
fn component_to_tree<PD: PointData>(component: Component<PD>, glif: &Glif<PD>, uniques: &mut HashSet<String>, unique_found: &mut Option<(String, String)>) -> Result<Tree<Component<PD>>, GlifParserError> {
191+
let mut tree = Tree::new(component.clone());
192+
for gc in component.glif.components.iter() {
193+
let component_inner = gc.to_component_of(glif)?;
194+
if uniques.contains(&gc.base) {
195+
return {
196+
*unique_found = Some((component.glif.name.clone(), gc.base.clone()));
197+
Ok(tree)
198+
}
199+
}
200+
uniques.insert(gc.base.clone());
201+
tree.push_back(component_to_tree(component_inner, glif, uniques, unique_found)?);
202+
}
203+
Ok(tree)
204+
}
205+
206+
let mut forest = Forest::new();
207+
let cs: Vec<_> = glif.components.iter().map(|gc| {
208+
let mut uniques = HashSet::new();
209+
uniques.insert(glif.name.clone());
210+
uniques.insert(gc.base.clone());
211+
component_to_tree(gc.to_component_of(glif).unwrap(), glif, &mut uniques, &mut unique_found).unwrap()
212+
}).collect();
213+
214+
for c in cs {
215+
forest.push_back(c);
216+
}
217+
218+
match unique_found {
219+
Some((base, unique)) => {Err(GlifParserError::GlifComponentsCyclical(format!("in glif {}, {} refers to {}", &glif.name, base, unique)))},
220+
None => Ok(forest)
221+
}
222+
}
223+
}

src/error.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use std::fmt::{Formatter, Display};
2+
use std::error::Error;
3+
use std::string;
4+
5+
use xmltree::{ParseError, Error as XMLTreeError};
6+
7+
#[derive(Debug, Clone)]
8+
pub enum GlifParserError {
9+
/// Glif filename not set
10+
GlifFilenameNotSet(String),
11+
/// Glif filename doesn't match name in XML
12+
GlifFilenameInsane(String),
13+
/// Components of the glyph form a loop
14+
GlifComponentsCyclical(String),
15+
/// Glif isn't UTF8
16+
GlifNotUtf8(String),
17+
/// The XML making up the glif is invalid
18+
XmlParseError(String),
19+
/// Failures when writing glif XML
20+
XmlWriteError(String),
21+
/// The XML is valid, but doesn't meet the UFO .glif spec
22+
GlifInputError(String),
23+
}
24+
25+
impl Display for GlifParserError {
26+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
27+
write!(f, "glifparser error: {}", match self {
28+
Self::GlifFilenameNotSet(s) => {
29+
format!("Glyph filename not set: {}", &s)
30+
},
31+
Self::GlifFilenameInsane(s) => {
32+
format!("Glyph filename not sane: {}", &s)
33+
},
34+
Self::GlifNotUtf8(_) => {
35+
format!("Glyph not utf-8")
36+
},
37+
Self::GlifComponentsCyclical(s) => {
38+
format!("Glyph components are cyclical: {}", &s)
39+
},
40+
Self::XmlParseError(s) | Self::XmlWriteError(s) => {
41+
format!("XML error: {}", &s)
42+
},
43+
Self::GlifInputError(s) => {
44+
format!("Glif format spec error: {}", &s)
45+
},
46+
})
47+
}
48+
}
49+
50+
// the parsing function in read_ufo_glif can only return this error type
51+
impl From<ParseError> for GlifParserError {
52+
fn from(e: ParseError) -> Self {
53+
Self::XmlParseError(format!("{}", e))
54+
}
55+
}
56+
57+
// . . . therefore it's OK to consider this a write-time error type
58+
impl From<XMLTreeError> for GlifParserError {
59+
fn from(e: XMLTreeError) -> Self {
60+
Self::XmlWriteError(format!("{}", e))
61+
}
62+
}
63+
64+
impl From<string::FromUtf8Error> for GlifParserError {
65+
fn from(_: string::FromUtf8Error) -> Self {
66+
Self::GlifNotUtf8("".to_string())
67+
}
68+
}
69+
70+
71+
impl Error for GlifParserError {}

0 commit comments

Comments
 (0)