Skip to content

Commit

Permalink
Update to quick-xml 0.27
Browse files Browse the repository at this point in the history
This was quite involved, as 0.27 reworks a bunch of details around how
serde is implemented.
  • Loading branch information
cmyr committed Mar 9, 2023
1 parent 53f4d65 commit 53c603c
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 122 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ uuid = { version = "1.2", features = ["v4"] }
serde = { version = "1.0", features = ["rc", "derive"] }
serde_derive = "1.0"
serde_repr = "0.1"
quick-xml = { version = "0.26.0", features = ["serialize"] }
quick-xml = { version = "0.27.0", features = ["serialize"] }
rayon = { version = "1.3.0", optional = true }
kurbo = { version = "0.9.0", optional = true }
thiserror = "1.0"
Expand Down
221 changes: 100 additions & 121 deletions src/designspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,218 +6,151 @@ use std::{fs::File, io::BufReader, path::Path};

use crate::error::DesignSpaceLoadError;

/// A [designspace]].
/// A [designspace].
///
/// [designspace]: https://fonttools.readthedocs.io/en/latest/designspaceLib/index.html
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(from = "DesignSpaceDocumentXmlRepr")]
#[serde(rename = "designspace")]
pub struct DesignSpaceDocument {
/// Design space format version.
#[serde(rename = "@format")]
pub format: f32,
/// One or more axes.
#[serde(deserialize_with = "serde_impls::deserialize_axes")]
pub axes: Vec<Axis>,
/// One or more sources.
#[serde(deserialize_with = "serde_impls::deserialize_sources")]
pub sources: Vec<Source>,
/// One or more instances.
#[serde(deserialize_with = "serde_impls::deserialize_instances")]
pub instances: Vec<Instance>,
}

/// <https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#overview>
#[derive(Deserialize)]
#[serde(rename = "designspace")]
struct DesignSpaceDocumentXmlRepr {
pub format: f32,
pub axes: AxesXmlRepr,
pub sources: SourcesXmlRepr,
pub instances: InstancesXmlRepr,
}

impl From<DesignSpaceDocumentXmlRepr> for DesignSpaceDocument {
fn from(xml_form: DesignSpaceDocumentXmlRepr) -> Self {
DesignSpaceDocument {
format: xml_form.format,
axes: xml_form.axes.axis,
sources: xml_form.sources.source,
instances: xml_form.instances.instance,
}
}
}

/// <https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#axes-element>
#[derive(Deserialize)]
#[serde(rename = "axes")]
pub struct AxesXmlRepr {
/// One or more axis definitions.
pub axis: Vec<Axis>,
}

/// An [axis]].
/// An [axis].
///
/// [axis]: https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#axis-element
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(rename = "axis")]
pub struct Axis {
/// Name of the axis that is used in the location elements.
#[serde(rename = "@name")]
pub name: String,
/// 4 letters. Some axis tags are registered in the OpenType Specification.
#[serde(rename = "@tag")]
pub tag: String,
/// The default value for this axis, in user space coordinates.
#[serde(rename = "@default")]
pub default: f32,
/// Records whether this axis needs to be hidden in interfaces.
#[serde(default)]
#[serde(rename = "@hidden")]
pub hidden: bool,
/// The minimum value for a continuous axis, in user space coordinates.
#[serde(rename = "@minimum")]
pub minimum: Option<f32>,
/// The maximum value for a continuous axis, in user space coordinates.
#[serde(rename = "@minimum")]
pub maximum: Option<f32>,
/// The possible values for a discrete axis, in user space coordinates.
#[serde(rename = "@values")]
pub values: Option<Vec<f32>>,
/// Mapping between user space coordinates and design space coordinates.
pub map: Option<Vec<AxisMapping>>,
#[serde(default)]
pub map: Vec<AxisMapping>,
}

/// Maps one input value (user space coord) to one output value (design space coord).
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(rename = "map")]
pub struct AxisMapping {
/// user space coordinate
#[serde(rename = "@input")]
pub input: f32,
/// designspace coordinate
#[serde(rename = "@output")]
pub output: f32,
}

/// <https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#sources-element>
#[derive(Deserialize)]
#[serde(rename = "sources")]
struct SourcesXmlRepr {
/// One or more sources.
pub source: Vec<Source>,
}

/// A [source]].
/// A [source].
///
/// [source]: https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#id25
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(from = "SourceXmlRepr")]
#[serde(rename = "source")]
pub struct Source {
/// The family name of the source font.
#[serde(rename = "@familyname")]
pub familyname: Option<String>,
/// The style name of the source font.
#[serde(rename = "@stylename")]
pub stylename: Option<String>,
/// A unique name that can be used to identify this font if it needs to be referenced elsewhere.
#[serde(rename = "@name")]
pub name: String,
/// A path to the source file, relative to the root path of this document. The path can be at the same level as the document or lower.
/// A path to the source file, relative to the root path of this document.
///
/// The path can be at the same level as the document or lower.
#[serde(rename = "@filename")]
pub filename: String,
/// The name of the layer in the source file. If no layer attribute is given assume the foreground layer should be used.
/// The name of the layer in the source file.
///
/// If no layer attribute is given assume the foreground layer should be used.
#[serde(rename = "@layer")]
pub layer: Option<String>,
/// Location in designspace coordinates.
#[serde(deserialize_with = "serde_impls::deserialize_location")]
pub location: Vec<Dimension>,
}

/// <https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#source-element>
#[derive(Deserialize)]
#[serde(rename = "source")]
struct SourceXmlRepr {
pub familyname: Option<String>,
pub stylename: Option<String>,
pub name: String,
pub filename: String,
pub layer: Option<String>,
pub location: LocationXmlRepr,
}

impl From<SourceXmlRepr> for Source {
fn from(xml_form: SourceXmlRepr) -> Self {
Source {
familyname: xml_form.familyname,
stylename: xml_form.stylename,
name: xml_form.name,
filename: xml_form.filename,
layer: xml_form.layer,
location: xml_form.location.dimension,
}
}
}

/// <https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#instances-element>
#[derive(Deserialize)]
#[serde(rename = "instances")]
struct InstancesXmlRepr {
/// One or more instances located somewhere in designspace.
pub instance: Vec<Instance>,
}

/// An [instance]].
/// An [instance].
///
/// [instance]: https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#instance-element
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(from = "InstanceXmlRepr")]
#[serde(rename = "instance")]
pub struct Instance {
// per @anthrotype, contrary to spec, filename, familyname and stylename are optional
/// The family name of the instance font. Corresponds with font.info.familyName
#[serde(rename = "@familyname")]
pub familyname: Option<String>,
/// The style name of the instance font. Corresponds with font.info.styleName
#[serde(rename = "@stylename")]
pub stylename: Option<String>,
/// A unique name that can be used to identify this font if it needs to be referenced elsewhere.
#[serde(rename = "@name")]
pub name: String,
/// A path to the instance file, relative to the root path of this document. The path can be at the same level as the document or lower.
#[serde(rename = "@filename")]
pub filename: Option<String>,
/// Corresponds with font.info.postscriptFontName
#[serde(rename = "@postscriptfontname")]
pub postscriptfontname: Option<String>,
/// Corresponds with styleMapFamilyName
#[serde(rename = "@stylemapfamilyname")]
pub stylemapfamilyname: Option<String>,
/// Corresponds with styleMapStyleName
#[serde(rename = "@stylemapstylename")]
pub stylemapstylename: Option<String>,
/// Location in designspace.
#[serde(deserialize_with = "serde_impls::deserialize_location")]
pub location: Vec<Dimension>,
}

/// <https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#instance-element>
#[derive(Deserialize)]
struct InstanceXmlRepr {
pub familyname: Option<String>,
pub stylename: Option<String>,
pub name: String,
pub filename: Option<String>,
pub postscriptfontname: Option<String>,
pub stylemapfamilyname: Option<String>,
pub stylemapstylename: Option<String>,
pub location: LocationXmlRepr,
}

impl From<InstanceXmlRepr> for Instance {
fn from(instance_xml: InstanceXmlRepr) -> Self {
Instance {
familyname: instance_xml.familyname,
stylename: instance_xml.stylename,
name: instance_xml.name,
filename: instance_xml.filename,
postscriptfontname: instance_xml.postscriptfontname,
stylemapfamilyname: instance_xml.stylemapfamilyname,
stylemapstylename: instance_xml.stylemapstylename,
location: instance_xml.location.dimension,
}
}
}

/// <https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#location-element-top-level-stat-label>
#[derive(Deserialize)]
struct LocationXmlRepr {
pub dimension: Vec<Dimension>,
}

/// A [design space dimension]].
/// A [design space dimension].
///
/// [design space location]: https://fonttools.readthedocs.io/en/latest/designspaceLib/xml.html#location-element-source
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
#[serde(rename = "dimension")]
pub struct Dimension {
/// Name of the axis, e.g. Weight.
#[serde(rename = "@name")]
pub name: String,
/// Value on the axis in user coordinates.
#[serde(rename = "@uservalue")]
pub uservalue: Option<f32>,
/// Value on the axis in designcoordinates.
#[serde(rename = "@xvalue")]
pub xvalue: Option<f32>,
/// Separate value for anisotropic interpolations.
#[serde(rename = "@yvalue")]
pub yvalue: Option<f32>,
}

Expand All @@ -229,6 +162,55 @@ impl DesignSpaceDocument {
}
}

mod serde_impls {
use super::{Axis, Dimension, Instance, Source};
use serde::{Deserialize, Deserializer};

pub fn deserialize_location<'de, D>(deserializer: D) -> Result<Vec<Dimension>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
dimension: Vec<Dimension>,
}
Helper::deserialize(deserializer).map(|x| x.dimension)
}

pub fn deserialize_instances<'de, D>(deserializer: D) -> Result<Vec<Instance>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
instance: Vec<Instance>,
}
Helper::deserialize(deserializer).map(|x| x.instance)
}

pub fn deserialize_axes<'de, D>(deserializer: D) -> Result<Vec<Axis>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
axis: Vec<Axis>,
}
Helper::deserialize(deserializer).map(|x| x.axis)
}

pub fn deserialize_sources<'de, D>(deserializer: D) -> Result<Vec<Source>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
source: Vec<Source>,
}
Helper::deserialize(deserializer).map(|x| x.source)
}
}

#[cfg(test)]
mod tests {
use std::path::Path;
Expand All @@ -237,7 +219,7 @@ mod tests {

use crate::designspace::{AxisMapping, Dimension};

use super::DesignSpaceDocument;
use super::*;

fn dim_name_xvalue(name: &str, xvalue: f32) -> Dimension {
Dimension { name: name.to_string(), uservalue: None, xvalue: Some(xvalue), yvalue: None }
Expand All @@ -247,10 +229,7 @@ mod tests {
fn read_single_wght() {
let ds = DesignSpaceDocument::load(Path::new("testdata/single_wght.designspace")).unwrap();
assert_eq!(1, ds.axes.len());
assert_eq!(
&vec![AxisMapping { input: 400., output: 100. }],
ds.axes[0].map.as_ref().unwrap()
);
assert_eq!(&vec![AxisMapping { input: 400., output: 100. }], &ds.axes[0].map);
assert_eq!(1, ds.sources.len());
let weight_100 = dim_name_xvalue("Weight", 100.);
assert_eq!(vec![weight_100.clone()], ds.sources[0].location);
Expand All @@ -262,7 +241,7 @@ mod tests {
fn read_wght_variable() {
let ds = DesignSpaceDocument::load("testdata/wght.designspace").unwrap();
assert_eq!(1, ds.axes.len());
assert!(ds.axes[0].map.is_none());
assert!(ds.axes[0].map.is_empty());
assert_eq!(
vec![
("TestFamily-Regular.ufo".to_string(), vec![dim_name_xvalue("Weight", 400.)]),
Expand Down

0 comments on commit 53c603c

Please sign in to comment.