| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #![allow(deprecated)] |
| |
| use { |
| crate::{font_catalog::TypefaceInAssetIndex, serde_ext}, |
| anyhow::Error, |
| serde::Deserialize, |
| std::path::Path, |
| }; |
| |
| /// Serializable representation of a ".fontcfg.json" file, which includes fallback chains. |
| #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] |
| #[serde(tag = "version")] |
| enum ProductConfigWrapper { |
| #[serde(rename = "1")] |
| Version1(ProductConfigInternal), |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] |
| struct ProductConfigInternal { |
| /// A sequence of typeface identifiers representing a fallback chain. |
| pub fallback_chain: Vec<TypefaceIdOrFileName>, |
| /// Runtime settings for the font provider service. |
| #[serde(default)] |
| pub settings: Settings, |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] |
| #[serde(untagged)] |
| enum TypefaceIdOrFileName { |
| TypefaceId(TypefaceId), |
| FileName(String), |
| } |
| |
| impl Into<TypefaceId> for TypefaceIdOrFileName { |
| fn into(self) -> TypefaceId { |
| match self { |
| TypefaceIdOrFileName::TypefaceId(typeface_id) => typeface_id, |
| TypefaceIdOrFileName::FileName(file_name) => TypefaceId { file_name, index: None }, |
| } |
| } |
| } |
| |
| /// Reference to a typeface, for use in specifying a fallback order. |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] |
| pub struct TypefaceId { |
| /// File name of the asset. |
| pub file_name: String, |
| /// Index of the typeface in the file. If absent, treat as all of the typefaces in the file. |
| pub index: Option<TypefaceInAssetIndex>, |
| } |
| |
| #[cfg(test)] |
| impl TypefaceId { |
| /// Creates a new `TypefaceId` representing a single typeface in the given file. |
| /// |
| /// - `file_name`: The file name of the font asset. |
| /// - `index`: The index of the typeface in the given file. See [`TypefaceInAssetIndex`]. |
| pub(crate) fn new( |
| file_name: impl Into<String>, |
| index: impl Into<TypefaceInAssetIndex>, |
| ) -> Self { |
| Self { file_name: file_name.into(), index: Some(index.into()) } |
| } |
| } |
| |
| /// An in-memory representation of a ".fontcfg.json" file. |
| #[derive(Clone, Debug, Eq, PartialEq, Hash, Default)] |
| pub struct ProductConfig { |
| pub fallback_chain: Vec<TypefaceId>, |
| pub settings: Settings, |
| } |
| |
| impl ProductConfig { |
| /// Loads the `ProductConfig` from a ".fontcfg.json" or ".fontcfg.json5" file. |
| pub fn load_from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> { |
| let wrapper: ProductConfigWrapper = serde_ext::load_from_path(path)?; |
| match wrapper { |
| ProductConfigWrapper::Version1(ProductConfigInternal { fallback_chain, settings }) => { |
| let fallback_chain = fallback_chain |
| .into_iter() |
| .map(|id_or_filename| id_or_filename.into()) |
| .collect(); |
| Ok(ProductConfig { fallback_chain, settings }) |
| } |
| } |
| } |
| |
| /// Iterates over the `TypefaceId`s in the configuration's font fallback chain. |
| pub fn iter_fallback_chain(&self) -> impl Iterator<Item = &TypefaceId> { |
| self.fallback_chain.iter() |
| } |
| } |
| |
| /// Runtime settings for the font provider service. |
| #[derive(Clone, Debug, Eq, PartialEq, Hash, Default, Deserialize)] |
| pub struct Settings { |
| /// Maximum size of the font service's in-memory asset cache, in bytes. |
| pub cache_size_bytes: Option<u64>, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| |
| use { |
| super::*, anyhow::Error, pretty_assertions::assert_eq, serde_json::json, std::io::Write, |
| tempfile::NamedTempFile, |
| }; |
| |
| #[test] |
| fn test_load_from_path() -> Result<(), Error> { |
| let contents = json!( |
| { |
| "version": "1", |
| "fallback_chain": [ |
| "a.ttf", |
| "b.ttf", |
| { "file_name": "c.ttf", "index": 1 }, |
| { "file_name": "d.ttf" }, |
| { "file_name": "e.ttf", "index": 0 } |
| ], |
| "settings": { |
| "cache_size_bytes": 5000123 |
| } |
| }) |
| .to_string(); |
| let mut file = NamedTempFile::new()?; |
| file.write_all(contents.as_bytes())?; |
| |
| let actual = ProductConfig::load_from_path(file.path())?; |
| let expected = ProductConfig { |
| fallback_chain: vec![ |
| TypefaceId { file_name: "a.ttf".to_string(), index: None }, |
| TypefaceId { file_name: "b.ttf".to_string(), index: None }, |
| TypefaceId { file_name: "c.ttf".to_string(), index: Some(TypefaceInAssetIndex(1)) }, |
| TypefaceId { file_name: "d.ttf".to_string(), index: None }, |
| TypefaceId { file_name: "e.ttf".to_string(), index: Some(TypefaceInAssetIndex(0)) }, |
| ], |
| settings: Settings { cache_size_bytes: Some(5_000_123) }, |
| }; |
| |
| assert_eq!(actual, expected); |
| |
| Ok(()) |
| } |
| } |