manifest/
lib.rs

1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Library for deserializing and converting Fuchsia font manifests.
6
7#[macro_use]
8pub mod serde_ext;
9mod v1_to_v2;
10pub mod v2;
11
12use crate::serde_ext::*;
13use crate::v2::FontsManifest as FontsManifestV2;
14use anyhow::Error;
15use char_set::CharSet;
16use clonable_error::ClonableError;
17use fidl_fuchsia_fonts::{GenericFontFamily, Slant, Width, WEIGHT_NORMAL};
18use fuchsia_url::AbsolutePackageUrl;
19use offset_string::OffsetString;
20use serde::de::{self, Deserializer, Error as DeError};
21use serde::ser::Serializer;
22use serde::{Deserialize, Serialize};
23use std::fmt;
24use std::fs::{self, File};
25use std::io::BufReader;
26use std::path::{Path, PathBuf};
27use thiserror::Error;
28
29/// The various possible versions of the fonts manifest.
30#[derive(Debug, Deserialize, Serialize)]
31#[serde(tag = "version")]
32// This is a trick to implement custom deserialization that sets a default "version" field if
33// missing, without introducing another `Deserialize` struct as an additional wrapper.
34// See https://github.com/serde-rs/serde/issues/1174#issuecomment-372411280 for another example.
35#[serde(remote = "Self")]
36pub enum FontManifestWrapper {
37    /// Version 1. Deprecated.
38    #[serde(rename = "1")]
39    Version1(FontsManifest),
40
41    /// Version 2. New and shiny. And most importantly, machine-generated, so production manifests
42    /// can never get out of sync with the schema. (Checked in test manifests still can.)
43    #[serde(rename = "2")]
44    Version2(FontsManifestV2),
45}
46
47impl<'de> Deserialize<'de> for FontManifestWrapper {
48    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
49    where
50        D: Deserializer<'de>,
51    {
52        // If there's no version field, assume that the version is "1".
53        let mut map = serde_json::value::Map::deserialize(deserializer)?;
54        if map.get("version").is_none() {
55            map.insert("version".to_string(), serde_json::Value::String("1".to_string()));
56        }
57
58        // This is not a recursive call. Here, `FontManifestWrapper` is the "remote" type.
59        Ok(FontManifestWrapper::deserialize(serde_json::Value::Object(map))
60            .map_err(de::Error::custom)?)
61    }
62}
63
64impl Serialize for FontManifestWrapper {
65    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
66    where
67        S: Serializer,
68    {
69        // This is not a recursive call. Here, `FontManifestWrapper` is the "remote" type.
70        FontManifestWrapper::serialize(self, serializer)
71    }
72}
73
74/// A collection of metadata about font families.
75#[derive(Debug, Deserialize, Serialize)]
76pub struct FontsManifest {
77    /// List of font families.
78    pub families: Vec<Family>,
79}
80
81/// Metadata about a single font family.
82#[derive(Debug, Deserialize, Serialize)]
83pub struct Family {
84    /// Family name
85    pub family: String,
86
87    /// List of alternate names
88    pub aliases: Option<Vec<String>>,
89
90    /// List of font assets and typeface metadata belonging to this family
91    pub fonts: Vec<Font>,
92
93    /// Whether this font can serve as a fallback when other fonts are missing.
94    #[serde(default = "default_fallback")]
95    pub fallback: bool,
96
97    /// The generic group of font families to which this family belongs.
98    #[serde(
99        alias = "fallback_group",
100        default = "default_generic_family",
101        with = "OptGenericFontFamily"
102    )]
103    pub generic_family: Option<GenericFontFamily>,
104}
105
106/// Collection of BCP-47 language IDs.
107pub type LanguageSet = Vec<String>;
108
109/// A reference to a font asset (file path) and metadata about one of the typefaces contained in the
110/// file.
111#[derive(Debug, Deserialize, Serialize)]
112pub struct Font {
113    /// Path to the font file.
114    pub asset: PathBuf,
115
116    /// Index of the typeface within the file.
117    #[serde(default = "default_index")]
118    pub index: u32,
119
120    /// The typeface's slant.
121    #[serde(default = "default_slant", with = "SlantDef")]
122    pub slant: Slant,
123
124    /// The typeface's weight.
125    #[serde(default = "default_weight")]
126    pub weight: u16,
127
128    /// The typeface's width.
129    #[serde(default = "default_width", with = "WidthDef")]
130    pub width: Width,
131
132    /// List of BCP-47 language IDs explicitly supported by the typeface.
133    #[serde(
134        alias = "language",
135        default = "default_languages",
136        deserialize_with = "deserialize_languages"
137    )]
138    pub languages: LanguageSet,
139
140    /// Fuchsia Package URL at which this font file can also be found.
141    #[serde(default = "default_package")]
142    pub package: Option<AbsolutePackageUrl>,
143
144    /// Character set supported by the typeface.
145    #[serde(
146        default,
147        deserialize_with = "deserialize_code_points",
148        serialize_with = "serialize_code_points"
149    )]
150    pub code_points: CharSet,
151}
152
153fn default_fallback() -> bool {
154    false
155}
156
157fn default_generic_family() -> Option<GenericFontFamily> {
158    None
159}
160
161fn default_index() -> u32 {
162    0
163}
164
165fn default_slant() -> Slant {
166    Slant::Upright
167}
168
169fn default_weight() -> u16 {
170    WEIGHT_NORMAL
171}
172
173fn default_width() -> Width {
174    Width::Normal
175}
176
177fn default_languages() -> LanguageSet {
178    LanguageSet::new()
179}
180
181fn default_package() -> Option<AbsolutePackageUrl> {
182    None
183}
184
185/// Helper used to deserialize language field for a font. Language field can contain either a single
186/// string or an array of strings.
187fn deserialize_languages<'d, D>(deserializer: D) -> Result<LanguageSet, D::Error>
188where
189    D: Deserializer<'d>,
190{
191    struct LanguageSetVisitor;
192
193    impl<'de> de::Visitor<'de> for LanguageSetVisitor {
194        type Value = Vec<String>;
195
196        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
197            formatter.write_str("string or list of strings")
198        }
199
200        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
201        where
202            E: de::Error,
203        {
204            Ok(vec![s.to_string()])
205        }
206
207        fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
208        where
209            S: de::SeqAccess<'de>,
210        {
211            Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))
212        }
213    }
214
215    deserializer.deserialize_any(LanguageSetVisitor)
216}
217
218fn deserialize_code_points<'d, D>(deserializer: D) -> Result<CharSet, D::Error>
219where
220    D: Deserializer<'d>,
221{
222    let offset_string = OffsetString::deserialize(deserializer)?;
223    CharSet::try_from(offset_string).map_err(|e| D::Error::custom(format!("{:?}", e)))
224}
225
226fn serialize_code_points<S>(code_points: &CharSet, serializer: S) -> Result<S::Ok, S::Error>
227where
228    S: Serializer,
229{
230    let offset_string: OffsetString = code_points.into();
231    offset_string.serialize(serializer)
232}
233
234impl FontsManifest {
235    /// Tries to deserialize a v1 or v2 manifest from a JSON file.
236    ///
237    /// (Also performs some file path cleanup in v1 manifests.)
238    pub fn load_from_file(path: &Path) -> Result<FontManifestWrapper, anyhow::Error> {
239        let path = fs::canonicalize(path)?;
240        let base_dir =
241            path.parent().ok_or_else(|| ManifestLoadError::InvalidPath { path: path.clone() })?;
242
243        let file = File::open(&path)
244            .map_err(|e| ManifestLoadError::ReadError { path: path.clone(), cause: e })?;
245
246        let mut wrapper: FontManifestWrapper =
247            serde_json::from_reader(BufReader::new(file)).map_err(|e| {
248                ManifestLoadError::ParseError { path: path.clone(), cause: Error::from(e).into() }
249            })?;
250
251        // Make sure all paths are absolute in v1.
252        // (In v2, the schema for `LocalFileLocator` is specific about path types.)
253        if let FontManifestWrapper::Version1(v1) = &mut wrapper {
254            for family in v1.families.iter_mut() {
255                for font in family.fonts.iter_mut() {
256                    if font.asset.is_relative() {
257                        font.asset = base_dir.join(font.asset.clone());
258                    }
259                }
260            }
261        }
262
263        Ok(wrapper)
264    }
265}
266
267/// Errors when loading manifest
268#[derive(Debug, Error)]
269pub enum ManifestLoadError {
270    /// Invalid manifest path
271    #[error("Invalid manifest path: {:?}", path)]
272    InvalidPath {
273        /// Manifest file path
274        path: PathBuf,
275    },
276
277    /// IO error when reading the manifest
278    #[error("Failed to read {:?}: {:?}", path, cause)]
279    ReadError {
280        /// Manifest file path
281        path: PathBuf,
282        /// Root cause of error
283        #[source]
284        cause: std::io::Error,
285    },
286
287    /// Invalid syntax in the manifest file
288    #[error("Failed to parse {:?}: {:?}", path, cause)]
289    ParseError {
290        /// Manifest file path
291        path: PathBuf,
292        /// Root cause of error
293        #[source]
294        cause: ClonableError,
295    },
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use assert_matches::assert_matches;
302
303    #[test]
304    fn test_deserialize_manifest_version_v1_implicit() -> Result<(), Error> {
305        let json = r#"
306        {
307            "families": []
308        }
309        "#;
310
311        let wrapper: FontManifestWrapper = serde_json::from_str(json)?;
312        assert_matches!(wrapper, FontManifestWrapper::Version1(_));
313        Ok(())
314    }
315
316    #[test]
317    fn test_deserialize_manifest_version_v2() -> Result<(), Error> {
318        let json = r#"
319        {
320            "version": "2",
321            "families": [],
322            "fallback_chain": []
323        }
324        "#;
325
326        let wrapper: FontManifestWrapper = serde_json::from_str(json)?;
327        assert_matches!(wrapper, FontManifestWrapper::Version2(_));
328        Ok(())
329    }
330}