mod xml;
pub use self::xml::FromXML;
use std::convert::TryInto;
use std::path;
use std::rc::Rc;
use integer_or_float::IntegerOrFloat;
use super::Glif;
#[cfg(feature = "glifserde")]
use super::Lib;
use crate::error::GlifParserError::{self, GlifInputError};
use crate::component::GlifComponent;
use crate::guideline::Guideline;
use crate::outline::{GlifContour, GlifOutline, Outline};
use crate::pedantry::Pedantry;
use crate::point::{GlifPoint, PointData, PointType};
use crate::point::IsValid as _;
use crate::string::GlifStringLenOne;
use crate::anchor::{Anchor, GlifAnchor};
#[cfg(feature = "glifimage")]
use crate::image::GlifImage;
macro_rules! input_error {
($str:expr) => {
GlifInputError($str.to_string())
}
}
macro_rules! load_matrix_and_identifier {
($xml_el:ident, $struct:ident, ($($attr:ident),+)) => {
$(
let maybe_err = $xml_el.attributes.get(stringify!($attr)).map(|e| -> Result<(),GlifParserError> {
let v = e.as_str().try_into().or(Err(input_error!(concat!("Matrix member ", stringify!($attr), " not float"))))?;
$struct.$attr = v;
Ok(())
});
if let Some(Err(e)) = maybe_err { Err(e)?; };
)+
$xml_el.attributes.get("identifier").map(|e|{ $struct.identifier = Some(e.clone()); });
}
}
use std::fs;
pub fn read_ufo_glif_from_filename<F: AsRef<path::Path> + Clone, PD: PointData>(filename: F) -> Result<Glif<PD>, GlifParserError> {
read_ufo_glif_from_filename_pedantic(filename, Pedantry::default())
}
pub fn read_ufo_glif_from_filename_pedantic<F: AsRef<path::Path> + Clone, PD: PointData>(filename: F, pedantry: Pedantry) -> Result<Glif<PD>, GlifParserError> {
let glifxml = match fs::read_to_string(&filename) {
Ok(s) => s,
Err(ioe) => Err(GlifParserError::GlifFileIoError(Some(Rc::new(ioe))))?
};
let mut glif: Glif<PD> = read_ufo_glif_pedantic(&glifxml, pedantry)?;
let filenamepb = filename.as_ref().to_path_buf();
for component in glif.components.vec.iter_mut() {
component.set_file_name(&filenamepb);
}
glif.filename = Some(filenamepb);
Ok(glif)
}
pub fn read_ufo_glif<PD: PointData>(glif: &str) -> Result<Glif<PD>, GlifParserError> {
read_ufo_glif_pedantic(glif, Pedantry::default())
}
pub fn read_ufo_glif_pedantic<PD: PointData>(glif: &str, pedantry: Pedantry) -> Result<Glif<PD>, GlifParserError> {
let mut glif = xmltree::Element::parse(glif.as_bytes())?;
let mut ret = Glif::new();
if glif.name != "glyph" {
return Err(input_error!("Root element not <glyph>"))
}
if glif.attributes.get("format").ok_or(input_error!("no format in <glyph>"))? != "2" {
return Err(input_error!("<glyph> format not 2"))
}
ret.name = glif
.attributes
.get("name")
.ok_or(input_error!("<glyph> has no name"))?
.clone();
ret.components.root = ret.name.clone();
let advance = glif
.take_child("advance");
ret.width = if let Some(a) = advance {
let width = a.attributes
.get("width")
.ok_or(input_error!("<advance> has no width"))?;
let widthc = width.parse::<u64>();
match widthc {
Err(e) => if let Ok((f, false)) = width.parse::<f32>().map(|f|(f, f.is_subnormal())) {
const FLOATPOINTWARNING: &str = "Floating point value given as <advance> width — OpenType `hmtx` / `vmtx` will truncate it";
if !pedantry.should_mend() {
Err(input_error!(FLOATPOINTWARNING))?
}
log::warn!("{}, so we do too!", FLOATPOINTWARNING);
Some(f as u64)
} else {
log::trace!("<advance> parsing as int rose {:?}", e);
Err(input_error!("<advance> width neither int nor downgradable (not subnormal) float!"))?
},
Ok(i) => Some(i)
}
} else {
None
};
let mut unicodes = vec![];
while let Some(u) = glif.take_child("unicode") {
let unicodehex = u
.attributes
.get("hex")
.ok_or(input_error!("<unicode> has no hex"))?;
unicodes.push(
char::from_u32(
u32::from_str_radix(unicodehex, 16)
.or(Err(input_error!("<unicode> hex not int")))?
)
.ok_or(input_error!("<unicode> char conversion failed"))?,
);
}
ret.unicode = unicodes;
let mut anchors: Vec<Anchor<PD>> = Vec::new();
while let Some(anchor_el) = glif.take_child("anchor") {
let mut anchor = GlifAnchor::default();
anchor.x = anchor_el
.attributes
.get("x")
.ok_or(input_error!("<anchor> missing x"))?
.parse()
.or(Err(input_error!("<anchor> x not integer/float")))?;
anchor.y = anchor_el
.attributes
.get("y")
.ok_or(input_error!("<anchor> missing y"))?
.parse()
.or(Err(input_error!("<anchor> y not integer/float")))?;
anchor.class = anchor_el
.attributes
.get("name")
.map(|a|GlifStringLenOne::try_from(a.clone()))
.map_or(None, |r|r.ok());
anchors.push(Anchor::from_glif(&anchor, pedantry)?);
}
ret.anchors = anchors;
#[cfg(feature = "glifimage")] {
let mut images: Vec<GlifImage> = Vec::new();
while let Some(image_el) = glif.take_child("image") {
let filename = path::PathBuf::from(image_el
.attributes
.get("fileName")
.ok_or(input_error!("<image> missing fileName"))?);
let mut gimage = GlifImage::from_filename(filename)?;
load_matrix_and_identifier!(image_el, gimage, (xScale, xyScale, yxScale, yScale, xOffset, yOffset));
if let Some(color) = image_el.attributes.get("color") {
gimage.color = Some(color.parse()?);
}
images.push(gimage);
}
ret.images = images;
}
let mut guidelines: Vec<Guideline<PD>> = Vec::new();
while let Some(guideline_el) = glif.take_child("guideline") {
let gx = guideline_el
.attributes
.get("x")
.ok_or(input_error!("<guideline> missing x"))?
.parse()
.or(Err(input_error!("<guideline> x not float")))?;
let gy = guideline_el
.attributes
.get("y")
.ok_or(input_error!("<guideline> missing y"))?
.parse()
.or(Err(input_error!("<guideline> x not float")))?;
let angle: IntegerOrFloat = guideline_el
.attributes
.get("angle")
.ok_or(input_error!("<guideline> missing angle"))?
.as_str()
.try_into()
.or(Err(input_error!("<guideline> angle not float")))?;
let mut guideline = Guideline::from_x_y_angle(gx, gy, angle);
if let Some(color) = guideline_el.attributes.get("color") {
guideline.color = Some(color.parse()?);
}
guideline.name = guideline_el.attributes.get("name").map(|n|n.clone());
guideline.identifier = guideline_el.attributes.get("identifier").map(|i|i.clone());
guidelines.push(guideline);
}
ret.guidelines = guidelines;
if let Some(note_el) = glif.take_child("note") {
note_el.get_text().map(|t|ret.note=Some(t.into_owned()));
}
let mut goutline: GlifOutline = GlifOutline::new();
let outline_el = glif.take_child("outline");
if let Some(mut outline_elu) = outline_el {
while let Some(mut contour_el) = outline_elu.take_child("contour") {
let mut gcontour: GlifContour = Vec::new();
while let Some(point_el) = contour_el.take_child("point") {
let mut gpoint = GlifPoint::new();
gpoint.x = point_el
.attributes
.get("x")
.ok_or(input_error!("<point> missing x"))?
.parse()
.or(Err(input_error!("<point> x not float")))?;
gpoint.y = point_el
.attributes
.get("y")
.ok_or(input_error!("<point> missing y"))?
.parse()
.or(Err(input_error!("<point> y not float")))?;
match point_el.attributes.get("name") {
Some(n) => gpoint.name = Some(n.clone()),
None => {}
}
gpoint.ptype = point_el.attributes.get("type").as_ref().map(|s| s.as_str()).unwrap_or("offcurve").into();
match point_el.attributes.get("smooth") {
Some(s) => if s == "yes" {
if gpoint.ptype != PointType::OffCurve {
gpoint.smooth = true;
} else {
log::error!("Ignoring illogical `smooth=yes` on offcurve point");
}
},
_ => {}
}
if gpoint.ptype.is_valid() {
gcontour.push(gpoint);
} else {
Err(GlifInputError(format!("Shouldn't write <point type={}> to UFO .glif!", gpoint.ptype)))?;
}
}
if gcontour.len() > 0 {
goutline.push(gcontour);
}
}
while let Some(component_el) = outline_elu.take_child("component") {
let mut gcomponent = GlifComponent::new();
load_matrix_and_identifier!(component_el, gcomponent, (xScale, xyScale, yxScale, yScale, xOffset, yOffset));
gcomponent.base = component_el.attributes.get("base").ok_or(input_error!("<component> missing base"))?.clone();
ret.components.vec.push(gcomponent);
}
}
#[cfg(feature = "glifserde")]
if let Some(mut lib) = glif.take_child("lib") {
let mut plist_temp: Vec<u8> = vec![];
lib.name = String::from("plist");
match lib.write(&mut plist_temp).map(|()|plist::from_bytes(&plist_temp)) {
Ok(Ok(lib_p)) => ret.lib = Lib::Plist(lib_p),
Err(e) => {
log::error!("Failed to serialize .glif lib as XML? Error: {:?}", e);
ret.lib = Lib::Xml(lib)
},
Ok(Err(e)) => {
log::error!("Failed to deserialize .glif lib XML as plist? Error: {:?}", e);
}
}
}
#[cfg(not(feature = "glifserde"))]
if let Some(_) = glif.take_child("lib") {
log::warn!("Without glifserde, cannot decode plist!")
}
let outline: Outline<PD> = goutline.into();
if outline.len() > 0 || ret.components.vec.len() > 0 {
ret.outline = Some(outline);
}
Ok(ret)
}