use std::sync::Arc; use schemars::schema::{InstanceType, SchemaObject}; /// The OpenType features that can be configured for a given font.#[derive(Default, Clone, Eq, PartialEq, Hash)]pub struct FontFeatures(pub Arc<Vec<(String, u32)>>); impl FontFeatures { /// Disables `calt`. pub fn disable_ligatures() -> Self { Self(Arc::new(vec![("calt".into(), 0)])) } /// Get the tag name list of the font OpenType features /// only enabled or disabled features are returned pub fn tag_value_list(&self) -> &[(String, u32)] { self.0.as_slice() } /// Returns whether the `calt` feature is enabled. /// /// Returns `None` if the feature is not present. pub fn is_calt_enabled(&self) -> Option<bool> { self.0 .iter() .find(|(feature, _)| feature == "calt") .map(|(_, value)| *value == 1) }} impl std::fmt::Debug for FontFeatures { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut debug = f.debug_struct("FontFeatures"); for (tag, value) in self.tag_value_list() { debug.field(tag, value); } debug.finish() }} #[derive(Debug, serde::Serialize, serde::Deserialize)]#[serde(untagged)]enum FeatureValue { Bool(bool), Number(serde_json::Number),} impl<'de> serde::Deserialize<'de> for FontFeatures { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { use serde::de::{MapAccess, Visitor}; use std::fmt; struct FontFeaturesVisitor; impl<'de> Visitor<'de> for FontFeaturesVisitor { type Value = FontFeatures; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map of font features") } fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error> where M: MapAccess<'de>, { let mut feature_list = Vec::new(); while let Some((key, value)) = access.next_entry::<String, Option<FeatureValue>>()? { if !is_valid_feature_tag(&key) { log::error!("Incorrect font feature tag: {}", key); continue; } if let Some(value) = value { match value { FeatureValue::Bool(enable) => { if enable { feature_list.push((key, 1)); } else { feature_list.push((key, 0)); } } FeatureValue::Number(value) => { if value.is_u64() { feature_list.push((key, value.as_u64().unwrap() as u32)); } else { log::error!( "Incorrect font feature value {} for feature tag {}", value, key ); continue; } } } } } Ok(FontFeatures(Arc::new(feature_list))) } } let features = deserializer.deserialize_map(FontFeaturesVisitor)?; Ok(features) }} impl serde::Serialize for FontFeatures { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { use serde::ser::SerializeMap; let mut map = serializer.serialize_map(None)?; for (tag, value) in self.tag_value_list() { map.serialize_entry(tag, value)?; } map.end() }} impl schemars::JsonSchema for FontFeatures { fn schema_name() -> String { "FontFeatures".into() } fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { let mut schema = SchemaObject::default(); schema.instance_type = Some(schemars::schema::SingleOrVec::Single(Box::new( InstanceType::Object, ))); { let mut property = SchemaObject { instance_type: Some(schemars::schema::SingleOrVec::Vec(vec![ InstanceType::Boolean, InstanceType::Integer, ])), ..Default::default() }; { let mut number_constraints = property.number(); number_constraints.multiple_of = Some(1.0); number_constraints.minimum = Some(0.0); } schema .object() .pattern_properties .insert("[0-9a-zA-Z]{4}$".into(), property.into()); } schema.into() }} fn is_valid_feature_tag(tag: &str) -> bool { tag.len() == 4 && tag.chars().all(|c| c.is_ascii_alphanumeric())}
use std::sync::Arc;
use schemars::schema::{InstanceType, SchemaObject};
/// The OpenType features that can be configured for a given font.
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct FontFeatures(pub Arc<Vec<(String, u32)>>);
impl FontFeatures {
/// Disables `calt`.
pub fn disable_ligatures() -> Self {
Self(Arc::new(vec![("calt".into(), 0)]))
}
/// Get the tag name list of the font OpenType features
/// only enabled or disabled features are returned
pub fn tag_value_list(&self) -> &[(String, u32)] {
self.0.as_slice()
}
/// Returns whether the `calt` feature is enabled.
///
/// Returns `None` if the feature is not present.
pub fn is_calt_enabled(&self) -> Option<bool> {
self.0
.iter()
.find(|(feature, _)| feature == "calt")
.map(|(_, value)| *value == 1)
}
}
impl std::fmt::Debug for FontFeatures {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("FontFeatures");
for (tag, value) in self.tag_value_list() {
debug.field(tag, value);
}
debug.finish()
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum FeatureValue {
Bool(bool),
Number(serde_json::Number),
}
impl<'de> serde::Deserialize<'de> for FontFeatures {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{MapAccess, Visitor};
use std::fmt;
struct FontFeaturesVisitor;
impl<'de> Visitor<'de> for FontFeaturesVisitor {
type Value = FontFeatures;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map of font features")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut feature_list = Vec::new();
while let Some((key, value)) =
access.next_entry::<String, Option<FeatureValue>>()?
{
if !is_valid_feature_tag(&key) {
log::error!("Incorrect font feature tag: {}", key);
continue;
}
if let Some(value) = value {
match value {
FeatureValue::Bool(enable) => {
if enable {
feature_list.push((key, 1));
} else {
feature_list.push((key, 0));
}
}
FeatureValue::Number(value) => {
if value.is_u64() {
feature_list.push((key, value.as_u64().unwrap() as u32));
} else {
log::error!(
"Incorrect font feature value {} for feature tag {}",
value,
key
);
continue;
}
}
}
}
}
Ok(FontFeatures(Arc::new(feature_list)))
}
}
let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
Ok(features)
}
}
impl serde::Serialize for FontFeatures {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(None)?;
for (tag, value) in self.tag_value_list() {
map.serialize_entry(tag, value)?;
}
map.end()
}
}
impl schemars::JsonSchema for FontFeatures {
fn schema_name() -> String {
"FontFeatures".into()
}
fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
let mut schema = SchemaObject::default();
schema.instance_type = Some(schemars::schema::SingleOrVec::Single(Box::new(
InstanceType::Object,
)));
{
let mut property = SchemaObject {
instance_type: Some(schemars::schema::SingleOrVec::Vec(vec![
InstanceType::Boolean,
InstanceType::Integer,
])),
..Default::default()
};
{
let mut number_constraints = property.number();
number_constraints.multiple_of = Some(1.0);
number_constraints.minimum = Some(0.0);
}
schema
.object()
.pattern_properties
.insert("[0-9a-zA-Z]{4}$".into(), property.into());
}
schema.into()
}
}
fn is_valid_feature_tag(tag: &str) -> bool {
tag.len() == 4 && tag.chars().all(|c| c.is_ascii_alphanumeric())
}