| // 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. |
| |
| #![allow(deprecated)] |
| |
| use { |
| crate::{ |
| constants::PKG_URL_PREFIX, |
| font_catalog as fc, |
| font_catalog::{AssetInFamilyIndex, FamilyIndex}, |
| product_config, FallbackChainEntry, FontCatalog, FontPackageListing, FontSet, FontSets, |
| ProductConfig, TypefaceInAssetIndex, |
| }, |
| anyhow::{format_err, Error}, |
| char_set::CharSet, |
| font_info::{FontAssetSource, FontInfo, FontInfoLoader}, |
| fuchsia_url::AbsolutePackageUrl, |
| itertools::{Either, Itertools}, |
| manifest::v2, |
| std::{ |
| collections::{BTreeMap, BTreeSet}, |
| fmt, |
| path::{Path, PathBuf}, |
| }, |
| thiserror::Error, |
| }; |
| |
| /// Index of a font asset within a font family, combined with the index of the family within the |
| /// `families` table. |
| type AssetKey = (FamilyIndex, AssetInFamilyIndex); |
| /// Consists of the asset name and the index of the typeface within that asset file. |
| type TypefaceKey = (String, TypefaceInAssetIndex); |
| |
| /// Collection of font metadata used for generating a font manifest for a particular target. |
| /// |
| /// Contains indices by family and asset names, which all provide access only to those fonts that |
| /// are included in `font_sets`. (`font_catalog` may contain other fonts that are not included in |
| /// the target product.) |
| /// |
| /// For test coverage, please see the integration tests. |
| #[derive(Debug)] |
| pub struct FontDb { |
| /// Font catalog, loaded from one or more .font_catalog.json files. |
| font_catalog: FontCatalog, |
| /// Indicates which fonts are local and which are downloadable. See [crate::font_sets]. |
| font_sets: FontSets, |
| /// Loaded (with some transformation) from a .fontcfg.json5 file. |
| product_config: ProductConfig, |
| |
| /// Map from font family name to index in the |
| /// [`families`](crate::font_catalog::FontCatalog::families) table. |
| family_name_to_family: BTreeMap<String, FamilyIndex>, |
| /// Map from asset (file) name to asset keys. There are potentially multiple asset keys because |
| /// a single font file can contain typefaces from more than one font family. |
| asset_name_to_assets: BTreeMap<String, BTreeSet<AssetKey>>, |
| /// Map from asset name to the Fuchsia package URL that contains that asset (for downloadable |
| /// font packages). |
| asset_name_to_pkg_url: BTreeMap<String, AbsolutePackageUrl>, |
| /// Map from a typeface to metadata extracted from the typeface (code points, Postscript name, |
| /// etc.). |
| typeface_to_metadata: BTreeMap<TypefaceKey, TypefaceMetatada>, |
| /// An ordered list of `TypefaceId`s, generated by resolving each [`FallbackChainEntry`] in |
| /// [`ProductConfig::fallback_chain`]. |
| explicit_fallback_chain: Vec<v2::TypefaceId>, |
| |
| /// The absolute path to the directory which will contain all the local fonts |
| /// for the requested target. |
| target_asset_dir: PathBuf, |
| } |
| |
| impl FontDb { |
| /// Tries to create a new instance of `FontDb`. |
| pub fn new<P: AsRef<Path>, Q: AsRef<Path>>( |
| font_catalog: FontCatalog, |
| font_pkgs: FontPackageListing, |
| font_sets: FontSets, |
| product_config: ProductConfig, |
| font_info_loader: impl FontInfoLoader, |
| font_dir: P, |
| target_asset_dir: Q, |
| ) -> Result<FontDb, FontDbErrors> { |
| let mut family_name_to_family = BTreeMap::new(); |
| let mut asset_name_to_assets = BTreeMap::new(); |
| let mut asset_name_to_pkg_url = BTreeMap::new(); |
| let mut full_name_to_typeface = BTreeMap::new(); |
| |
| let mut errors: Vec<FontDbError> = vec![]; |
| |
| for (family_idx, family) in (&font_catalog.families).iter().enumerate() { |
| let family_idx = FamilyIndex(family_idx); |
| let mut asset_count = 0; |
| for (asset_idx, asset) in (&family.assets).iter().enumerate() { |
| let asset_idx = AssetInFamilyIndex(asset_idx); |
| let asset_name = asset.file_name.clone(); |
| if asset.typefaces.is_empty() { |
| errors.push(FontDbError::FontCatalogNoTypeFaces { asset_name }); |
| continue; |
| } |
| |
| if font_sets.get_font_set(&asset.file_name).is_some() { |
| let safe_name = font_pkgs.get_safe_name(&asset_name); |
| if safe_name.is_none() { |
| errors.push(FontDbError::FontPkgsMissingEntry { asset_name }); |
| continue; |
| } |
| |
| let pkg_url = Self::make_pkg_url(safe_name.unwrap()); |
| if let Err(error) = pkg_url { |
| errors.push(error); |
| continue; |
| } |
| let pkg_url = pkg_url.unwrap(); |
| |
| asset_name_to_assets |
| .entry(asset_name.clone()) |
| .or_insert_with(|| BTreeSet::new()) |
| .insert((family_idx, asset_idx)); |
| asset_name_to_pkg_url.insert(asset_name.clone(), pkg_url); |
| |
| asset_count += 1; |
| } |
| } |
| // Skip families where no assets are included in the target product |
| if asset_count > 0 { |
| family_name_to_family.insert(family.name.clone(), family_idx); |
| } |
| } |
| |
| // typeface_to_metadata and explicit_fallback_chain are empty at this point. |
| let mut db = FontDb { |
| font_catalog, |
| font_sets, |
| product_config, |
| family_name_to_family, |
| asset_name_to_assets, |
| asset_name_to_pkg_url, |
| typeface_to_metadata: BTreeMap::new(), |
| explicit_fallback_chain: Vec::new(), |
| target_asset_dir: target_asset_dir.as_ref().to_path_buf(), |
| }; |
| |
| // Read metadata from font files, validate, and index it. |
| { |
| let font_infos = Self::load_font_infos(&db, &font_pkgs, font_info_loader, font_dir); |
| match font_infos { |
| Ok(font_infos) => { |
| for (request, font_info) in font_infos { |
| // load_font_infos already asserted that postscript_name is populated |
| let postscript_name = font_info.postscript_name.expect("postscript_name"); |
| let typeface_key = |
| (request.asset_name(), TypefaceInAssetIndex(request.index)); |
| db.typeface_to_metadata.insert( |
| typeface_key.clone(), |
| TypefaceMetatada { |
| code_points: font_info.char_set, |
| postscript_name: postscript_name.clone(), |
| full_name: font_info.full_name.clone(), |
| }, |
| ); |
| if let Some(full_name) = font_info.full_name { |
| full_name_to_typeface.insert(full_name, typeface_key); |
| } |
| } |
| } |
| Err(mut font_info_errors) => { |
| errors.append(&mut font_info_errors); |
| } |
| } |
| } |
| |
| // Resolve and validate fallback chain entries from the .fontcfg.json5 file. |
| { |
| let mut fallback_typeface_counts: BTreeMap<v2::TypefaceId, usize> = BTreeMap::new(); |
| let (mut fallback_chain_typeface_ids, mut fallback_chain_errors): (Vec<_>, Vec<_>) = db |
| .iter_unprocessed_fallback_chain(&full_name_to_typeface) |
| .map(|typeface_id| { |
| match typeface_id { |
| Ok(typeface_id) => { |
| *fallback_typeface_counts.entry(typeface_id.clone()).or_insert(0) += 1; |
| // Confirm that the TypefaceId in the fallback chain actually exists in |
| // the database. |
| if db.get_assets_by_name(&typeface_id.file_name).iter().any(|asset| { |
| asset.typefaces.contains_key(&typeface_id.index.into()) |
| }) { |
| Ok(typeface_id) |
| } else { |
| Err(FontDbError::UnknownFallbackChainEntry { |
| entry: typeface_id.clone().into(), |
| }) |
| } |
| } |
| Err(err) => Err(err), |
| } |
| }) |
| .partition_result(); |
| |
| db.explicit_fallback_chain.append(&mut fallback_chain_typeface_ids); |
| errors.append(&mut fallback_chain_errors); |
| |
| for (typeface_id, count) in fallback_typeface_counts.into_iter() { |
| if count > 1 { |
| errors.push(FontDbError::DuplicateFallbackChainEntry { typeface_id }) |
| } |
| } |
| } |
| |
| if errors.is_empty() { |
| Ok(db) |
| } else { |
| Err(FontDbErrors(errors.into())) |
| } |
| } |
| |
| pub fn get_family_by_name(&self, family_name: impl AsRef<str>) -> Option<&fc::Family> { |
| let family_idx = self.family_name_to_family.get(family_name.as_ref())?; |
| self.font_catalog.families.get(family_idx.0) |
| } |
| |
| /// Get all [`Asset`]s with the given file name. There may be more than one instance if the |
| /// asset appears in multiple font families. |
| pub fn get_assets_by_name(&self, asset_name: impl AsRef<str>) -> Vec<&fc::Asset> { |
| self.asset_name_to_assets |
| .get(asset_name.as_ref()) |
| // Iterate over the 0 or 1 values inside Option |
| .iter() |
| .flat_map(|asset_keys| asset_keys.iter()) |
| .flat_map(move |(family_idx, asset_idx)| { |
| self.font_catalog |
| .families |
| .get(family_idx.0) |
| .and_then(|family| family.get_asset(*asset_idx)) |
| }) |
| .collect_vec() |
| } |
| |
| /// The asset must be in the `FontDb` or this method will panic. |
| pub fn get_typeface_metadata( |
| &self, |
| asset: &fc::Asset, |
| index: TypefaceInAssetIndex, |
| ) -> &TypefaceMetatada { |
| // Alas, no sane way to transpose between `(&str, &x)` and `&(String, x)`. |
| let key = (asset.file_name.to_owned(), index); |
| &self |
| .typeface_to_metadata |
| .get(&key) |
| .ok_or_else(|| format_err!("No code points for {:?}", &key)) |
| .unwrap() |
| } |
| |
| /// The asset must be in the `FontDb` or this method will panic. |
| pub fn get_asset_location(&self, asset: &fc::Asset) -> v2::AssetLocation { |
| match self.font_sets.get_font_set(&*asset.file_name).unwrap() { |
| FontSet::Local => v2::AssetLocation::LocalFile(v2::LocalFileLocator { |
| directory: self.target_asset_dir.clone(), |
| }), |
| FontSet::Download => v2::AssetLocation::Package(v2::PackageLocator { |
| url: self.asset_name_to_pkg_url.get(&*asset.file_name).unwrap().clone(), |
| }), |
| } |
| } |
| |
| /// Iterates over all the _included_ font families in the `FontDb`. |
| pub fn iter_families(&self) -> impl Iterator<Item = &'_ fc::Family> + '_ { |
| self.font_catalog |
| .families |
| .iter() |
| .filter(move |family| self.get_family_by_name(&*family.name).is_some()) |
| } |
| |
| /// Iterates over all the _included_ assets in the given font family. Note this is _not_ the |
| /// same as iterating over `Family::assets`. |
| pub fn iter_assets<'a>( |
| &'a self, |
| family: &'a fc::Family, |
| ) -> impl Iterator<Item = &'a fc::Asset> + 'a { |
| family |
| .assets |
| .iter() |
| .filter(move |asset| !self.get_assets_by_name(&*asset.file_name).is_empty()) |
| } |
| |
| /// Iterates over the `TypefaceId`s in the target product's fallback chain, plus those marked as |
| /// `"fallback": true` in font catalog files. |
| pub fn iter_fallback_chain<'a>(&'a self) -> impl Iterator<Item = v2::TypefaceId> + 'a { |
| // .unique() eliminates any duplicates from the legacy chain that are already in the |
| // explicit one. |
| itertools::chain(self.iter_explicit_fallback_chain(), self.iter_legacy_fallback_chain()) |
| .unique() |
| } |
| |
| /// Gets a list of `TypefaceId`s that are not used in the _explicit_ fallback chain, for |
| /// debugging. |
| pub fn iter_non_fallback_typefaces<'a>(&'a self) -> impl Iterator<Item = v2::TypefaceId> + 'a { |
| let fallback_typefaces: BTreeSet<v2::TypefaceId> = |
| self.iter_explicit_fallback_chain().collect(); |
| self.iter_families() |
| .flat_map(move |family| self.family_to_typeface_ids(family)) |
| .filter(move |typeface_id| !fallback_typefaces.contains(typeface_id)) |
| } |
| |
| /// Iterates over the `TypefaceId`s derived from the explicit font chain in the product font |
| /// config. |
| fn iter_explicit_fallback_chain<'a>(&'a self) -> impl Iterator<Item = v2::TypefaceId> + 'a { |
| self.explicit_fallback_chain.iter().map(Clone::clone) |
| } |
| |
| /// Iterates over the `FallbackChainEntry`s in the product font config's fallback chain, |
| /// fallibly converting them to `TypefaceId`. |
| fn iter_unprocessed_fallback_chain<'a>( |
| &'a self, |
| full_name_to_typeface: &'a BTreeMap<String, TypefaceKey>, |
| ) -> impl Iterator<Item = Result<v2::TypefaceId, FontDbError>> + 'a { |
| /// Converts a product config TypefaceId into one or more manifest typeface IDs |
| /// (an omitted font index expands to multiple indices). |
| fn get_manifest_typeface_ids<'b>( |
| font_db: &'b FontDb, |
| full_name_to_typeface: &'b BTreeMap<String, TypefaceKey>, |
| id: &product_config::FallbackChainEntry, |
| ) -> impl Iterator<Item = Result<v2::TypefaceId, FontDbError>> + 'b { |
| use product_config::FallbackChainEntry::*; |
| // TODO: Dynamic type acrobatics instead of collecting into `Vec`s. |
| match id { |
| FileNameAndIndex { file_name, index: Some(index) } => { |
| vec![Ok(v2::TypefaceId::new(file_name, index.0))] |
| } |
| FileNameAndIndex { file_name, index: None } => { |
| let assets = font_db.get_assets_by_name(file_name); |
| if assets.is_empty() { |
| vec![Err(FontDbError::UnknownFallbackChainEntry { entry: id.clone() })] |
| } else { |
| assets |
| .iter() |
| .flat_map(|asset| FontDb::asset_to_typeface_ids(asset).map(Ok)) |
| .collect_vec() |
| } |
| } |
| FullName(full_name) => match full_name_to_typeface.get(full_name) { |
| None => vec![Err(FontDbError::UnknownFallbackChainEntry { entry: id.clone() })], |
| Some((file_name, index)) => { |
| vec![Ok(v2::TypefaceId::new(file_name, index.0))] |
| } |
| }, |
| } |
| .into_iter() |
| } |
| |
| self.product_config |
| .iter_fallback_chain() |
| .flat_map(move |id| get_manifest_typeface_ids(self, full_name_to_typeface, id)) |
| .into_iter() |
| } |
| |
| /// Iterates over legacy fallbacks specified by `"fallback": true` in the font catalog. |
| // TODO(https://fxbug.dev/42122760): Remove this code after all product font collections and font catalogs |
| // are updated. |
| fn iter_legacy_fallback_chain<'a>(&'a self) -> impl Iterator<Item = v2::TypefaceId> + 'a { |
| self.iter_families() |
| .filter(|family| family.fallback) |
| .flat_map(move |family| self.family_to_typeface_ids(family)) |
| .into_iter() |
| } |
| |
| fn family_to_typeface_ids<'a>( |
| &'a self, |
| family: &'a fc::Family, |
| ) -> impl Iterator<Item = v2::TypefaceId> + 'a { |
| self.iter_assets(family).flat_map(FontDb::asset_to_typeface_ids) |
| } |
| |
| fn asset_to_typeface_ids<'a>( |
| asset: &'a fc::Asset, |
| ) -> impl Iterator<Item = v2::TypefaceId> + 'a { |
| let file_name = asset.file_name.clone(); |
| asset |
| .typefaces |
| .keys() |
| .map(move |key| v2::TypefaceId { file_name: file_name.clone(), index: key.0 }) |
| } |
| |
| fn make_pkg_url(safe_name: impl AsRef<str>) -> Result<AbsolutePackageUrl, FontDbError> { |
| let pkg_url = format!("{}{}", PKG_URL_PREFIX, safe_name.as_ref()); |
| Ok(AbsolutePackageUrl::parse(&pkg_url) |
| .map_err(|error| FontDbError::PkgUrl { error: error.into() })?) |
| } |
| |
| fn load_font_infos( |
| db: &FontDb, |
| font_pkgs: &FontPackageListing, |
| font_info_loader: impl FontInfoLoader, |
| font_dir: impl AsRef<Path>, |
| ) -> Result<Vec<(FontInfoRequest, FontInfo)>, Vec<FontDbError>> { |
| let (requests, errors): (Vec<_>, Vec<_>) = db |
| .font_sets |
| .iter() |
| .map(|(asset_name, _)| { |
| Self::asset_to_font_info_requests(db, font_pkgs, font_dir.as_ref(), asset_name) |
| }) |
| .flatten() |
| .partition_map(|r| match r { |
| Ok(data) => Either::Left(data), |
| Err(err) => Either::Right(err), |
| }); |
| |
| if !errors.is_empty() { |
| return Err(errors); |
| } |
| |
| let (font_infos, errors): (Vec<_>, Vec<_>) = requests |
| .into_iter() |
| .map(|request| { |
| let source = FontAssetSource::FilePath(request.path.to_str().unwrap().to_owned()); |
| let font_info = font_info_loader.load_font_info(source, request.index); |
| match font_info { |
| Ok(font_info) => { |
| if font_info.postscript_name.is_some() { |
| Ok((request, font_info)) |
| } else { |
| Err(FontDbError::FontInfoMissingPostscriptName { request }) |
| } |
| } |
| Err(error) => Err(FontDbError::FontInfo { request, error }), |
| } |
| }) |
| .partition_map(|r| match r { |
| Ok(data) => Either::Left(data), |
| Err(err) => Either::Right(err), |
| }); |
| |
| if !errors.is_empty() { |
| Err(errors) |
| } else { |
| Ok(font_infos) |
| } |
| } |
| |
| fn asset_to_font_info_requests( |
| db: &FontDb, |
| font_pkgs: &FontPackageListing, |
| font_dir: impl AsRef<Path>, |
| asset_name: &str, |
| ) -> Vec<Result<FontInfoRequest, FontDbError>> { |
| let mut path = font_dir.as_ref().to_path_buf(); |
| |
| let path_prefix = font_pkgs.get_path_prefix(asset_name); |
| if path_prefix.is_none() { |
| return vec![Err(FontDbError::FontPkgsMissingEntry { |
| asset_name: asset_name.to_owned(), |
| })]; |
| } |
| |
| let path_prefix = path_prefix.unwrap(); |
| path.push(path_prefix); |
| path.push(asset_name); |
| |
| // We have to collect into a vector here because otherwise there's no way to return a |
| // consistent `Iterator` type. |
| let requests = db |
| .get_assets_by_name(asset_name) |
| .iter() |
| .flat_map(|asset| asset.typefaces.keys()) |
| .map(move |index| Ok(FontInfoRequest { path: path.clone(), index: index.0 })) |
| .collect_vec(); |
| |
| if requests.is_empty() { |
| vec![Err(FontDbError::FontCatalogMissingEntry { asset_name: asset_name.to_owned() })] |
| } else { |
| requests |
| } |
| } |
| } |
| |
| /// Collection of errors from loading / building `FontDb`. |
| #[derive(Debug, Error)] |
| #[error("Errors occurred while building FontDb: {}", _0)] |
| pub struct FontDbErrors(FontDbErrorVec); |
| |
| #[derive(Debug)] |
| pub struct FontDbErrorVec(Vec<FontDbError>); |
| |
| impl From<Vec<FontDbError>> for FontDbErrorVec { |
| fn from(errors: Vec<FontDbError>) -> Self { |
| FontDbErrorVec(errors) |
| } |
| } |
| |
| impl From<FontDbErrors> for Vec<FontDbError> { |
| fn from(wrapper: FontDbErrors) -> Self { |
| (wrapper.0).0 |
| } |
| } |
| |
| impl fmt::Display for FontDbErrorVec { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let descriptions = self.0.iter().map(|e| format!("{}", e)).collect_vec(); |
| write!(f, "{:#?}", descriptions) |
| } |
| } |
| |
| /// An error in a single `FontDb` operation. |
| #[derive(Debug, Error)] |
| pub enum FontDbError { |
| #[error("Asset {} has no typefaces", asset_name)] |
| FontCatalogNoTypeFaces { asset_name: String }, |
| |
| #[error("Asset {} is not listed in *.font_pkgs.json", asset_name)] |
| FontPkgsMissingEntry { asset_name: String }, |
| |
| #[error("Asset {} is not listed in *.font_catalog.json", asset_name)] |
| FontCatalogMissingEntry { asset_name: String }, |
| |
| #[error("Fallback asset {:?} is unknown", entry)] |
| UnknownFallbackChainEntry { entry: FallbackChainEntry }, |
| |
| #[error("Duplicate entries in fallback chain for {}, index {}", |
| typeface_id.file_name, typeface_id.index)] |
| DuplicateFallbackChainEntry { typeface_id: v2::TypefaceId }, |
| |
| #[error("PkgUrl error: {:?}", error)] |
| PkgUrl { error: Error }, |
| |
| #[error("Failed to load font info for {:?}: {:?}", request, error)] |
| FontInfo { request: FontInfoRequest, error: Error }, |
| |
| #[error("Font info for {:?} is missing postscript_name", request)] |
| FontInfoMissingPostscriptName { request: FontInfoRequest }, |
| } |
| |
| /// Metadata needed for [`FontInfoLoader::load_font_info`]. |
| #[derive(Debug, Clone)] |
| pub struct FontInfoRequest { |
| /// Path to the file |
| path: PathBuf, |
| /// Index of the font in the file |
| index: u32, |
| } |
| |
| impl FontInfoRequest { |
| fn asset_name(&self) -> String { |
| self.path.file_name().and_then(|os_str| os_str.to_str()).unwrap().to_owned() |
| } |
| } |
| |
| /// Metadata about a typeface, extracted from the actual typeface file and converted from |
| /// `FontInfo`. |
| #[derive(Debug)] |
| pub struct TypefaceMetatada { |
| pub code_points: CharSet, |
| pub postscript_name: String, |
| pub full_name: Option<String>, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| constants, |
| fake_font_info_loader::FakeFontInfoLoaderImpl, |
| font_catalog::{Asset, Family, Typeface}, |
| font_pkgs::FontPackageEntry, |
| product_config::Settings, |
| }, |
| assert_matches::assert_matches, |
| fidl_fuchsia_fonts::{GenericFontFamily, Slant, Width}, |
| manifest::v2::Style, |
| maplit::btreemap, |
| }; |
| |
| fn make_font_db_contents() -> (FontCatalog, FontPackageListing, FontSets) { |
| let font_catalog = FontCatalog { |
| families: vec![Family { |
| name: "Alpha Sans".to_string(), |
| aliases: vec![], |
| generic_family: Some(GenericFontFamily::SansSerif), |
| fallback: true, |
| assets: vec![Asset { |
| file_name: "AlphaSans-Regular.ttc".to_string(), |
| typefaces: btreemap! { |
| TypefaceInAssetIndex(0) => Typeface { |
| index: 0, |
| languages: vec!["en".to_string()], |
| style: Style { |
| slant: Slant::Upright, |
| weight: 400, |
| width: Width::Normal, |
| }, |
| }, |
| TypefaceInAssetIndex(2) => Typeface { |
| index: 2, |
| languages: vec!["ru".to_string()], |
| style: Style { |
| slant: Slant::Upright, |
| weight: 400, |
| width: Width::Normal, |
| }, |
| }, |
| }, |
| }], |
| }], |
| }; |
| |
| let font_pkgs = FontPackageListing::new(btreemap! { |
| "AlphaSans-Regular.ttc".to_string() => |
| FontPackageEntry::new( |
| "AlphaSans-Regular.ttc", |
| "alphasans-regular-ttc", |
| "alphasans/", |
| ), |
| }); |
| |
| let font_sets = FontSets::new(btreemap! { |
| "AlphaSans-Regular.ttc".to_string() => FontSet::Local, |
| }); |
| |
| (font_catalog, font_pkgs, font_sets) |
| } |
| |
| /// The fallback chain references an unknown asset with an index. |
| #[test] |
| fn test_unknown_fallback_asset_with_index() -> Result<(), Error> { |
| let (font_catalog, font_pkgs, font_sets) = make_font_db_contents(); |
| |
| let product_config = ProductConfig { |
| fallback_chain: vec![FallbackChainEntry::with_index("BetaSans-Regular.ttc", 0)], |
| settings: Settings { cache_size_bytes: None }, |
| }; |
| |
| let result = FontDb::new( |
| font_catalog, |
| font_pkgs, |
| font_sets, |
| product_config, |
| FakeFontInfoLoaderImpl::new(), |
| "foo/bar", |
| constants::LOCAL_ASSET_DIRECTORY, |
| ); |
| |
| assert!(result.is_err()); |
| let errors: Vec<FontDbError> = result.unwrap_err().into(); |
| assert_matches!(errors[0], FontDbError::UnknownFallbackChainEntry { .. }); |
| |
| Ok(()) |
| } |
| |
| /// The fallback chain references an unknown asset without an index. |
| #[test] |
| fn test_unknown_fallback_asset() -> Result<(), Error> { |
| let (font_catalog, font_pkgs, font_sets) = make_font_db_contents(); |
| |
| let product_config = ProductConfig { |
| fallback_chain: vec![FallbackChainEntry::without_index("BetaSans-Regular.ttc")], |
| settings: Settings { cache_size_bytes: None }, |
| }; |
| |
| let result = FontDb::new( |
| font_catalog, |
| font_pkgs, |
| font_sets, |
| product_config, |
| FakeFontInfoLoaderImpl::new(), |
| "foo/bar", |
| constants::LOCAL_ASSET_DIRECTORY, |
| ); |
| |
| assert!(result.is_err()); |
| let errors: Vec<FontDbError> = result.unwrap_err().into(); |
| assert_matches!(errors[0], FontDbError::UnknownFallbackChainEntry { .. }); |
| |
| Ok(()) |
| } |
| |
| /// The fallback chain references a known asset with an unknown typeface index. |
| #[test] |
| fn test_unknown_fallback_typeface_index() -> Result<(), Error> { |
| let (font_catalog, font_pkgs, font_sets) = make_font_db_contents(); |
| |
| let product_config = ProductConfig { |
| fallback_chain: vec![FallbackChainEntry::with_index("AlphaSans-Regular.ttc", 1)], |
| settings: Settings { cache_size_bytes: None }, |
| }; |
| |
| let result = FontDb::new( |
| font_catalog, |
| font_pkgs, |
| font_sets, |
| product_config, |
| FakeFontInfoLoaderImpl::new(), |
| "foo/bar", |
| constants::LOCAL_ASSET_DIRECTORY, |
| ); |
| |
| assert!(result.is_err()); |
| let errors: Vec<FontDbError> = result.unwrap_err().into(); |
| assert_matches!(errors[0], FontDbError::UnknownFallbackChainEntry { .. }); |
| |
| Ok(()) |
| } |
| |
| /// The fallback chain references an unknown full typeface name. |
| #[test] |
| fn test_unknown_fallback_full_name() -> Result<(), Error> { |
| let (font_catalog, font_pkgs, font_sets) = make_font_db_contents(); |
| |
| let product_config = ProductConfig { |
| fallback_chain: vec![FallbackChainEntry::with_full_name("Gamma Regular")], |
| settings: Settings { cache_size_bytes: None }, |
| }; |
| |
| let result = FontDb::new( |
| font_catalog, |
| font_pkgs, |
| font_sets, |
| product_config, |
| FakeFontInfoLoaderImpl::new(), |
| "foo/bar", |
| constants::LOCAL_ASSET_DIRECTORY, |
| ); |
| |
| assert!(result.is_err()); |
| let errors: Vec<FontDbError> = result.unwrap_err().into(); |
| assert_matches!(errors[0], FontDbError::UnknownFallbackChainEntry { .. }); |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_missing_postscript_name() -> Result<(), Error> { |
| /// Omits Postscript name |
| struct NoPostscriptNameFontInfoLoader {} |
| impl FontInfoLoader for NoPostscriptNameFontInfoLoader { |
| fn load_font_info<S, E>(&self, _source: S, index: u32) -> Result<FontInfo, Error> |
| where |
| S: std::convert::TryInto<FontAssetSource, Error = E>, |
| E: Sync + Send + Into<Error>, |
| { |
| Ok(FontInfo { |
| char_set: CharSet::new(vec![0x1, 0x2]), |
| postscript_name: None, |
| full_name: Some(format!("Some Full Name {}", index)), |
| }) |
| } |
| } |
| |
| let (font_catalog, font_pkgs, font_sets) = make_font_db_contents(); |
| let product_config = ProductConfig { |
| fallback_chain: vec![FallbackChainEntry::with_index("AlphaSans-Regular.ttc", 0)], |
| settings: Settings { cache_size_bytes: None }, |
| }; |
| |
| let result = FontDb::new( |
| font_catalog, |
| font_pkgs, |
| font_sets, |
| product_config, |
| NoPostscriptNameFontInfoLoader {}, |
| "foo/bar", |
| constants::LOCAL_ASSET_DIRECTORY, |
| ); |
| |
| assert_matches!(result, Err(_)); |
| let errors: Vec<FontDbError> = result.unwrap_err().into(); |
| assert_matches!(errors[0], FontDbError::FontInfoMissingPostscriptName { .. }); |
| |
| Ok(()) |
| } |
| } |