From 1ef6f20d9317f8b51a6fc46aafc119dace5aa316 Mon Sep 17 00:00:00 2001 From: Colin Rofls Date: Fri, 2 Dec 2022 16:53:09 -0500 Subject: [PATCH] Add custom Codepoints type, wrapping IndexSet --- src/glyph/codepoints.rs | 90 +++++++++++++++++++++++++++++++++++++++++ src/glyph/mod.rs | 6 ++- src/glyph/tests.rs | 6 +-- src/layer.rs | 6 +-- src/lib.rs | 2 +- tests/save.rs | 7 ++-- 6 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 src/glyph/codepoints.rs diff --git a/src/glyph/codepoints.rs b/src/glyph/codepoints.rs new file mode 100644 index 00000000..ddc4076a --- /dev/null +++ b/src/glyph/codepoints.rs @@ -0,0 +1,90 @@ +//! A collection of codepoints +//! +//! We want to preserve order and ensure uniqueness, so we use an IndexSet; +//! however we don't want this to be part of our public API, so use a wrapper. + +use indexmap::IndexSet; + +/// A set of Unicode codepoints +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Codepoints(IndexSet); + +impl Codepoints { + /// Construct a new set of codepoints. + /// + /// + /// The input can be anything that impls `IntoIterator`, + /// and the simplest use would be to pass an array: + /// + /// ``` + /// # use norad::Codepoints; + /// let mut codepoints = Codepoints::new(['A', 'B']); + /// ``` + pub fn new(src: impl IntoIterator) -> Self { + Self(src.into_iter().collect()) + } + /// Return the number of codepoints. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns true if there are no codepoints. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Set the codepoints. See [Codepoints::new] for usage. + pub fn set(&mut self, codepoints: impl IntoIterator) { + self.0.clear(); + self.0.extend(codepoints); + } + + /// Remove all codepoints from the set. + pub fn clear(&mut self) { + self.0.clear() + } + + /// Returns true if the provided codepoint is in this set. + pub fn contains(&self, codepoint: char) -> bool { + self.0.contains(&codepoint) + } + + /// Insert a codepoint into the set. + /// + /// Returns `true` if this item did not exist in the set. + /// If this item *does* exist, the order will be unchanged. + pub fn insert(&mut self, codepoint: char) -> bool { + self.0.insert(codepoint) + } + + /// Iterate over the codepoints. + pub fn iter(&self) -> impl Iterator + '_ { + self.0.iter().copied() + } +} + +impl FromIterator for Codepoints { + fn from_iter>(iter: T) -> Self { + Codepoints(iter.into_iter().collect()) + } +} + +impl IntoIterator for Codepoints { + type Item = char; + + type IntoIter = indexmap::set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a Codepoints { + type Item = &'a char; + + type IntoIter = indexmap::set::Iter<'a, char>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} diff --git a/src/glyph/mod.rs b/src/glyph/mod.rs index c8102662..bcbdea59 100644 --- a/src/glyph/mod.rs +++ b/src/glyph/mod.rs @@ -1,6 +1,7 @@ //! Data related to individual glyphs. pub mod builder; +mod codepoints; mod parse; mod serialize; #[cfg(test)] @@ -13,7 +14,6 @@ use crate::error::ConvertContourError; #[cfg(feature = "druid")] use druid::{Data, Lens}; -use indexmap::IndexSet; use crate::error::{ErrorKind, GlifLoadError, GlifWriteError, StoreError}; use crate::name::Name; @@ -21,6 +21,8 @@ use crate::names::NameList; use crate::shared_types::PUBLIC_OBJECT_LIBS_KEY; use crate::{Color, Guideline, Identifier, Line, Plist, WriteOptions}; +pub use codepoints::Codepoints; + /// A glyph, loaded from a [`.glif` file][glif]. /// /// Norad can load glif version 1.0 and 2.0, and can save 2.0 only. @@ -39,7 +41,7 @@ pub struct Glyph { /// A collection of glyph Unicode code points. /// /// The first entry defines the primary Unicode value for this glyph. - pub codepoints: IndexSet, + pub codepoints: Codepoints, /// Arbitrary glyph note. pub note: Option, /// A collection of glyph guidelines. diff --git a/src/glyph/tests.rs b/src/glyph/tests.rs index 29c9e601..9cc4934d 100644 --- a/src/glyph/tests.rs +++ b/src/glyph/tests.rs @@ -1,5 +1,3 @@ -use indexmap::indexset; - use super::parse::parse_glyph; use super::*; use crate::write::QuoteChar; @@ -883,9 +881,9 @@ fn deduplicate_unicodes2() { "#; let mut glyph = parse_glyph(data.as_bytes()).unwrap(); - assert_eq!(glyph.codepoints, indexset!['e', 'f', 'g'].into()); + assert_eq!(glyph.codepoints, Codepoints::new(['e', 'f', 'g'])); - glyph.codepoints = indexset!['e', 'f', 'e', 'g'].into(); + glyph.codepoints = Codepoints::new(['e', 'f', 'e', 'g']); let data2 = glyph.encode_xml().unwrap(); let data2 = std::str::from_utf8(&data2).unwrap(); let data2_expected = r#" diff --git a/src/layer.rs b/src/layer.rs index 70224427..518c15c3 100644 --- a/src/layer.rs +++ b/src/layer.rs @@ -594,9 +594,7 @@ impl Default for Layer { #[cfg(test)] mod tests { - use indexmap::indexset; - - use crate::DataRequest; + use crate::{Codepoints, DataRequest}; use super::*; use std::path::Path; @@ -615,7 +613,7 @@ mod tests { let glyph = layer.get_glyph("A").expect("failed to load glyph 'A'"); assert_eq!(glyph.height, 0.); assert_eq!(glyph.width, 1190.); - assert_eq!(glyph.codepoints, indexset!['A']); + assert_eq!(glyph.codepoints, Codepoints::new(['A'])); } #[test] diff --git a/src/lib.rs b/src/lib.rs index cb6c12e2..c656fe6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ pub use data_request::DataRequest; pub use font::{Font, FormatVersion, MetaInfo}; pub use fontinfo::FontInfo; pub use glyph::{ - AffineTransform, Anchor, Component, Contour, ContourPoint, Glyph, Image, PointType, + AffineTransform, Anchor, Codepoints, Component, Contour, ContourPoint, Glyph, Image, PointType, }; pub use name::Name; diff --git a/tests/save.rs b/tests/save.rs index eae8a510..07614126 100644 --- a/tests/save.rs +++ b/tests/save.rs @@ -1,7 +1,6 @@ //! Testing saving files. -use indexmap::indexset; -use norad::{Font, FormatVersion, Glyph, Identifier, Plist}; +use norad::{Codepoints, Font, FormatVersion, Glyph, Identifier, Plist}; use plist::Value; #[test] @@ -25,7 +24,7 @@ fn save_default() { fn save_new_file() { let mut my_ufo = Font::new(); let mut my_glyph = Glyph::new("A"); - my_glyph.codepoints = indexset!['A']; + my_glyph.codepoints.set(['A']); my_glyph.note = Some("I did a glyph!".into()); let mut plist = Plist::new(); plist.insert("my-cool-key".into(), plist::Value::Integer(420_u32.into())); @@ -43,7 +42,7 @@ fn save_new_file() { let loaded = Font::load(dir).unwrap(); assert!(loaded.default_layer().get_glyph("A").is_some()); let glyph = loaded.default_layer().get_glyph("A").unwrap(); - assert_eq!(glyph.codepoints, indexset!['A']); + assert_eq!(glyph.codepoints, Codepoints::new(['A'])); let lib_val = glyph.lib.get("my-cool-key").and_then(|val| val.as_unsigned_integer()); assert_eq!(lib_val, Some(420)); }