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}