blob: 3d1e3fb03024778c288f5eeda05d39fc2bb38960 [file] [log] [blame]
// 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
}
}