pub mod builder;
mod codepoints;
mod parse;
mod serialize;
#[cfg(test)]
mod tests;
use std::path::{Path, PathBuf};
#[cfg(feature = "kurbo")]
use crate::error::ConvertContourError;
#[cfg(feature = "druid")]
use druid::{Data, Lens};
use crate::error::{ErrorKind, GlifLoadError, GlifWriteError, StoreError};
use crate::name::Name;
use crate::names::NameList;
use crate::shared_types::PUBLIC_OBJECT_LIBS_KEY;
use crate::{Color, Guideline, Identifier, Line, Plist, WriteOptions};
pub use codepoints::Codepoints;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "druid", derive(Lens))]
pub struct Glyph {
#[cfg_attr(feature = "druid", lens(ignore))]
pub(crate) name: Name,
pub height: f64,
pub width: f64,
pub codepoints: Codepoints,
pub note: Option<String>,
pub guidelines: Vec<Guideline>,
pub anchors: Vec<Anchor>,
pub components: Vec<Component>,
pub contours: Vec<Contour>,
pub image: Option<Image>,
pub lib: Plist,
}
impl Glyph {
pub fn load(path: impl AsRef<Path>) -> Result<Self, GlifLoadError> {
let path = path.as_ref();
let names = NameList::default();
Glyph::load_with_names(path, &names)
}
pub(crate) fn load_with_names(path: &Path, names: &NameList) -> Result<Self, GlifLoadError> {
std::fs::read(path)
.map_err(GlifLoadError::Io)
.and_then(|data| parse::GlifParser::from_xml(&data, Some(names)))
}
#[doc(hidden)]
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), GlifWriteError> {
let path = path.as_ref();
let opts = WriteOptions::default();
self.save_with_options(path, &opts)
}
pub(crate) fn save_with_options(
&self,
path: &Path,
opts: &WriteOptions,
) -> Result<(), GlifWriteError> {
if self.lib.contains_key(PUBLIC_OBJECT_LIBS_KEY) {
return Err(GlifWriteError::PreexistingPublicObjectLibsKey);
}
let data = self.encode_xml_with_options(opts)?;
std::fs::write(path, &data).map_err(GlifWriteError::Io)?;
Ok(())
}
pub fn new(name: &str) -> Self {
Glyph::new_impl(Name::new_raw(name))
}
pub(crate) fn new_impl(name: Name) -> Self {
Glyph {
name,
height: 0.0,
width: 0.0,
codepoints: Default::default(),
note: None,
guidelines: Vec::new(),
anchors: Vec::new(),
components: Vec::new(),
contours: Vec::new(),
image: None,
lib: Plist::new(),
}
}
pub fn name(&self) -> &Name {
&self.name
}
pub fn has_component(&self) -> bool {
!self.components.is_empty()
}
pub fn component_count(&self) -> usize {
self.components.len()
}
pub fn has_component_with_base(&self, basename: &str) -> bool {
self.components.iter().any(|x| *x.base == *basename)
}
pub fn get_components_with_base<'b, 'a: 'b>(
&'a self,
basename: &'b str,
) -> impl Iterator<Item = &'a Component> + 'b {
self.components.iter().filter(move |x| *x.base == *basename)
}
fn load_object_libs(&mut self) -> Result<(), GlifLoadError> {
macro_rules! transfer_lib {
($object:expr, $object_libs:expr) => {
if let Some(id) = $object.identifier().map(|v| v.as_str()) {
if let Some(lib) = $object_libs.remove(id) {
let lib = lib
.into_dictionary()
.ok_or(GlifLoadError::ObjectLibMustBeDictionary(id.into()))?;
$object.replace_lib(lib);
}
}
};
}
let mut object_libs = match self.lib.remove(PUBLIC_OBJECT_LIBS_KEY) {
Some(lib) => {
lib.into_dictionary().ok_or(GlifLoadError::PublicObjectLibsMustBeDictionary)?
}
None => return Ok(()),
};
for anchor in &mut self.anchors {
transfer_lib!(anchor, object_libs);
}
for guideline in &mut self.guidelines {
transfer_lib!(guideline, object_libs);
}
for contour in &mut self.contours {
transfer_lib!(contour, object_libs);
for point in &mut contour.points {
transfer_lib!(point, object_libs);
}
}
for component in &mut self.components {
transfer_lib!(component, object_libs);
}
Ok(())
}
fn dump_object_libs(&self) -> Plist {
let mut object_libs = Plist::default();
let mut dump_lib = |id: Option<&Identifier>, lib: &Plist| {
let id = id.map(|id| id.as_str().to_string());
object_libs.insert(id.unwrap(), plist::Value::Dictionary(lib.clone()));
};
for anchor in &self.anchors {
if let Some(lib) = anchor.lib() {
dump_lib(anchor.identifier(), lib);
}
}
for guideline in &self.guidelines {
if let Some(lib) = guideline.lib() {
dump_lib(guideline.identifier(), lib);
}
}
for contour in &self.contours {
if let Some(lib) = contour.lib() {
dump_lib(contour.identifier(), lib);
}
for point in &contour.points {
if let Some(lib) = point.lib() {
dump_lib(point.identifier(), lib);
}
}
}
for component in &self.components {
if let Some(lib) = component.lib() {
dump_lib(component.identifier(), lib);
}
}
object_libs
}
}
#[cfg(feature = "druid")]
impl Data for Glyph {
fn same(&self, other: &Glyph) -> bool {
self.name.same(&other.name)
&& self.height.same(&other.height)
&& self.width.same(&other.width)
&& self.codepoints == other.codepoints
&& self.note == other.note
&& self.guidelines == other.guidelines
&& self.anchors == other.anchors
&& self.components == other.components
&& self.contours == other.contours
&& self.image == other.image
&& self.lib == other.lib
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Anchor {
pub x: f64,
pub y: f64,
pub name: Option<Name>,
pub color: Option<Color>,
identifier: Option<Identifier>,
lib: Option<Plist>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Component {
pub base: Name,
pub transform: AffineTransform,
identifier: Option<Identifier>,
lib: Option<Plist>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Contour {
pub points: Vec<ContourPoint>,
identifier: Option<Identifier>,
lib: Option<Plist>,
}
impl Contour {
pub fn is_closed(&self) -> bool {
self.points.first().map_or(true, |v| v.typ != PointType::Move)
}
#[cfg(feature = "kurbo")]
pub fn to_kurbo(&self) -> Result<kurbo::BezPath, ConvertContourError> {
let mut path = kurbo::BezPath::new();
let mut offs = std::collections::VecDeque::new();
let mut points = if self.is_closed() {
let rotate = self
.points
.iter()
.rev()
.position(|pt| pt.typ != PointType::OffCurve)
.map(|idx| self.points.len() - 1 - idx);
self.points.iter().cycle().skip(rotate.unwrap_or(0)).take(self.points.len() + 1)
} else {
self.points.iter().cycle().skip(0).take(self.points.len())
};
if let Some(start) = points.next() {
path.move_to(start.to_kurbo());
}
for pt in points {
let kurbo_point = pt.to_kurbo();
match pt.typ {
PointType::Move => path.move_to(kurbo_point),
PointType::Line => path.line_to(kurbo_point),
PointType::OffCurve => offs.push_back(kurbo_point),
PointType::Curve => {
match offs.make_contiguous() {
[] => return Err(ConvertContourError::new(ErrorKind::BadPoint)),
[p1] => path.quad_to(*p1, kurbo_point),
[p1, p2] => path.curve_to(*p1, *p2, kurbo_point),
_ => return Err(ConvertContourError::new(ErrorKind::TooManyOffCurves)),
};
offs.clear();
}
PointType::QCurve => {
while let Some(pt) = offs.pop_front() {
if let Some(next) = offs.front() {
let implied_point = pt.midpoint(*next);
path.quad_to(pt, implied_point);
} else {
path.quad_to(pt, kurbo_point);
}
}
offs.clear();
}
}
}
Ok(path)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ContourPoint {
pub x: f64,
pub y: f64,
pub typ: PointType,
pub smooth: bool,
pub name: Option<Name>,
identifier: Option<Identifier>,
lib: Option<Plist>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PointType {
Move,
Line,
OffCurve,
Curve,
QCurve,
}
impl std::str::FromStr for PointType {
type Err = ErrorKind;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"move" => Ok(PointType::Move),
"line" => Ok(PointType::Line),
"offcurve" => Ok(PointType::OffCurve),
"curve" => Ok(PointType::Curve),
"qcurve" => Ok(PointType::QCurve),
_other => Err(ErrorKind::UnknownPointType),
}
}
}
impl std::fmt::Display for PointType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PointType::Move => write!(f, "move"),
PointType::Line => write!(f, "line"),
PointType::OffCurve => write!(f, "offcurve"),
PointType::Curve => write!(f, "curve"),
PointType::QCurve => write!(f, "qcurve"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "druid", derive(Data))]
pub struct AffineTransform {
pub x_scale: f64,
pub xy_scale: f64,
pub yx_scale: f64,
pub y_scale: f64,
pub x_offset: f64,
pub y_offset: f64,
}
impl Anchor {
pub fn new(
x: f64,
y: f64,
name: Option<Name>,
color: Option<Color>,
identifier: Option<Identifier>,
lib: Option<Plist>,
) -> Self {
let mut this = Self { x, y, name, color, identifier: None, lib: None };
if let Some(id) = identifier {
this.replace_identifier(id);
}
if let Some(lib) = lib {
this.replace_lib(lib);
}
this
}
pub fn lib(&self) -> Option<&Plist> {
self.lib.as_ref()
}
pub fn lib_mut(&mut self) -> Option<&mut Plist> {
self.lib.as_mut()
}
pub fn replace_lib(&mut self, lib: Plist) -> Option<Plist> {
if self.identifier.is_none() {
self.identifier.replace(Identifier::from_uuidv4());
}
self.lib.replace(lib)
}
pub fn take_lib(&mut self) -> Option<Plist> {
self.lib.take()
}
pub fn identifier(&self) -> Option<&Identifier> {
self.identifier.as_ref()
}
pub fn replace_identifier(&mut self, id: Identifier) -> Option<Identifier> {
self.identifier.replace(id)
}
}
impl Contour {
pub fn new(
points: Vec<ContourPoint>,
identifier: Option<Identifier>,
lib: Option<Plist>,
) -> Self {
let mut this = Self { points, identifier: None, lib: None };
if let Some(id) = identifier {
this.replace_identifier(id);
}
if let Some(lib) = lib {
this.replace_lib(lib);
}
this
}
pub fn lib(&self) -> Option<&Plist> {
self.lib.as_ref()
}
pub fn lib_mut(&mut self) -> Option<&mut Plist> {
self.lib.as_mut()
}
pub fn replace_lib(&mut self, lib: Plist) -> Option<Plist> {
if self.identifier.is_none() {
self.identifier.replace(Identifier::from_uuidv4());
}
self.lib.replace(lib)
}
pub fn take_lib(&mut self) -> Option<Plist> {
self.lib.take()
}
pub fn identifier(&self) -> Option<&Identifier> {
self.identifier.as_ref()
}
pub fn replace_identifier(&mut self, id: Identifier) -> Option<Identifier> {
self.identifier.replace(id)
}
}
impl ContourPoint {
pub fn new(
x: f64,
y: f64,
typ: PointType,
smooth: bool,
name: Option<Name>,
identifier: Option<Identifier>,
lib: Option<Plist>,
) -> Self {
let mut this = Self { x, y, typ, smooth, name, identifier: None, lib: None };
if let Some(id) = identifier {
this.replace_identifier(id);
}
if let Some(lib) = lib {
this.replace_lib(lib);
}
this
}
pub fn lib(&self) -> Option<&Plist> {
self.lib.as_ref()
}
pub fn lib_mut(&mut self) -> Option<&mut Plist> {
self.lib.as_mut()
}
pub fn replace_lib(&mut self, lib: Plist) -> Option<Plist> {
if self.identifier.is_none() {
self.identifier.replace(Identifier::from_uuidv4());
}
self.lib.replace(lib)
}
pub fn take_lib(&mut self) -> Option<Plist> {
self.lib.take()
}
pub fn identifier(&self) -> Option<&Identifier> {
self.identifier.as_ref()
}
pub fn replace_identifier(&mut self, id: Identifier) -> Option<Identifier> {
self.identifier.replace(id)
}
#[cfg(feature = "kurbo")]
pub fn to_kurbo(&self) -> kurbo::Point {
kurbo::Point::new(self.x as f64, self.y as f64)
}
pub fn transform(&mut self, transform: AffineTransform) {
let new_x = transform.x_scale * self.x + transform.yx_scale * self.y + transform.x_offset;
let new_y = transform.xy_scale * self.x + transform.y_scale * self.y + transform.y_offset;
self.x = new_x;
self.y = new_y;
}
}
impl Component {
pub fn new(
base: Name,
transform: AffineTransform,
identifier: Option<Identifier>,
lib: Option<Plist>,
) -> Self {
let mut this = Self { base, transform, identifier: None, lib: None };
if let Some(id) = identifier {
this.replace_identifier(id);
}
if let Some(lib) = lib {
this.replace_lib(lib);
}
this
}
pub fn lib(&self) -> Option<&Plist> {
self.lib.as_ref()
}
pub fn lib_mut(&mut self) -> Option<&mut Plist> {
self.lib.as_mut()
}
pub fn replace_lib(&mut self, lib: Plist) -> Option<Plist> {
if self.identifier.is_none() {
self.identifier.replace(Identifier::from_uuidv4());
}
self.lib.replace(lib)
}
pub fn take_lib(&mut self) -> Option<Plist> {
self.lib.take()
}
pub fn identifier(&self) -> Option<&Identifier> {
self.identifier.as_ref()
}
pub fn replace_identifier(&mut self, id: Identifier) -> Option<Identifier> {
self.identifier.replace(id)
}
}
impl AffineTransform {
fn identity() -> Self {
AffineTransform {
x_scale: 1.0,
xy_scale: 0.,
yx_scale: 0.,
y_scale: 1.0,
x_offset: 0.,
y_offset: 0.,
}
}
}
impl std::default::Default for AffineTransform {
fn default() -> Self {
Self::identity()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Image {
file_name: PathBuf,
pub color: Option<Color>,
pub transform: AffineTransform,
}
impl Image {
pub fn new(
file_name: PathBuf,
color: Option<Color>,
transform: AffineTransform,
) -> Result<Self, StoreError> {
if file_name.as_os_str().is_empty() {
return Err(StoreError::EmptyPath);
}
if file_name.is_absolute() {
return Err(StoreError::PathIsAbsolute);
}
if file_name.parent().map_or(false, |p| !p.as_os_str().is_empty()) {
return Err(StoreError::Subdir);
}
Ok(Self { file_name, color, transform })
}
pub fn file_name(&self) -> &Path {
self.file_name.as_path()
}
}
#[cfg(feature = "kurbo")]
impl From<AffineTransform> for kurbo::Affine {
fn from(src: AffineTransform) -> kurbo::Affine {
kurbo::Affine::new([
src.x_scale,
src.xy_scale,
src.yx_scale,
src.y_scale,
src.x_offset,
src.y_offset,
])
}
}
#[cfg(feature = "kurbo")]
impl From<kurbo::Affine> for AffineTransform {
fn from(src: kurbo::Affine) -> AffineTransform {
let coeffs = src.as_coeffs();
AffineTransform {
x_scale: coeffs[0],
xy_scale: coeffs[1],
yx_scale: coeffs[2],
y_scale: coeffs[3],
x_offset: coeffs[4],
y_offset: coeffs[5],
}
}
}
#[cfg(feature = "druid")]
impl From<druid::piet::Color> for Color {
fn from(src: druid::piet::Color) -> Color {
let rgba = src.as_rgba_u32();
let r = ((rgba >> 24) & 0xff) as f64 / 255.0;
let g = ((rgba >> 16) & 0xff) as f64 / 255.0;
let b = ((rgba >> 8) & 0xff) as f64 / 255.0;
let a = (rgba & 0xff) as f64 / 255.0;
assert!((0.0..=1.0).contains(&b), "b: {}, raw {}", b, (rgba & (0xff << 8)));
Color::new(
r.max(0.0).min(1.0),
g.max(0.0).min(1.0),
b.max(0.0).min(1.0),
a.max(0.0).min(1.0),
)
.unwrap()
}
}
#[cfg(feature = "druid")]
impl From<Color> for druid::piet::Color {
fn from(src: Color) -> druid::piet::Color {
let (red, green, blue, alpha) = src.channels();
druid::piet::Color::rgba(red, green, blue, alpha)
}
}