| // 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. |
| |
| #![warn(missing_docs)] |
| |
| mod asset; |
| mod builder; |
| mod family; |
| mod inspect; |
| mod typeface; |
| |
| use { |
| self::{ |
| asset::AssetCollection, |
| family::{FamilyOrAlias, FontFamily, TypefaceQueryOverrides}, |
| typeface::{Collection as TypefaceCollection, TypefaceInfoAndCharSet}, |
| }, |
| anyhow::{format_err, Context as _, Error}, |
| fidl::{self, encoding::Decodable, endpoints::ServerEnd}, |
| fidl_fuchsia_fonts::{self as fonts, CacheMissPolicy}, |
| fidl_fuchsia_fonts_experimental as fonts_exp, |
| fidl_fuchsia_fonts_ext::{ |
| FontFamilyInfoExt, RequestExt, TypefaceRequestExt, TypefaceResponseExt, |
| }, |
| fidl_fuchsia_intl::LocaleId, |
| fuchsia_async as fasync, |
| fuchsia_component::server::{ServiceFs, ServiceObj}, |
| fuchsia_syslog::*, |
| fuchsia_trace as trace, |
| futures::prelude::*, |
| itertools::Itertools, |
| std::{collections::BTreeMap, iter, sync::Arc}, |
| unicase::UniCase, |
| }; |
| |
| pub use { |
| asset::{AssetId, AssetLoader, AssetLoaderImpl}, |
| builder::FontServiceBuilder, |
| }; |
| |
| /// Get a field out of a `TypefaceRequest`'s `query` field as a reference, or returns early with a |
| /// `anyhow::Error` if the query is missing. |
| macro_rules! query_field { |
| ($request:ident, $field:ident) => { |
| $request.query.as_ref().ok_or(format_err!("Missing query"))?.$field.as_ref() |
| }; |
| } |
| |
| pub enum ProviderRequestStream { |
| Stable(fonts::ProviderRequestStream), |
| Experimental(fonts_exp::ProviderRequestStream), |
| } |
| |
| /// The result of a successful lookup of a font family by name. Combines a `FontFamily` and, |
| /// if the `FontFamilyAlias` that the client requested turned out to include |
| /// `TypefaceQueryOverrides` then also contains those overrides. |
| struct FontFamilyMatch<'a> { |
| family: &'a FontFamily, |
| overrides: Option<Arc<TypefaceQueryOverrides>>, |
| } |
| |
| /// Maintains state and handles request streams for the font server. |
| #[derive(Debug)] |
| pub struct FontService<L> |
| where |
| L: AssetLoader, |
| { |
| assets: AssetCollection<L>, |
| /// Maps the font family name from the manifest (`families[x].name`) to a FamilyOrAlias. |
| families: BTreeMap<UniCase<String>, FamilyOrAlias>, |
| fallback_collection: TypefaceCollection, |
| /// Holds Inspect data about manifests, families, and the fallback collection. |
| inspect_data: inspect::ServiceInspectData, |
| } |
| |
| impl<L> FontService<L> |
| where |
| L: AssetLoader, |
| { |
| /// Resolves a font family or alias either to itself (if it's a family), or to the canonical |
| /// family. If it's an alias and contains `TypefaceQueryOverrides`, then the resulting |
| /// `FontFamilyMatch` will contain those overrides. |
| fn resolve_alias<'a>( |
| &'a self, |
| family_or_alias: &'a FamilyOrAlias, |
| ) -> Option<FontFamilyMatch<'a>> { |
| match family_or_alias { |
| FamilyOrAlias::Family(family) => Some(FontFamilyMatch { family, overrides: None }), |
| FamilyOrAlias::Alias(name, overrides) => match self.families.get(name) { |
| Some(FamilyOrAlias::Family(family)) => { |
| Some(FontFamilyMatch { family, overrides: overrides.clone() }) |
| } |
| _ => None, |
| }, |
| } |
| } |
| |
| /// Get font family by name. |
| fn match_family(&self, family_name: &UniCase<String>) -> Option<FontFamilyMatch<'_>> { |
| self.resolve_alias(self.families.get(family_name)?) |
| } |
| |
| /// Get all font families whose name contains the requested string |
| fn match_families_substr(&self, family_name: String) -> impl Iterator<Item = &FontFamily> { |
| self.families |
| .iter() |
| .filter_map(move |(key, value)| { |
| // Note: This might not work for some non-Latin strings |
| if key.as_ref().to_lowercase().contains(&family_name.to_lowercase()) { |
| return self.resolve_alias(value).map(|matched| matched.family); |
| } |
| None |
| }) |
| .unique_by(|family| family.name.to_owned()) |
| } |
| |
| fn apply_query_overrides( |
| mut request: fonts::TypefaceRequest, |
| overrides: Arc<TypefaceQueryOverrides>, |
| ) -> fonts::TypefaceRequest { |
| match &mut request.query { |
| Some(query) => { |
| if overrides.has_style_overrides() { |
| // If query has no style at all, use the values from TypefaceQueryOverrides |
| match &mut query.style { |
| None => query.style = Some(overrides.style.clone().into()), |
| Some(style) => { |
| style.slant = style.slant.or(overrides.style.slant); |
| style.width = style.width.or(overrides.style.width); |
| style.weight = style.weight.or(overrides.style.weight); |
| } |
| } |
| } |
| |
| if overrides.has_language_overrides() { |
| match &mut query.languages { |
| None => { |
| query.languages = Some( |
| overrides |
| .languages |
| .iter() |
| .map(|lang| LocaleId { id: lang.to_owned() }) |
| .collect_vec(), |
| ) |
| } |
| Some(_) => (), |
| } |
| } |
| } |
| None => (), |
| } |
| request |
| } |
| |
| async fn match_request( |
| &self, |
| request: fonts::TypefaceRequest, |
| ) -> Result<fonts::TypefaceResponse, Error> { |
| let query_family = query_field!(request, family); |
| let query_family_string = |
| (&query_family).map(|family| family.name.clone()).unwrap_or_default(); |
| trace::duration!( |
| "fonts", |
| "service:match_request", |
| "family" => &query_family_string[..]); |
| // TODO(fxbug.dev/44328): If support for lazy trace args is added, include more query params, e.g. |
| // code points. |
| |
| let matched_family: Option<FontFamilyMatch<'_>> = |
| query_family.and_then(|family| self.match_family(&UniCase::new(family.name.clone()))); |
| |
| let mut request = match &matched_family { |
| Some(FontFamilyMatch { family: _, overrides: Some(overrides) }) => { |
| Self::apply_query_overrides(request, overrides.clone()) |
| } |
| _ => request, |
| }; |
| |
| let mut typeface = match &matched_family { |
| Some(FontFamilyMatch { family, overrides: _ }) => { |
| family.faces.match_request(&request)? |
| } |
| None => None, |
| }; |
| |
| // If an exact match wasn't found, but fallback families are allowed... |
| if typeface.is_none() && !request.exact_family() { |
| // If fallback_family is not specified by the client explicitly then copy it from |
| // the matched font family. |
| if query_field!(request, fallback_family).is_none() { |
| if let Some(FontFamilyMatch { family, overrides: _ }) = matched_family { |
| request |
| .query |
| .as_mut() |
| .ok_or_else(|| format_err!("This should never happen"))? |
| .fallback_family = family.generic_family; |
| } |
| } |
| typeface = self.fallback_collection.match_request(&request)?; |
| } |
| |
| let typeface_response = match typeface { |
| Some(typeface) => self |
| .assets |
| .get_asset(typeface.asset_id, request.cache_miss_policy()) |
| .await |
| .and_then(|buffer| { |
| Ok(fonts::TypefaceResponse { |
| buffer: Some(buffer), |
| buffer_id: Some(typeface.asset_id.into()), |
| font_index: Some(typeface.font_index), |
| ..fonts::TypefaceResponse::EMPTY |
| }) |
| }) |
| .unwrap_or_else(|_| fonts::TypefaceResponse::new_empty()), |
| None => { |
| if let Some(code_points) = query_field!(request, code_points) { |
| fx_log_err!("Missing code points: {:?}", code_points); |
| } |
| fonts::TypefaceResponse::new_empty() |
| } |
| }; |
| |
| // Note that not finding a typeface is not an error, as long as the query was legal. |
| Ok(typeface_response) |
| } |
| |
| fn get_family_info(&self, family_name: fonts::FamilyName) -> fonts::FontFamilyInfo { |
| let family_name = UniCase::new(family_name.name); |
| let family = self.match_family(&family_name); |
| family.map_or_else( |
| || fonts::FontFamilyInfo::new_empty(), |
| |FontFamilyMatch { family, overrides: _ }| fonts::FontFamilyInfo { |
| name: Some(fonts::FamilyName { name: family.name.clone() }), |
| styles: Some(family.faces.get_styles().collect()), |
| ..fonts::FontFamilyInfo::EMPTY |
| }, |
| ) |
| } |
| |
| async fn get_typeface_by_id( |
| &self, |
| id: AssetId, |
| policy: CacheMissPolicy, |
| ) -> Result<fonts::TypefaceResponse, fonts_exp::Error> { |
| match self.assets.get_asset(id, policy).await { |
| Ok(buffer) => { |
| let response = fonts::TypefaceResponse { |
| buffer: Some(buffer), |
| buffer_id: Some(id.into()), |
| font_index: None, |
| ..fonts::TypefaceResponse::EMPTY |
| }; |
| Ok(response) |
| } |
| Err(e) => { |
| let msg = e.to_string(); |
| if msg.starts_with("No asset found") { |
| return Err(fonts_exp::Error::NotFound); |
| } |
| Err(fonts_exp::Error::Internal) |
| } |
| } |
| } |
| |
| fn get_typefaces_by_family( |
| &self, |
| family_name: fonts::FamilyName, |
| ) -> Result<fonts_exp::TypefaceInfoResponse, fonts_exp::Error> { |
| let family = self |
| .match_family(&UniCase::new(family_name.name.clone())) |
| .map(|matched| matched.family) |
| .ok_or(fonts_exp::Error::NotFound)?; |
| let faces = family.extract_faces().map_into().collect(); |
| let response = fonts_exp::TypefaceInfoResponse { |
| results: Some(faces), |
| ..fonts_exp::TypefaceInfoResponse::EMPTY |
| }; |
| Ok(response) |
| } |
| |
| /// Helper that runs the "match by family name" step of [`list_typefaces`]. |
| /// Returns a vector of all available font families whose name or alias equals (or contains, if |
| /// the `MatchFamilyNameSubstring` flag is set) the name requested in `query`. |
| /// If `query` or `query.family` is `None`, all families are matched. |
| fn list_typefaces_match_families<'a>( |
| &'a self, |
| flags: fonts_exp::ListTypefacesFlags, |
| request: &fonts_exp::ListTypefacesRequest, |
| ) -> Box<dyn Iterator<Item = &FontFamily> + 'a> { |
| match request.family.as_ref() { |
| Some(fonts::FamilyName { name }) => { |
| if flags.contains(fonts_exp::ListTypefacesFlags::MatchFamilyNameSubstring) { |
| Box::new(self.match_families_substr(name.clone())) |
| } else { |
| match self.match_family(&UniCase::new(name.clone())) { |
| Some(matched) => Box::new(iter::once(matched.family)), |
| None => Box::new(iter::empty()), |
| } |
| } |
| } |
| None => Box::new(self.families.iter().filter_map(move |(_, value)| match value { |
| FamilyOrAlias::Family(family) => Some(family), |
| FamilyOrAlias::Alias(_, _) => None, |
| })), |
| } |
| } |
| |
| fn list_typefaces_inner( |
| &self, |
| request: fonts_exp::ListTypefacesRequest, |
| ) -> Result<Vec<fonts_exp::TypefaceInfo>, fonts_exp::Error> { |
| let flags = request.flags.unwrap_or(fonts_exp::ListTypefacesFlags::new_empty()); |
| |
| let matched_families = self.list_typefaces_match_families(flags, &request); |
| |
| // Flatten matches into Iter<TypefaceInfoAndCharSet> |
| let matched_faces = matched_families.flat_map(|family| family.extract_faces()); |
| |
| let slant_predicate = |face: &TypefaceInfoAndCharSet| -> bool { |
| match request.slant { |
| Some(fonts_exp::SlantRange { lower, upper }) => { |
| // Unwrap is safe because manifest loading assigns default values if needed |
| (lower..=upper).contains(&face.style.slant.unwrap()) |
| } |
| None => true, |
| } |
| }; |
| |
| let weight_predicate = |face: &TypefaceInfoAndCharSet| -> bool { |
| match request.weight { |
| Some(fonts_exp::WeightRange { lower, upper }) => { |
| // Unwrap is safe because manifest loading assigns default values if needed |
| (lower..=upper).contains(&face.style.weight.unwrap()) |
| } |
| None => true, |
| } |
| }; |
| |
| let width_predicate = |face: &TypefaceInfoAndCharSet| -> bool { |
| match request.width { |
| Some(fonts_exp::WidthRange { lower, upper }) => { |
| // Unwrap is safe because manifest loading assigns default values if needed |
| (lower..=upper).contains(&face.style.width.unwrap()) |
| } |
| None => true, |
| } |
| }; |
| |
| let lang_predicate = |face: &TypefaceInfoAndCharSet| -> bool { |
| match request.languages.as_ref() { |
| // This is O(face_langs.len() * fonts.MAX_FACE_QUERY_LANGUAGES). As of 06/2019, |
| // MAX_FACE_QUERY_LANGAUGES == 8. face_langs.len() *should* be small as well. |
| Some(langs) => langs.iter().all(|lang| face.languages.contains(&lang)), |
| None => true, |
| } |
| }; |
| |
| let code_point_predicate = |face: &TypefaceInfoAndCharSet| -> bool { |
| match request.code_points.as_ref() { |
| Some(points) => points.iter().all(|point| face.char_set.contains(*point)), |
| None => true, |
| } |
| }; |
| |
| let generic_family_predicate = |face: &TypefaceInfoAndCharSet| -> bool { |
| match request.generic_family.as_ref() { |
| Some(generic_family) => { |
| face.generic_family.map_or(false, |gf| generic_family == &gf) |
| } |
| None => true, |
| } |
| }; |
| |
| let total_predicate = |face: &TypefaceInfoAndCharSet| -> bool { |
| slant_predicate(face) |
| && weight_predicate(face) |
| && width_predicate(face) |
| && lang_predicate(face) |
| && code_point_predicate(face) |
| && generic_family_predicate(face) |
| }; |
| |
| // Filter |
| let matched_faces = matched_faces.filter(total_predicate).map_into().collect(); |
| |
| Ok(matched_faces) |
| } |
| |
| fn list_typefaces( |
| &self, |
| request: fonts_exp::ListTypefacesRequest, |
| iterator: ServerEnd<fonts_exp::ListTypefacesIteratorMarker>, |
| ) -> Result<(), fonts_exp::Error> { |
| let mut results = self.list_typefaces_inner(request)?; |
| |
| fasync::Task::spawn( |
| async move { |
| let mut stream = iterator.into_stream()?; |
| while let Some(request) = stream.try_next().await? { |
| match request { |
| fonts_exp::ListTypefacesIteratorRequest::GetNext { responder } => { |
| let split_at = |
| (fonts_exp::MAX_TYPEFACE_RESULTS as usize).min(results.len()); |
| // Return results in order |
| let chunk = results.drain(..split_at).collect_vec(); |
| let response = fonts_exp::TypefaceInfoResponse { |
| results: Some(chunk), |
| ..fonts_exp::TypefaceInfoResponse::EMPTY |
| }; |
| responder.send(response)?; |
| } |
| } |
| } |
| Ok(()) |
| } |
| .unwrap_or_else(|e: Error| { |
| fx_log_err!("Error while running ListTypefacesIterator: {:?}", e) |
| }), |
| ) |
| .detach(); |
| |
| Ok(()) |
| } |
| |
| async fn handle_font_provider_request( |
| &self, |
| request: fonts::ProviderRequest, |
| ) -> Result<(), Error> { |
| use fonts::ProviderRequest::*; |
| |
| match request { |
| // TODO(fxbug.dev/8903): Remove when all clients have migrated to GetTypeface |
| GetFont { request, responder } => { |
| let request = request.into_typeface_request(); |
| let mut response = self.match_request(request).await?.into_font_response(); |
| Ok(responder.send(response.as_mut())?) |
| } |
| // TODO(fxbug.dev/8903): Remove when all clients have migrated to GetFontFamilyInfo |
| GetFamilyInfo { family, responder } => { |
| let mut font_info = |
| self.get_family_info(fonts::FamilyName { name: family }).into_family_info(); |
| Ok(responder.send(font_info.as_mut())?) |
| } |
| GetTypeface { request, responder } => { |
| let response = self.match_request(request).await?; |
| Ok(responder.send(response)?) |
| } |
| GetFontFamilyInfo { family, responder } => { |
| let family_info = self.get_family_info(family); |
| Ok(responder.send(family_info)?) |
| } |
| // TODO(fxbug.dev/34897): Implement font event dispatch |
| RegisterFontSetEventListener { listener: _, responder: _ } => unimplemented!(), |
| } |
| } |
| |
| async fn handle_experimental_request( |
| &self, |
| request: fonts_exp::ProviderRequest, |
| ) -> Result<(), Error> { |
| use fonts_exp::ProviderRequest::*; |
| |
| match request { |
| GetTypefaceById { id, responder } => { |
| let mut response = self |
| .get_typeface_by_id(AssetId(id), CacheMissPolicy::BlockUntilDownloaded) |
| .await; |
| Ok(responder.send(&mut response)?) |
| } |
| GetTypefacesByFamily { family, responder } => { |
| let mut response = self.get_typefaces_by_family(family); |
| Ok(responder.send(&mut response)?) |
| } |
| ListTypefaces { request, iterator, responder } => { |
| let mut response = self.list_typefaces(request, iterator); |
| Ok(responder.send(&mut response)?) |
| } |
| } |
| } |
| |
| pub async fn run(self, fs: ServiceFs<ServiceObj<'static, ProviderRequestStream>>) { |
| let self_ = Arc::new(self); |
| fs.for_each_concurrent(None, move |stream| self_.clone().handle_stream(stream)).await; |
| } |
| |
| async fn handle_stream(self: Arc<Self>, stream: ProviderRequestStream) { |
| let self_ = self.clone(); |
| match stream { |
| ProviderRequestStream::Stable(stream) => { |
| self_.as_ref().handle_stream_stable(stream).await.unwrap_or_default() |
| } |
| ProviderRequestStream::Experimental(stream) => { |
| self_.as_ref().handle_stream_experimental(stream).await.unwrap_or_default() |
| } |
| } |
| } |
| |
| async fn handle_stream_stable( |
| &self, |
| mut stream: fonts::ProviderRequestStream, |
| ) -> Result<(), Error> { |
| while let Some(request) = stream.try_next().await.context("Error running provider")? { |
| self.handle_font_provider_request(request) |
| .await |
| .context("Error while handling font provider request") |
| .map_err(|err| { |
| fx_log_err!("{:?}", err); |
| err |
| })?; |
| } |
| Ok(()) |
| } |
| |
| async fn handle_stream_experimental( |
| &self, |
| mut stream: fonts_exp::ProviderRequestStream, |
| ) -> Result<(), Error> { |
| while let Some(request) = stream.try_next().await.context("Error running provider")? { |
| self.handle_experimental_request(request) |
| .await |
| .context("Error while handling experimental font provider request") |
| .map_err(|err| { |
| fx_log_err!("{:?}", err); |
| err |
| })?; |
| } |
| Ok(()) |
| } |
| } |