manifest/
v2.rs

1// Copyright 2019 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//! Version 2 of the Font Manifest schema.
6
7use crate::serde_ext::*;
8use anyhow::{ensure, Error};
9use char_set::CharSet;
10use fidl_fuchsia_fonts::{GenericFontFamily, Slant, Width, WEIGHT_NORMAL};
11use fuchsia_url::AbsolutePackageUrl;
12use itertools::Itertools;
13use offset_string::OffsetString;
14use serde::de::{Deserializer, Error as DeError};
15use serde::ser::Serializer;
16use serde::{Deserialize, Serialize};
17use std::iter;
18use std::ops::Deref;
19use std::path::PathBuf;
20use unicase::UniCase;
21
22/// Version 2 of the Font Manifest schema.
23///
24/// Less duplication than v1 schema. Intended for generation using a Rust tool, not for writing by
25/// hand.
26#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq, Hash)]
27pub struct FontsManifest {
28    /// List of families in the manifest.
29    pub families: Vec<Family>,
30
31    /// Sequence in which to consider typefaces when selecting a fallback, whether within a
32    /// requested generic family or as a last resort.
33    pub fallback_chain: Vec<TypefaceId>,
34
35    /// Settings for the font provider service.
36    #[serde(default, skip_serializing_if = "Settings::is_empty")]
37    pub settings: Settings,
38}
39
40impl FontsManifest {
41    /// Creates an empty fonts manifest
42    pub fn empty() -> Self {
43        Self::default()
44    }
45}
46
47/// Represents a font family, its metadata, and its font files.
48#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)]
49pub struct Family {
50    /// Canonical name of the font family.
51    pub name: String,
52    /// Alternate names for the font family.
53    // During de/serialization, omitted `aliases` are treated as an empty array and vice-versa.
54    #[serde(skip_serializing_if = "Vec::is_empty", default)]
55    pub aliases: Vec<FontFamilyAliasSet>,
56    /// The generic font family that this font belongs to. If this is a specialty font (e.g. for
57    /// custom icons), this should be set to `None`.
58    #[serde(with = "OptGenericFontFamily", default)]
59    pub generic_family: Option<GenericFontFamily>,
60    /// Collection of font files that make up the font family.
61    pub assets: Vec<Asset>,
62}
63
64/// Represents a set of font family aliases, and optionally, typeface properties that should be
65/// applied when treating those aliases as the canonical family.
66///
67/// For example, the font family `"Roboto"` might have one alias set of the form:
68/// ```json
69/// {
70///   "names": [ "Roboto Condensed" ],
71///   "width": "condensed"
72/// }
73/// ```
74/// This means that when a client requests the family `"Roboto Condensed"`, the font server will
75/// treat that as a request for `"Roboto"` with `Width::Condensed`.
76///
77/// The font family `"Noto Sans CJK"` might have aliases of the form:
78/// ```json
79/// [
80///   {
81///     "names": [ "Noto Sans CJK KR", "Noto Sans KR" ],
82///     "languages": [ "ko" ]
83///   },
84///   {
85///     "names": [ "Noto Sans CJK JP", "Noto Sans JP" ],
86///     "languages: [ "ja" ]
87///   }
88/// ]
89/// ```
90///
91/// When a client requests `"Noto Sans CJK JP"` or `"Noto Sans JP"`, the font server will look under
92/// `"Noto Sans CJK"` for typefaces that support Japanese (`"ja"`).
93///
94/// Create using [`FontFamilyAliasSet::new`] if the aliases will map to typeface property overrides,
95/// or [`FontFamilyAliasSet::without_overrides`] to create a plain set of aliases.
96///
97/// When loaded by the font server, a `FontFamilyAliasSet` is expanded over all the `names`,
98/// creating an alias entry for every name, with identical `style` and `languages` values.
99#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
100pub struct FontFamilyAliasSet {
101    /// Alternate names for the font family.
102    #[serde(
103        deserialize_with = "FontFamilyAliasSet::deserialize_names",
104        serialize_with = "FontFamilyAliasSet::serialize_names"
105    )]
106    names: Vec<UniCase<String>>,
107    /// If non-empty, style overrides that will automatically be inserted into `TypefaceQuery` when
108    /// a client requests a font family using `alias` as the font family name.
109    #[serde(flatten)]
110    style: StyleOptions,
111    /// If non-empty, the languages that will automatically be inserted into `TypefaceQuery` when a
112    /// requests a font family using `alias` as the font family name. Language codes should be
113    /// specified in descending order of preference (i.e. more preferred languages come first).
114    #[serde(default, skip_serializing_if = "Vec::is_empty")]
115    languages: Vec<String>,
116}
117
118impl FontFamilyAliasSet {
119    /// Create a new `FontFamilyAliasSet` with one or more names, and with optional style and
120    /// language overrides.
121    ///
122    /// - `names`: A list of one more alias names
123    /// - `style`: Optionally, style overrides that are automatically applied to the typeface
124    ///    request when one of the `names` is requested.
125    /// - `languages`: Optionally, a list of language codes that is automatically applied to the
126    ///   typeface request when one of the `names` is requested.
127    ///   Do not sort the language codes. They are given in priority order, just as in
128    ///   `TypefaceQuery.languages`.
129    ///
130    /// Examples:
131    /// ```
132    /// use manifest::v2::FontFamilyAliasSet;
133    /// use manifest::serde_ext::StyleOptions;
134    ///
135    /// // Alias set for "Noto Sans CJK" for Traditional Chinese. Both `"Noto Sans CJK TC"` and
136    /// // `"Noto Sans TC"` will serve as aliases that apply the languages `["zh-Hant", "zh-Bopo"]`
137    /// // when requested.
138    /// FontFamilyAliasSet::new(
139    ///     vec!["Noto Sans CJK TC", "Noto Sans TC"],
140    ///     StyleOptions::default(),
141    ///     vec!["zh-Hant", "zh-Bopo"]);
142    ///
143    /// // Alias set for "Roboto Condensed". `"Roboto Condensed"` will serve as an alias that
144    /// // applies the style options `width: condensed` when requested.
145    /// FontFamilyAliasSet::new(
146    ///     vec!["Roboto Condensed"],
147    ///     StyleOptions {
148    ///         width: Some(fidl_fuchsia_fonts::Width::Condensed),
149    ///         ..Default::default()
150    ///     },
151    ///     vec![]);
152    /// ```
153    pub fn new(
154        names: impl IntoIterator<Item = impl AsRef<str>>,
155        style: impl Into<StyleOptions>,
156        languages: impl IntoIterator<Item = impl AsRef<str>>,
157    ) -> Result<Self, Error> {
158        let set = FontFamilyAliasSet {
159            names: Self::preprocess_names(names),
160            style: style.into(),
161            // Note: Do not sort the language codes. They are given in priority order, just as in
162            // `TypefaceQuery.languages`.
163            languages: languages.into_iter().map(|s| s.as_ref().to_string()).collect_vec(),
164        };
165        ensure!(!set.names.is_empty(), "Must contain at least one name");
166        Ok(set)
167    }
168
169    /// Create a new `FontFamilyAliasSet` with one or more names, with no typeface property
170    /// overrides.
171    pub fn without_overrides(
172        names: impl IntoIterator<Item = impl AsRef<str>>,
173    ) -> Result<Self, Error> {
174        Self::new(names, StyleOptions::default(), iter::empty::<String>())
175    }
176
177    /// Gets the alias names in this set.
178    pub fn names(&self) -> impl Iterator<Item = &String> {
179        (&self.names).iter().map(|uni| uni.deref())
180    }
181
182    /// Gets the style property overrides for this set of aliases (may be empty).
183    pub fn style_overrides(&self) -> &StyleOptions {
184        &self.style
185    }
186
187    /// Gets the language code overrides for this set of aliases (may be empty).
188    pub fn language_overrides(&self) -> impl Iterator<Item = &String> {
189        (&self.languages).iter()
190    }
191
192    /// Whether the alias set has any property overrides. If `false`, it's just a name alias.
193    pub fn has_overrides(&self) -> bool {
194        self.has_style_overrides() || self.has_language_overrides()
195    }
196
197    /// Whether the alias set has style overrides.
198    pub fn has_style_overrides(&self) -> bool {
199        self.style != StyleOptions::default()
200    }
201
202    /// Whether the alias set has language overrides.
203    pub fn has_language_overrides(&self) -> bool {
204        !self.languages.is_empty()
205    }
206
207    /// Helper for deserializing a vector of `UniCase` strings.
208    fn deserialize_names<'de, D>(deserializer: D) -> Result<Vec<UniCase<String>>, D::Error>
209    where
210        D: Deserializer<'de>,
211    {
212        let names: Vec<String> = Vec::deserialize(deserializer)?;
213        Ok(Self::preprocess_names(names))
214    }
215
216    /// Helper for serializing a vector of `UniCase` strings.
217    fn serialize_names<S>(names: &Vec<UniCase<String>>, serializer: S) -> Result<S::Ok, S::Error>
218    where
219        S: Serializer,
220    {
221        names.iter().map(|u| u.deref().to_string()).collect_vec().serialize(serializer)
222    }
223
224    /// Sort the names using case-insensitive sort.
225    fn preprocess_names(names: impl IntoIterator<Item = impl AsRef<str>>) -> Vec<UniCase<String>> {
226        names.into_iter().map(|name| UniCase::new(name.as_ref().to_string())).sorted().collect()
227    }
228}
229
230/// Represents a single font file, which contains one or more [`Typeface`]s.
231#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)]
232pub struct Asset {
233    /// Asset identifier. Should be a valid file name, e.g. `"Roboto-Regular.ttf`.
234    pub file_name: String,
235    /// Where to find the file
236    pub location: AssetLocation,
237    /// List of typefaces in the file
238    pub typefaces: Vec<Typeface>,
239}
240
241impl Asset {
242    /// If the asset represents a local file, returns the package-relative path to the file.
243    /// Otherwise, returns `None`.
244    pub fn local_path(&self) -> Option<PathBuf> {
245        match &self.location {
246            AssetLocation::LocalFile(locator) => {
247                Some(locator.directory.join(self.file_name.clone()))
248            }
249            _ => None,
250        }
251    }
252}
253
254/// Describes the location of a font asset (excluding its file name).
255#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, Ord, PartialOrd)]
256pub enum AssetLocation {
257    /// Indicates that the file is accessible through a file path in the font server's namespace
258    /// (e.g. at `/config/data/fonts/`).
259    #[serde(rename = "local")]
260    LocalFile(LocalFileLocator),
261    /// Indicates that the file is accessible in a separate font package (e.g.
262    /// `fuchsia-pkg://fuchsia.com/font-package-roboto-regular-ttf`).
263    #[serde(rename = "package")]
264    Package(PackageLocator),
265}
266
267/// Describes the location of a local file asset. Used in conjunction with [`Asset::file_name`].
268#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, Ord, PartialOrd)]
269pub struct LocalFileLocator {
270    /// Package-relative path to the file, excluding the file name
271    pub directory: PathBuf,
272}
273
274/// Describes the location of a font asset that's part of a Fuchsia package. Used in conjunction
275/// with [`Asset::file_name`].
276#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, Ord, PartialOrd)]
277pub struct PackageLocator {
278    /// URL of just the package (not including the file name)
279    pub url: AbsolutePackageUrl,
280}
281
282/// Describes a single typeface within a font file
283#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq, Hash)]
284pub struct Typeface {
285    /// Index of the typeface in the file. If the file only contains a single typeface, this will
286    /// always be `0`.
287    #[serde(default = "Typeface::default_index")]
288    pub index: u32,
289
290    /// List of languages that the typeface supports, in BCP-47 format.
291    ///
292    /// Example: `["en", "zh-Hant", "sr-Cyrl"]`
293    #[serde(default = "Typeface::default_languages", skip_serializing_if = "Vec::is_empty")]
294    pub languages: Vec<String>,
295
296    /// Text style of the typeface.
297    #[serde(flatten)]
298    pub style: Style,
299
300    /// List of Unicode code points supported by the typeface.
301    #[serde(with = "code_points_serde")]
302    pub code_points: CharSet,
303
304    /// The typeface's unique "Postscript name".
305    ///
306    /// This may be absent in manifests converted from v1, in which case it will need to be filled
307    /// in when the font server starts up.
308    #[serde(default)]
309    pub postscript_name: Option<String>,
310
311    /// The typeface's unique "full name".
312    ///
313    /// This may be absent in manifests converted from v1, in which case it may be filled in when
314    /// the font server starts up.
315    #[serde(default)]
316    pub full_name: Option<String>,
317}
318
319impl Typeface {
320    fn default_index() -> u32 {
321        0
322    }
323
324    fn default_languages() -> Vec<String> {
325        vec![]
326    }
327}
328
329/// Font provider service settings.
330#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq, Hash)]
331pub struct Settings {
332    /// Maximum size of the in-memory font asset cache, in bytes.
333    #[serde(default)]
334    pub cache_size_bytes: Option<u64>,
335}
336
337impl Settings {
338    /// Returns `true` if all of the settings are unset.
339    fn is_empty(&self) -> bool {
340        self == &Settings::default()
341    }
342}
343
344/// Used for de/serializing a `CharSet`.
345mod code_points_serde {
346    use super::*;
347
348    pub fn deserialize<'d, D>(deserializer: D) -> Result<CharSet, D::Error>
349    where
350        D: Deserializer<'d>,
351    {
352        let offset_string = OffsetString::deserialize(deserializer)?;
353        CharSet::try_from(offset_string).map_err(|e| D::Error::custom(format!("{:?}", e)))
354    }
355
356    pub fn serialize<S>(code_points: &CharSet, serializer: S) -> Result<S::Ok, S::Error>
357    where
358        S: Serializer,
359    {
360        let offset_string: OffsetString = code_points.into();
361        offset_string.serialize(serializer)
362    }
363}
364
365/// Reference to a typeface, for use in specifying a fallback order.
366#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, Ord, PartialOrd)]
367pub struct TypefaceId {
368    /// File name of the asset.
369    pub file_name: String,
370    /// Index of the typeface in the file.
371    ///
372    /// It is important to allow selecting specific indices rather than including all of the file's
373    /// indices in order because some files can contain typefaces from multiple font families, and a
374    /// product owner may wish to assign them different priorities in the fallback chain.
375    #[serde(default = "Typeface::default_index")]
376    pub index: u32,
377}
378
379impl TypefaceId {
380    pub fn new(file_name: impl Into<String>, index: u32) -> Self {
381        TypefaceId { file_name: file_name.into(), index }
382    }
383}
384
385/// Describes a typeface's style properties. Equivalent to [`fidl_fuchsia_fonts::Style2`], but all
386/// fields are required.
387#[allow(missing_docs)]
388#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)]
389pub struct Style {
390    #[serde(default = "Style::default_slant", with = "SlantDef")]
391    pub slant: Slant,
392    #[serde(default = "Style::default_weight")]
393    pub weight: u16,
394    #[serde(default = "Style::default_width", with = "WidthDef")]
395    pub width: Width,
396}
397
398impl Default for Style {
399    fn default() -> Self {
400        Self {
401            slant: Style::default_slant(),
402            weight: Style::default_weight(),
403            width: Style::default_width(),
404        }
405    }
406}
407
408impl Style {
409    fn default_slant() -> Slant {
410        Slant::Upright
411    }
412
413    fn default_weight() -> u16 {
414        WEIGHT_NORMAL
415    }
416
417    fn default_width() -> Width {
418        Width::Normal
419    }
420}