blob: 405df18aefb78006f203dc6b0c14109f1edc8ee9 [file] [log] [blame] [edit]
// 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(())
}
}