blob: 014ece8183778d7629bddae7f736d39d85d83a1b [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::collection::{Font, FontCollection};
use super::font_info;
use super::manifest;
use failure::{format_err, Error, ResultExt};
use fdio;
use fidl;
use fidl::encoding::OutOfLine;
use fidl::endpoints::RequestStream;
use fidl_fuchsia_fonts as fonts;
use fidl_fuchsia_mem as mem;
use fuchsia_async as fasync;
use fuchsia_zircon as zx;
use fuchsia_zircon::HandleBased;
use futures::prelude::*;
use futures::{future, Future, FutureExt};
use log::error;
use std::collections::BTreeMap;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use unicase::UniCase;
fn clone_buffer(buf: &mem::Buffer) -> Result<mem::Buffer, Error> {
let vmo_rights = zx::Rights::BASIC | zx::Rights::READ | zx::Rights::MAP;
let vmo = buf
.vmo
.duplicate_handle(vmo_rights)
.context("Failed to duplicate VMO handle.")?;
Ok(mem::Buffer {
vmo,
size: buf.size,
})
}
struct Asset {
path: PathBuf,
buffer: RwLock<Option<mem::Buffer>>,
}
struct AssetCollection {
assets_map: BTreeMap<PathBuf, u32>,
assets: Vec<Asset>,
}
fn load_asset_to_vmo(path: &Path) -> Result<mem::Buffer, Error> {
let file = File::open(path)?;
let vmo = fdio::get_vmo_copy_from_file(&file)?;
let size = file.metadata()?.len();
Ok(mem::Buffer { vmo, size })
}
impl AssetCollection {
fn new() -> AssetCollection {
AssetCollection {
assets_map: BTreeMap::new(),
assets: vec![],
}
}
fn add_or_get_asset_id(&mut self, path: &Path) -> u32 {
if let Some(id) = self.assets_map.get(path) {
return *id;
}
let id = self.assets.len() as u32;
self.assets.push(Asset {
path: path.to_path_buf(),
buffer: RwLock::new(None),
});
self.assets_map.insert(path.to_path_buf(), id);
id
}
fn get_asset(&self, id: u32) -> Result<mem::Buffer, Error> {
assert!(id < self.assets.len() as u32);
let asset = &self.assets[id as usize];
if let Some(cached) = asset.buffer.read().unwrap().as_ref() {
return clone_buffer(cached);
}
let buf = load_asset_to_vmo(&asset.path)
.with_context(|_| format!("Failed to load {}", asset.path.to_string_lossy()))?;
let buf_clone = clone_buffer(&buf)?;
*asset.buffer.write().unwrap() = Some(buf);
Ok(buf_clone)
}
fn reset_cache(&mut self) {
for asset in self.assets.iter_mut() {
*asset.buffer.write().unwrap() = None;
}
}
}
struct FontFamily {
name: String,
fonts: FontCollection,
fallback_group: fonts::FallbackGroup,
}
impl FontFamily {
fn new(name: String, fallback_group: fonts::FallbackGroup) -> FontFamily {
FontFamily {
name,
fonts: FontCollection::new(),
fallback_group,
}
}
}
enum FamilyOrAlias {
Family(FontFamily),
Alias(UniCase<String>),
}
pub struct FontService {
assets: AssetCollection,
families: BTreeMap<UniCase<String>, FamilyOrAlias>,
fallback_collection: FontCollection,
}
impl FontService {
pub fn new() -> FontService {
FontService {
assets: AssetCollection::new(),
families: BTreeMap::new(),
fallback_collection: FontCollection::new(),
}
}
// Verifies that we have a reasonable font configuration and can start.
pub fn check_can_start(&self) -> Result<(), Error> {
if self.fallback_collection.is_empty() {
return Err(format_err!("Need at least one fallback font"));
}
Ok(())
}
pub fn load_manifest(&mut self, manifest_path: &Path) -> Result<(), Error> {
let manifest = manifest::FontsManifest::load_from_file(&manifest_path)?;
self.add_fonts_from_manifest(manifest).with_context(|_| {
format!(
"Failed to load fonts from {}",
manifest_path.to_string_lossy()
)
})?;
Ok(())
}
fn add_fonts_from_manifest(
&mut self, mut manifest: manifest::FontsManifest,
) -> Result<(), Error> {
let font_info_loader = font_info::FontInfoLoader::new()?;
for mut family_manifest in manifest.families.drain(..) {
if family_manifest.fonts.is_empty() {
continue;
}
let family_name = UniCase::new(family_manifest.family.clone());
let family = match self.families.entry(family_name.clone()).or_insert_with(|| {
FamilyOrAlias::Family(FontFamily::new(
family_manifest.family.clone(),
family_manifest.fallback_group,
))
}) {
FamilyOrAlias::Family(f) => f,
FamilyOrAlias::Alias(other_family) => {
// Different manifest files may declare fonts for the same family,
// but font aliases cannot conflict with main family name.
return Err(format_err!(
"Conflicting font alias: {} is already declared as an alias for {}",
family_name,
other_family
));
}
};
for font_manifest in family_manifest.fonts.drain(..) {
let asset_id = self
.assets
.add_or_get_asset_id(font_manifest.asset.as_path());
let buffer = self.assets.get_asset(asset_id).with_context(|_| {
format!(
"Failed to load font from {}",
font_manifest.asset.to_string_lossy()
)
})?;
let info = font_info_loader
.load_font_info(buffer.vmo, buffer.size as usize, font_manifest.index)
.with_context(|_| {
format!(
"Failed to load font info from {}",
font_manifest.asset.to_string_lossy()
)
})?;
let font = Arc::new(Font::new(
asset_id,
font_manifest,
info,
family_manifest.fallback_group,
));
family.fonts.add_font(font.clone());
if family_manifest.fallback {
self.fallback_collection.add_font(font);
}
}
// Register family aliases.
for alias in family_manifest.aliases.unwrap_or(vec![]) {
let alias_unicase = UniCase::new(alias.clone());
match self.families.get(&alias_unicase) {
None => {
self.families
.insert(alias_unicase, FamilyOrAlias::Alias(family_name.clone()));
}
Some(FamilyOrAlias::Family(_)) => {
return Err(format_err!(
"Can't add alias {} for {} because a family with that name already \
exists.",
alias,
family_name
))
}
Some(FamilyOrAlias::Alias(other_family)) => {
// If the alias exists then it must be for the same font family.
if *other_family != family_name {
return Err(format_err!(
"Can't add alias {} for {} because it's already declared as alias \
for {}.",
alias,
family_name,
other_family
));
}
}
}
}
}
// Flush the cache. Font files will be loaded again when they are needed.
self.assets.reset_cache();
Ok(())
}
fn match_family(&self, family_name: &UniCase<String>) -> Option<&FontFamily> {
let family = match self.families.get(family_name)? {
FamilyOrAlias::Family(f) => f,
FamilyOrAlias::Alias(a) => match self.families.get(a) {
Some(FamilyOrAlias::Family(f)) => f,
_ => panic!("Invalid font alias."),
},
};
Some(family)
}
fn match_request(&self, mut request: fonts::Request) -> Option<fonts::Response> {
request.language = request
.language
.map(|list| list.iter().map(|l| l.to_ascii_lowercase()).collect());
let matched_family = request
.family
.as_ref()
.and_then(|family| self.match_family(&UniCase::new(family.clone())));
let mut font = matched_family.and_then(|family| family.fonts.match_request(&request));
if font.is_none() && (request.flags & fonts::REQUEST_FLAG_NO_FALLBACK) == 0 {
// If fallback_group is not specified by the client explicitly then copy it from
// the matched font family.
if request.fallback_group == fonts::FallbackGroup::None {
if let Some(family) = matched_family {
request.fallback_group = family.fallback_group;
}
}
font = self.fallback_collection.match_request(&request);
}
font.and_then(|font| match self.assets.get_asset(font.asset_id) {
Ok(buffer) => Some(fonts::Response {
buffer,
buffer_id: font.asset_id,
font_index: font.font_index,
}),
Err(err) => {
error!("Failed to load font file: {}", err);
None
}
})
}
fn get_font_info(&self, family_name: String) -> Option<fonts::FamilyInfo> {
let family_name = UniCase::new(family_name);
let family = self.match_family(&family_name)?;
Some(fonts::FamilyInfo {
name: family.name.clone(),
styles: family.fonts.get_styles().collect(),
})
}
fn handle_font_provider_request(
&self, request: fonts::ProviderRequest,
) -> impl Future<Output = Result<(), fidl::Error>> {
match request {
fonts::ProviderRequest::GetFont { request, responder } => {
let mut response = self.match_request(request);
future::ready(responder.send(response.as_mut().map(OutOfLine)))
}
fonts::ProviderRequest::GetFamilyInfo { family, responder } => {
let mut font_info = self.get_font_info(family);
future::ready(responder.send(font_info.as_mut().map(OutOfLine)))
}
}
}
}
pub fn spawn_server(font_service: Arc<FontService>, chan: fasync::Channel) {
// TODO(sergeyu): Consider making handle_font_provider_request() and
// load_asset_to_vmo() asynchronous and using try_for_each_concurrent()
// instead of try_for_each() here. That would be useful only if clients can
// send more than one concurrent request.
let stream_complete = fonts::ProviderRequestStream::from_channel(chan)
.try_for_each(move |request| font_service.handle_font_provider_request(request))
.map(|_| ());
fasync::spawn(stream_complete);
}