| // Copyright 2019 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. |
| |
| //! Version 2 of the Font Manifest schema. |
| |
| use { |
| crate::serde_ext::*, |
| anyhow::{ensure, Error}, |
| char_set::CharSet, |
| fidl_fuchsia_fonts::{GenericFontFamily, Slant, Width, WEIGHT_NORMAL}, |
| fuchsia_url::pkg_url::PkgUrl, |
| itertools::Itertools, |
| offset_string::OffsetString, |
| serde::{ |
| de::{Deserializer, Error as DeError}, |
| ser::Serializer, |
| }, |
| serde::{Deserialize, Serialize}, |
| std::{convert::TryFrom, iter, ops::Deref, path::PathBuf}, |
| unicase::UniCase, |
| }; |
| |
| /// Version 2 of the Font Manifest schema. |
| /// |
| /// Less duplication than v1 schema. Intended for generation using a Rust tool, not for writing by |
| /// hand. |
| #[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq, Hash)] |
| pub struct FontsManifest { |
| /// List of families in the manifest. |
| pub families: Vec<Family>, |
| |
| /// Sequence in which to consider typefaces when selecting a fallback, whether within a |
| /// requested generic family or as a last resort. |
| pub fallback_chain: Vec<TypefaceId>, |
| |
| /// Settings for the font provider service. |
| #[serde(default, skip_serializing_if = "Settings::is_empty")] |
| pub settings: Settings, |
| } |
| |
| impl FontsManifest { |
| /// Creates an empty fonts manifest |
| pub fn empty() -> Self { |
| Self::default() |
| } |
| } |
| |
| /// Represents a font family, its metadata, and its font files. |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)] |
| pub struct Family { |
| /// Canonical name of the font family. |
| pub name: String, |
| /// Alternate names for the font family. |
| // During de/serialization, omitted `aliases` are treated as an empty array and vice-versa. |
| #[serde(skip_serializing_if = "Vec::is_empty", default)] |
| pub aliases: Vec<FontFamilyAliasSet>, |
| /// The generic font family that this font belongs to. If this is a specialty font (e.g. for |
| /// custom icons), this should be set to `None`. |
| #[serde(with = "OptGenericFontFamily", default)] |
| pub generic_family: Option<GenericFontFamily>, |
| /// Collection of font files that make up the font family. |
| pub assets: Vec<Asset>, |
| } |
| |
| /// Represents a set of font family aliases, and optionally, typeface properties that should be |
| /// applied when treating those aliases as the canonical family. |
| /// |
| /// For example, the font family `"Roboto"` might have one alias set of the form: |
| /// ```json |
| /// { |
| /// "names": [ "Roboto Condensed" ], |
| /// "width": "condensed" |
| /// } |
| /// ``` |
| /// This means that when a client requests the family `"Roboto Condensed"`, the font server will |
| /// treat that as a request for `"Roboto"` with `Width::Condensed`. |
| /// |
| /// The font family `"Noto Sans CJK"` might have aliases of the form: |
| /// ```json |
| /// [ |
| /// { |
| /// "names": [ "Noto Sans CJK KR", "Noto Sans KR" ], |
| /// "languages": [ "ko" ] |
| /// }, |
| /// { |
| /// "names": [ "Noto Sans CJK JP", "Noto Sans JP" ], |
| /// "languages: [ "ja" ] |
| /// } |
| /// ] |
| /// ``` |
| /// |
| /// When a client requests `"Noto Sans CJK JP"` or `"Noto Sans JP"`, the font server will look under |
| /// `"Noto Sans CJK"` for typefaces that support Japanese (`"ja"`). |
| /// |
| /// Create using [`FontFamilyAliasSet::new`] if the aliases will map to typeface property overrides, |
| /// or [`FontFamilyAliasSet::without_overrides`] to create a plain set of aliases. |
| /// |
| /// When loaded by the font server, a `FontFamilyAliasSet` is expanded over all the `names`, |
| /// creating an alias entry for every name, with identical `style` and `languages` values. |
| #[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)] |
| pub struct FontFamilyAliasSet { |
| /// Alternate names for the font family. |
| #[serde( |
| deserialize_with = "FontFamilyAliasSet::deserialize_names", |
| serialize_with = "FontFamilyAliasSet::serialize_names" |
| )] |
| names: Vec<UniCase<String>>, |
| /// If non-empty, style overrides that will automatically be inserted into `TypefaceQuery` when |
| /// a client requests a font family using `alias` as the font family name. |
| #[serde(flatten)] |
| style: StyleOptions, |
| /// If non-empty, the languages that will automatically be inserted into `TypefaceQuery` when a |
| /// requests a font family using `alias` as the font family name. Language codes should be |
| /// specified in descending order of preference (i.e. more preferred languages come first). |
| #[serde(default, skip_serializing_if = "Vec::is_empty")] |
| languages: Vec<String>, |
| } |
| |
| impl FontFamilyAliasSet { |
| /// Create a new `FontFamilyAliasSet` with one or more names, and with optional style and |
| /// language overrides. |
| /// |
| /// - `names`: A list of one more alias names |
| /// - `style`: Optionally, style overrides that are automatically applied to the typeface |
| /// request when one of the `names` is requested. |
| /// - `languages`: Optionally, a list of language codes that is automatically applied to the |
| /// typeface request when one of the `names` is requested. |
| /// Do not sort the language codes. They are given in priority order, just as in |
| /// `TypefaceQuery.languages`. |
| /// |
| /// Examples: |
| /// ``` |
| /// use manifest::v2::FontFamilyAliasSet; |
| /// use manifest::serde_ext::StyleOptions; |
| /// |
| /// // Alias set for "Noto Sans CJK" for Traditional Chinese. Both `"Noto Sans CJK TC"` and |
| /// // `"Noto Sans TC"` will serve as aliases that apply the languages `["zh-Hant", "zh-Bopo"]` |
| /// // when requested. |
| /// FontFamilyAliasSet::new( |
| /// vec!["Noto Sans CJK TC", "Noto Sans TC"], |
| /// StyleOptions::default(), |
| /// vec!["zh-Hant", "zh-Bopo"]); |
| /// |
| /// // Alias set for "Roboto Condensed". `"Roboto Condensed"` will serve as an alias that |
| /// // applies the style options `width: condensed` when requested. |
| /// FontFamilyAliasSet::new( |
| /// vec!["Roboto Condensed"], |
| /// StyleOptions { |
| /// width: Some(fidl_fuchsia_fonts::Width::Condensed), |
| /// ..Default::default() |
| /// }, |
| /// vec![]); |
| /// ``` |
| pub fn new( |
| names: impl IntoIterator<Item = impl AsRef<str>>, |
| style: impl Into<StyleOptions>, |
| languages: impl IntoIterator<Item = impl AsRef<str>>, |
| ) -> Result<Self, Error> { |
| let set = FontFamilyAliasSet { |
| names: Self::preprocess_names(names), |
| style: style.into(), |
| // Note: Do not sort the language codes. They are given in priority order, just as in |
| // `TypefaceQuery.languages`. |
| languages: languages.into_iter().map(|s| s.as_ref().to_string()).collect_vec(), |
| }; |
| ensure!(!set.names.is_empty(), "Must contain at least one name"); |
| Ok(set) |
| } |
| |
| /// Create a new `FontFamilyAliasSet` with one or more names, with no typeface property |
| /// overrides. |
| pub fn without_overrides( |
| names: impl IntoIterator<Item = impl AsRef<str>>, |
| ) -> Result<Self, Error> { |
| Self::new(names, StyleOptions::default(), iter::empty::<String>()) |
| } |
| |
| /// Gets the alias names in this set. |
| pub fn names(&self) -> impl Iterator<Item = &String> { |
| (&self.names).iter().map(|uni| uni.deref()) |
| } |
| |
| /// Gets the style property overrides for this set of aliases (may be empty). |
| pub fn style_overrides(&self) -> &StyleOptions { |
| &self.style |
| } |
| |
| /// Gets the language code overrides for this set of aliases (may be empty). |
| pub fn language_overrides(&self) -> impl Iterator<Item = &String> { |
| (&self.languages).iter() |
| } |
| |
| /// Whether the alias set has any property overrides. If `false`, it's just a name alias. |
| pub fn has_overrides(&self) -> bool { |
| self.has_style_overrides() || self.has_language_overrides() |
| } |
| |
| /// Whether the alias set has style overrides. |
| pub fn has_style_overrides(&self) -> bool { |
| self.style != StyleOptions::default() |
| } |
| |
| /// Whether the alias set has language overrides. |
| pub fn has_language_overrides(&self) -> bool { |
| !self.languages.is_empty() |
| } |
| |
| /// Helper for deserializing a vector of `UniCase` strings. |
| fn deserialize_names<'de, D>(deserializer: D) -> Result<Vec<UniCase<String>>, D::Error> |
| where |
| D: Deserializer<'de>, |
| { |
| let names: Vec<String> = Vec::deserialize(deserializer)?; |
| Ok(Self::preprocess_names(names)) |
| } |
| |
| /// Helper for serializing a vector of `UniCase` strings. |
| fn serialize_names<S>(names: &Vec<UniCase<String>>, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: Serializer, |
| { |
| names.iter().map(|u| u.deref().to_string()).collect_vec().serialize(serializer) |
| } |
| |
| /// Sort the names using case-insensitive sort. |
| fn preprocess_names(names: impl IntoIterator<Item = impl AsRef<str>>) -> Vec<UniCase<String>> { |
| names.into_iter().map(|name| UniCase::new(name.as_ref().to_string())).sorted().collect() |
| } |
| } |
| |
| /// Represents a single font file, which contains one or more [`Typeface`]s. |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)] |
| pub struct Asset { |
| /// Asset identifier. Should be a valid file name, e.g. `"Roboto-Regular.ttf`. |
| pub file_name: String, |
| /// Where to find the file |
| pub location: AssetLocation, |
| /// List of typefaces in the file |
| pub typefaces: Vec<Typeface>, |
| } |
| |
| impl Asset { |
| /// If the asset represents a local file, returns the package-relative path to the file. |
| /// Otherwise, returns `None`. |
| pub fn local_path(&self) -> Option<PathBuf> { |
| match &self.location { |
| AssetLocation::LocalFile(locator) => { |
| Some(locator.directory.join(self.file_name.clone())) |
| } |
| _ => None, |
| } |
| } |
| } |
| |
| /// Describes the location of a font asset (excluding its file name). |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, Ord, PartialOrd)] |
| pub enum AssetLocation { |
| /// Indicates that the file is accessible through a file path in the font server's namespace |
| /// (e.g. at `/config/data/fonts/`). |
| #[serde(rename = "local")] |
| LocalFile(LocalFileLocator), |
| /// Indicates that the file is accessible in a separate font package (e.g. |
| /// `fuchsia-pkg://fuchsia.com/font-package-roboto-regular-ttf`). |
| #[serde(rename = "package")] |
| Package(PackageLocator), |
| } |
| |
| /// Describes the location of a local file asset. Used in conjunction with [`Asset::file_name`]. |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, Ord, PartialOrd)] |
| pub struct LocalFileLocator { |
| /// Package-relative path to the file, excluding the file name |
| pub directory: PathBuf, |
| } |
| |
| /// Describes the location of a font asset that's part of a Fuchsia package. Used in conjunction |
| /// with [`Asset::file_name`]. |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, Ord, PartialOrd)] |
| pub struct PackageLocator { |
| /// URL of just the package (not including the file name) |
| pub url: PkgUrl, |
| } |
| |
| /// Describes a single typeface within a font file |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)] |
| pub struct Typeface { |
| /// Index of the typeface in the file. If the file only contains a single typeface, this will |
| /// always be `0`. |
| #[serde(default = "Typeface::default_index")] |
| pub index: u32, |
| |
| /// List of languages that the typeface supports, in BCP-47 format. |
| /// |
| /// Example: `["en", "zh-Hant", "sr-Cyrl"]` |
| #[serde(default = "Typeface::default_languages", skip_serializing_if = "Vec::is_empty")] |
| pub languages: Vec<String>, |
| |
| /// Text style of the typeface. |
| #[serde(flatten)] |
| pub style: Style, |
| |
| /// List of Unicode code points supported by the typeface. |
| #[serde(with = "code_points_serde")] |
| pub code_points: CharSet, |
| } |
| |
| impl Typeface { |
| fn default_index() -> u32 { |
| 0 |
| } |
| |
| fn default_languages() -> Vec<String> { |
| vec![] |
| } |
| } |
| |
| /// Font provider service settings. |
| #[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq, Hash)] |
| pub struct Settings { |
| /// Maximum size of the in-memory font asset cache, in bytes. |
| #[serde(default)] |
| pub cache_size_bytes: Option<u64>, |
| } |
| |
| impl Settings { |
| /// Returns `true` if all of the settings are unset. |
| fn is_empty(&self) -> bool { |
| self == &Settings::default() |
| } |
| } |
| |
| /// Used for de/serializing a `CharSet`. |
| mod code_points_serde { |
| use super::*; |
| |
| pub fn deserialize<'d, D>(deserializer: D) -> Result<CharSet, D::Error> |
| where |
| D: Deserializer<'d>, |
| { |
| let offset_string = OffsetString::deserialize(deserializer)?; |
| CharSet::try_from(offset_string).map_err(|e| D::Error::custom(format!("{:?}", e))) |
| } |
| |
| pub fn serialize<S>(code_points: &CharSet, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: Serializer, |
| { |
| let offset_string: OffsetString = code_points.into(); |
| offset_string.serialize(serializer) |
| } |
| } |
| |
| /// Reference to a typeface, for use in specifying a fallback order. |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, Ord, PartialOrd)] |
| pub struct TypefaceId { |
| /// File name of the asset. |
| pub file_name: String, |
| /// Index of the typeface in the file. |
| /// |
| /// It is important to allow selecting specific indices rather than including all of the file's |
| /// indices in order because some files can contain typefaces from multiple font families, and a |
| /// product owner may wish to assign them different priorities in the fallback chain. |
| #[serde(default = "Typeface::default_index")] |
| pub index: u32, |
| } |
| |
| impl TypefaceId { |
| pub fn new(file_name: impl Into<String>, index: u32) -> Self { |
| TypefaceId { file_name: file_name.into(), index } |
| } |
| } |
| |
| /// Describes a typeface's style properties. Equivalent to [`fidl_fuchsia_fonts::Style2`], but all |
| /// fields are required. |
| #[allow(missing_docs)] |
| #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)] |
| pub struct Style { |
| #[serde(default = "Style::default_slant", with = "SlantDef")] |
| pub slant: Slant, |
| #[serde(default = "Style::default_weight")] |
| pub weight: u16, |
| #[serde(default = "Style::default_width", with = "WidthDef")] |
| pub width: Width, |
| } |
| |
| impl Style { |
| fn default_slant() -> Slant { |
| Slant::Upright |
| } |
| |
| fn default_weight() -> u16 { |
| WEIGHT_NORMAL |
| } |
| |
| fn default_width() -> Width { |
| Width::Normal |
| } |
| } |