manifest/v2.rs
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
// 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::*;
use anyhow::{ensure, Error};
use char_set::CharSet;
use fidl_fuchsia_fonts::{GenericFontFamily, Slant, Width, WEIGHT_NORMAL};
use fuchsia_url::AbsolutePackageUrl;
use itertools::Itertools;
use offset_string::OffsetString;
use serde::de::{Deserializer, Error as DeError};
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
use std::iter;
use std::ops::Deref;
use std::path::PathBuf;
use 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
}
}