1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
// 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::AbsolutePackageUrl,
    itertools::Itertools,
    offset_string::OffsetString,
    serde::{
        de::{Deserializer, Error as DeError},
        ser::Serializer,
    },
    serde::{Deserialize, Serialize},
    std::{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: AbsolutePackageUrl,
}

/// Describes a single typeface within a font file
#[derive(Clone, Debug, Default, 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,

    /// The typeface's unique "Postscript name".
    ///
    /// This may be absent in manifests converted from v1, in which case it will need to be filled
    /// in when the font server starts up.
    #[serde(default)]
    pub postscript_name: Option<String>,

    /// The typeface's unique "full name".
    ///
    /// This may be absent in manifests converted from v1, in which case it may be filled in when
    /// the font server starts up.
    #[serde(default)]
    pub full_name: Option<String>,
}

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 Default for Style {
    fn default() -> Self {
        Self {
            slant: Style::default_slant(),
            weight: Style::default_weight(),
            width: Style::default_width(),
        }
    }
}

impl Style {
    fn default_slant() -> Slant {
        Slant::Upright
    }

    fn default_weight() -> u16 {
        WEIGHT_NORMAL
    }

    fn default_width() -> Width {
        Width::Normal
    }
}