blob: 2d105879092a21c58513a3b9b66bd945aa90a3a0 [file] [log] [blame]
// Copyright 2018 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.
use {
super::{
asset::{AssetCollection, AssetCollectionBuilder},
family::{FamilyOrAlias, FontFamily},
typeface::{Collection as TypefaceCollection, Typeface},
FontService,
},
anyhow::{format_err, Error},
clonable_error::ClonableError,
font_info::FontInfoLoaderImpl,
fuchsia_syslog::fx_vlog,
manifest::{v2, FontManifestWrapper, FontsManifest},
std::{
collections::BTreeMap,
convert::TryFrom,
path::{Path, PathBuf},
sync::Arc,
},
thiserror::Error,
unicase::UniCase,
};
/// Builder for [`FontService`]. Allows populating the fields that remain immutable for the
/// lifetime of the service.
///
/// Create a new builder with [`new()`](FontServiceBuilder::new), then populate using
/// [`load_manifest()`](FontServiceBuilder::load_manifest), and finally construct a `FontService`
/// using [`build()`](FontServiceBuilder::build).
pub struct FontServiceBuilder {
manifests: Vec<ManifestOrPath>,
assets: AssetCollectionBuilder,
/// Maps the font family name from the manifest (`families.family`) to a FamilyOrAlias.
families: BTreeMap<UniCase<String>, FamilyOrAlias>,
fallback_collection: TypefaceCollection,
}
impl FontServiceBuilder {
/// Creates a new, empty builder.
pub fn new() -> Self {
FontServiceBuilder {
manifests: vec![],
assets: AssetCollectionBuilder::new(),
families: BTreeMap::new(),
fallback_collection: TypefaceCollection::new(),
}
}
/// Add a manifest path to be parsed and processed.
pub fn add_manifest_from_file(&mut self, manifest_path: &Path) -> &mut Self {
self.manifests.push(ManifestOrPath::Path(manifest_path.to_path_buf()));
self
}
/// Adds a parsed manifest to be processed.
#[allow(dead_code)]
#[cfg(test)]
pub fn add_manifest(&mut self, manifest_wrapper: FontManifestWrapper) -> &mut Self {
self.manifests.push(ManifestOrPath::Manifest(manifest_wrapper));
self
}
/// Tries to build a [`FontService`] from the provided manifests, with some additional error checking.
pub async fn build(mut self) -> Result<FontService, Error> {
let manifests: Result<Vec<(FontManifestWrapper, Option<PathBuf>)>, Error> = self
.manifests
.drain(..)
.map(|manifest_or_path| match manifest_or_path {
ManifestOrPath::Manifest(manifest) => Ok((manifest, None)),
ManifestOrPath::Path(path) => {
fx_vlog!(1, "Loading manifest {:?}", &path);
Ok((FontsManifest::load_from_file(&path)?, Some(path)))
}
})
.collect();
for (wrapper, path) in manifests? {
match wrapper {
FontManifestWrapper::Version1(v1) => {
self.add_fonts_from_manifest_v1(v1, path).await?
}
FontManifestWrapper::Version2(v2) => {
self.add_fonts_from_manifest_v2(v2, path).await?
}
}
}
if self.fallback_collection.is_empty() {
return Err(FontServiceBuilderError::NoFallbackCollection.into());
}
Ok(FontService {
assets: self.assets.build(),
families: self.families,
fallback_collection: self.fallback_collection,
})
}
async fn add_fonts_from_manifest_v2(
&mut self,
manifest: v2::FontsManifest,
manifest_path: Option<PathBuf>,
) -> Result<(), Error> {
for mut manifest_family in manifest.families {
// Register the family itself
let family_name = UniCase::new(manifest_family.name.clone());
let family = match self.families.entry(family_name.clone()).or_insert_with(|| {
FamilyOrAlias::Family(FontFamily::new(
family_name.to_string(),
manifest_family.generic_family,
))
}) {
FamilyOrAlias::Family(f) => f,
FamilyOrAlias::Alias(_, _) => {
return Err(FontServiceBuilderError::AliasFamilyConflict {
conflicting_name: family_name.to_string(),
manifest_path: manifest_path.clone(),
}
.into());
}
};
// Register the family's assets.
// We have to use `.drain()` here in order to leave `manifest_family` in a valid state
// to be able to keep using it further down.
for manifest_asset in manifest_family.assets.drain(..) {
let asset_id = self.assets.add_or_get_asset_id(&manifest_asset);
for manifest_typeface in manifest_asset.typefaces {
if manifest_typeface.code_points.is_empty() {
return Err(FontServiceBuilderError::NoCodePoints {
asset_name: manifest_asset.file_name.to_string(),
typeface_idx: manifest_typeface.index,
manifest_path: manifest_path.clone(),
}
.into());
}
let typeface = Arc::new(Typeface::new(
asset_id,
manifest_typeface,
manifest_family.generic_family,
)?);
family.faces.add_typeface(typeface.clone());
if manifest_family.fallback {
self.fallback_collection.add_typeface(typeface);
}
}
}
// Above, we're working with `family` mutably borrowed from `self.families`. We have to
// finish using any mutable references to `self.families` before we can create further
// references to `self.families` below.
// Register aliases
let aliases = FamilyOrAlias::aliases_from_family(&manifest_family);
for (key, value) in aliases {
match self.families.get(&key) {
None => {
self.families.insert(key, value);
}
Some(FamilyOrAlias::Family(_)) => {
return Err(FontServiceBuilderError::AliasFamilyConflict {
conflicting_name: key.to_string(),
manifest_path: manifest_path.clone(),
}
.into());
}
Some(FamilyOrAlias::Alias(other_family_name, _)) => {
// If the alias exists then it must be for the same font family.
if *other_family_name != family_name {
return Err(FontServiceBuilderError::AmbiguousAlias {
alias: key.to_string(),
canonical_1: other_family_name.to_string(),
canonical_2: family_name.to_string(),
manifest_path: manifest_path.clone(),
}
.into());
}
}
}
}
}
Ok(())
}
async fn add_fonts_from_manifest_v1(
&mut self,
manifest_v1: FontsManifest,
manifest_path: Option<PathBuf>,
) -> Result<(), Error> {
let manifest_v2 = self.convert_manifest_v1_to_v2(manifest_v1).await.map_err(|e| {
FontServiceBuilderError::ConversionFromV1 {
manifest_path: manifest_path.clone(),
cause: Error::from(e).into(),
}
})?;
self.add_fonts_from_manifest_v2(manifest_v2, manifest_path).await
}
/// Converts data format from manifest v1 to v2 and loads character sets for any typefaces that
/// lack them.
async fn convert_manifest_v1_to_v2(
&self,
manifest_v1: FontsManifest,
) -> Result<v2::FontsManifest, Error> {
let mut manifest_v2 = v2::FontsManifest::try_from(manifest_v1)?;
let font_info_loader = FontInfoLoaderImpl::new()?;
for manifest_family in &mut manifest_v2.families {
for manifest_asset in &mut manifest_family.assets {
for manifest_typeface in &mut manifest_asset.typefaces {
if manifest_typeface.code_points.is_empty() {
match &manifest_asset.location {
v2::AssetLocation::LocalFile(v2::LocalFileLocator { directory }) => {
let asset_path = directory.join(&manifest_asset.file_name);
let buffer = AssetCollection::load_asset_to_vmo(&asset_path)?;
let font_info = font_info_loader
.load_font_info(buffer, manifest_typeface.index)?;
manifest_typeface.code_points = font_info.char_set;
}
_ => {
return Err(format_err!(
"Impossible asset location: {:?}",
&manifest_asset
));
}
}
}
}
}
}
Ok(manifest_v2)
}
}
#[allow(dead_code)]
enum ManifestOrPath {
Manifest(FontManifestWrapper),
Path(PathBuf),
}
/// Errors arising from the use of [`FontServiceBuilder`].
#[derive(Debug, Error)]
pub enum FontServiceBuilderError {
/// A name was used as both a canonical family name and a font family alias.
#[error(
"Conflict in {:?}: {} cannot be both a canonical family name and an alias",
manifest_path,
conflicting_name
)]
AliasFamilyConflict { conflicting_name: String, manifest_path: Option<PathBuf> },
/// One string was used as an alias for two different font families.
#[error(
"Conflict in {:?}: {} cannot be an alias for both {} and {}",
manifest_path,
alias,
canonical_1,
canonical_2
)]
AmbiguousAlias {
alias: String,
canonical_1: String,
canonical_2: String,
manifest_path: Option<PathBuf>,
},
/// Something went wrong when converting a manifest from v1 to v2.
#[error("Conversion from manifest v1 failed in {:?}: {:?}", manifest_path, cause)]
ConversionFromV1 {
manifest_path: Option<PathBuf>,
#[source]
cause: ClonableError,
},
/// None of the loaded manifests contained any families designated as `fallback: true`.
#[error("Need at least one fallback font family")]
NoFallbackCollection,
/// The manifest did not have defined code points for a particular typeface.
#[error("Missing code points for \"{}\"[{}] in {:?}", asset_name, typeface_idx, manifest_path)]
NoCodePoints { asset_name: String, typeface_idx: u32, manifest_path: Option<PathBuf> },
}