[fonts] Ephemeral FontProvider
- Read code_points field from manifest if possible
- Use FontResolver if assets are not already present
- FontService is more async due to asset::Collection.get_asset()
becoming async
- Add a mock FontResolver service for testing
Bug: 8904
Change-Id: I391f99edabeb023d63ef22155083f5d503d139b4
diff --git a/src/fonts/BUILD.gn b/src/fonts/BUILD.gn
index 224e155..a351c65 100644
--- a/src/fonts/BUILD.gn
+++ b/src/fonts/BUILD.gn
@@ -23,6 +23,7 @@
deps = [
"//garnet/lib/rust/fidl_fuchsia_fonts_ext",
+ "//garnet/lib/rust/io_util",
"//garnet/public/lib/fidl/rust/fidl",
"//garnet/public/rust/fdio",
"//garnet/public/rust/fuchsia-async",
@@ -32,6 +33,8 @@
"//sdk/fidl/fuchsia.fonts:fuchsia.fonts-rustc",
"//sdk/fidl/fuchsia.fonts.experimental:fuchsia.fonts.experimental-rustc",
"//sdk/fidl/fuchsia.intl:fuchsia.intl-rustc",
+ "//sdk/fidl/fuchsia.pkg:fuchsia.pkg-rustc",
+ "//src/sys/lib/fuchsia_url",
"//third_party/rust_crates:failure",
"//third_party/rust_crates:futures-preview",
"//third_party/rust_crates:getopts",
@@ -45,6 +48,7 @@
"//third_party/rust_crates:serde_derive",
"//third_party/rust_crates:serde_json",
"//third_party/rust_crates:unicase",
+ "//zircon/public/fidl/fuchsia-io:fuchsia-io-rustc",
"//zircon/public/fidl/fuchsia-mem:fuchsia-mem-rustc",
]
@@ -99,12 +103,16 @@
"//garnet/public/rust/fdio",
"//garnet/public/rust/fuchsia-async",
"//garnet/public/rust/fuchsia-component",
+ "//garnet/public/rust/fuchsia-syslog",
"//garnet/public/rust/fuchsia-zircon",
"//sdk/fidl/fuchsia.fonts:fuchsia.fonts-rustc",
"//sdk/fidl/fuchsia.fonts.experimental:fuchsia.fonts.experimental-rustc",
"//sdk/fidl/fuchsia.intl:fuchsia.intl-rustc",
+ "//sdk/fidl/fuchsia.pkg:fuchsia.pkg-rustc",
+ "//src/sys/lib/fuchsia_url",
"//third_party/rust_crates:failure",
"//third_party/rust_crates:futures-preview",
+ "//zircon/public/fidl/fuchsia-io:fuchsia-io-rustc",
]
source_root = "tests/font_provider_test.rs"
}
@@ -113,6 +121,20 @@
deps = [
":font_provider_test_test",
":font_server_test",
+ ":mock_font_resolver_bin",
+ ]
+
+ meta = [
+ {
+ path = rebase_path("meta/mock_font_resolver.cmx")
+ dest = "mock_font_resolver.cmx"
+ },
+ ]
+
+ binaries = [
+ {
+ name = "mock_font_resolver"
+ },
]
tests = [
@@ -135,6 +157,10 @@
path = rebase_path("tests/all_fonts_manifest.json")
dest = "testdata/test_fonts/all_fonts_manifest.json"
},
+ {
+ path = rebase_path("tests/ephemeral_manifest.json")
+ dest = "testdata/test_fonts/ephemeral_manifest.json"
+ },
]
# TODO(sergeyu): Noto CJK fonts are not included in the default fonts package
@@ -188,3 +214,24 @@
]
}
}
+
+rustc_binary("mock_font_resolver_bin") {
+ name = "mock_font_resolver"
+ edition = "2018"
+ source_root = "tests/mock_font_resolver.rs"
+ deps = [
+ "//garnet/public/lib/fidl/rust/fidl",
+ "//garnet/public/rust/fdio",
+ "//garnet/public/rust/fuchsia-async",
+ "//garnet/public/rust/fuchsia-component",
+ "//garnet/public/rust/fuchsia-syslog",
+ "//garnet/public/rust/fuchsia-vfs/pseudo-fs",
+ "//garnet/public/rust/fuchsia-zircon",
+ "//sdk/fidl/fuchsia.pkg:fuchsia.pkg-rustc",
+ "//src/sys/lib/fuchsia_url:fuchsia_url",
+ "//third_party/rust_crates:failure",
+ "//third_party/rust_crates:futures-preview",
+ "//third_party/rust_crates:lazy_static",
+ "//zircon/public/fidl/fuchsia-io:fuchsia-io-rustc",
+ ]
+}
diff --git a/src/fonts/meta/font_provider_test.cmx b/src/fonts/meta/font_provider_test.cmx
index 9337147..75e225c 100644
--- a/src/fonts/meta/font_provider_test.cmx
+++ b/src/fonts/meta/font_provider_test.cmx
@@ -2,6 +2,7 @@
"facets": {
"fuchsia.test": {
"injected-services": {
+ "fuchsia.pkg.FontResolver": "fuchsia-pkg://fuchsia.com/font_provider_tests#meta/mock_font_resolver.cmx",
"fuchsia.tracing.provider.Registry": "fuchsia-pkg://fuchsia.com/trace_manager#meta/trace_manager.cmx"
}
}
@@ -11,6 +12,7 @@
},
"sandbox": {
"services": [
+ "fuchsia.pkg.FontResolver",
"fuchsia.sys.Launcher"
]
}
diff --git a/src/fonts/meta/fonts.cmx b/src/fonts/meta/fonts.cmx
index 90e76cb..fff4d9d3 100644
--- a/src/fonts/meta/fonts.cmx
+++ b/src/fonts/meta/fonts.cmx
@@ -7,7 +7,8 @@
"config-data"
],
"services": [
- "fuchsia.logger.LogSink"
+ "fuchsia.logger.LogSink",
+ "fuchsia.pkg.FontResolver"
]
}
}
diff --git a/src/fonts/meta/mock_font_resolver.cmx b/src/fonts/meta/mock_font_resolver.cmx
new file mode 100644
index 0000000..712d9b6
--- /dev/null
+++ b/src/fonts/meta/mock_font_resolver.cmx
@@ -0,0 +1,10 @@
+{
+ "program": {
+ "binary": "bin/mock_font_resolver"
+ },
+ "sandbox": {
+ "services": [
+ "fuchsia.logger.LogSink"
+ ]
+ }
+}
diff --git a/src/fonts/src/font_service/asset/collection.rs b/src/fonts/src/font_service/asset/collection.rs
index ad36b20..20f1ece 100644
--- a/src/fonts/src/font_service/asset/collection.rs
+++ b/src/fonts/src/font_service/asset/collection.rs
@@ -5,8 +5,14 @@
use {
super::{asset::Asset, cache::Cache},
failure::{format_err, Error, ResultExt},
- fidl_fuchsia_mem as mem,
- parking_lot::RwLock,
+ fidl::endpoints::create_proxy,
+ fidl_fuchsia_io as io, fidl_fuchsia_mem as mem,
+ fidl_fuchsia_pkg::FontResolverMarker,
+ fuchsia_component::client::connect_to_service,
+ fuchsia_url::pkg_url::PkgUrl,
+ fuchsia_zircon as zx,
+ futures::lock::Mutex,
+ io_util,
std::{
collections::BTreeMap,
fs::File,
@@ -36,9 +42,13 @@
path_to_id_map: BTreeMap<PathBuf, u32>,
/// Inverse of `path_to_id_map`.
id_to_path_map: BTreeMap<u32, PathBuf>,
+ /// Maps asset paths to package URLs.
+ path_to_url_map: BTreeMap<PathBuf, PkgUrl>,
+ /// Maps asset paths to previously-resolved directory handles.
+ path_to_dir_map: Mutex<BTreeMap<PathBuf, io::DirectoryProxy>>,
/// Next ID to assign, autoincremented from 0.
next_id: u32,
- cache: RwLock<Cache>,
+ cache: Mutex<Cache>,
}
const CACHE_SIZE_BYTES: u64 = 4_000_000;
@@ -48,48 +58,94 @@
Collection {
path_to_id_map: BTreeMap::new(),
id_to_path_map: BTreeMap::new(),
+ path_to_url_map: BTreeMap::new(),
+ path_to_dir_map: Mutex::new(BTreeMap::new()),
next_id: 0,
- cache: RwLock::new(Cache::new(CACHE_SIZE_BYTES)),
+ cache: Mutex::new(Cache::new(CACHE_SIZE_BYTES)),
}
}
- /// Add the [`Asset`] found at `path` to the collection and return its ID.
+ /// Add the [`Asset`] found at `path` to the collection, store its package URL if provided,
+ /// and return the asset's ID.
/// If `path` is already in the collection, return the existing ID.
- ///
- /// TODO(seancuff): Switch to updating ID of existing entries. This would allow assets to be
- /// updated without restarting the service (e.g. installing a newer version of a file). Clients
- /// would need to check the ID of their currently-held asset against the response.
- pub fn add_or_get_asset_id(&mut self, path: &Path) -> u32 {
+ pub fn add_or_get_asset_id(&mut self, path: &Path, package_url: Option<&PkgUrl>) -> u32 {
if let Some(id) = self.path_to_id_map.get(&path.to_path_buf()) {
return *id;
}
let id = self.next_id;
self.id_to_path_map.insert(id, path.to_path_buf());
self.path_to_id_map.insert(path.to_path_buf(), id);
+ if let Some(url) = package_url {
+ self.path_to_url_map.insert(path.to_path_buf(), url.clone());
+ }
self.next_id += 1;
id
}
/// Get a `Buffer` holding the `Vmo` for the [`Asset`] corresponding to `id`, using the cache
/// if possible.
- pub fn get_asset(&self, id: u32) -> Result<mem::Buffer, Error> {
+ pub async fn get_asset(&self, id: u32) -> Result<mem::Buffer, Error> {
if let Some(path) = self.id_to_path_map.get(&id) {
- let mut cache_writer = self.cache.write();
- let buf = match cache_writer.get(id) {
+ let mut cache_lock = self.cache.lock().await;
+ let buf = match cache_lock.get(id) {
Some(cached) => cached.buffer,
None => {
- cache_writer
- .push(Asset {
- id,
- buffer: load_asset_to_vmo(path).with_context(|_| {
- format!("Failed to load {}", path.to_string_lossy())
- })?,
- })
- .buffer
+ let buffer = if path.exists() {
+ load_asset_to_vmo(path).with_context(|_| {
+ format!("Failed to load {}.", path.to_string_lossy())
+ })?
+ } else {
+ self.get_ephemeral_asset(path).await?
+ };
+
+ cache_lock.push(Asset { id, buffer }).buffer
}
};
return Ok(buf);
}
Err(format_err!("No asset found with id {}", id))
}
+
+ async fn get_ephemeral_asset(&self, path_buf: &PathBuf) -> Result<mem::Buffer, Error> {
+ let filename = path_buf.as_path().file_name().ok_or(format_err!(
+ "Path '{}' does not contain a valid filename.",
+ path_buf.to_string_lossy()
+ ))?;
+
+ // Get cached directory if it is cached
+ let mut cache_lock = self.path_to_dir_map.lock().await;
+
+ let directory_proxy = match cache_lock.get(path_buf) {
+ Some(dir_proxy) => dir_proxy,
+ None => {
+ let url = self.path_to_url_map.get(path_buf).ok_or(format_err!(
+ "No asset found with path {}",
+ path_buf.to_string_lossy()
+ ))?;
+
+ // Get directory handle from FontResolver
+ let font_resolver = connect_to_service::<FontResolverMarker>()?;
+ let (dir_proxy, dir_request) = create_proxy::<io::DirectoryMarker>()?;
+
+ let status = font_resolver.resolve(&url.to_string(), dir_request).await?;
+ zx::Status::ok(status)?;
+
+ // Cache directory handle
+ cache_lock.insert(path_buf.to_path_buf(), dir_proxy);
+ cache_lock.get(path_buf).unwrap() // Safe because just inserted
+ }
+ };
+
+ let file_proxy =
+ io_util::open_file(directory_proxy, Path::new(&filename), io::OPEN_RIGHT_READABLE)?;
+
+ drop(cache_lock);
+
+ let (status, buffer) = file_proxy.get_buffer(io::VMO_FLAG_READ).await?;
+ zx::Status::ok(status)?;
+
+ let buffer = *buffer
+ .ok_or(format_err!("Failed to get buffer for {}.", filename.to_string_lossy()))?;
+ Ok(buffer)
+ }
}
diff --git a/src/fonts/src/font_service/font_info/char_set.rs b/src/fonts/src/font_service/font_info/char_set.rs
index 86626f0..7245439 100644
--- a/src/fonts/src/font_service/font_info/char_set.rs
+++ b/src/fonts/src/font_service/font_info/char_set.rs
@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::cmp::Ordering;
+use {
+ failure::{format_err, Error},
+ std::cmp::Ordering,
+};
type BitmapElement = u64;
const BITMAP_ELEMENT_SIZE: usize = 64;
@@ -88,6 +91,37 @@
CharSet { ranges }
}
+ pub fn from_string(s: String) -> Result<CharSet, Error> {
+ let mut code_points: Vec<u32> = vec![];
+ let mut prev: u32 = 0;
+ for range in s.split(',').filter(|x| !x.is_empty()) {
+ let mut split = range.split('+');
+
+ let offset: u32 = match split.next() {
+ Some(off) => off
+ .parse()
+ .or_else(|_| Err(format_err!("Failed to parse {:?} as u32.", off)))?,
+ None => return Err(format_err!("Failed to parse {:?}: not a valid range.", range)),
+ };
+
+ let length: u32 = match split.next() {
+ Some(len) => len
+ .parse()
+ .or_else(|_| Err(format_err!("Failed to parse {:?} as u32.", len)))?,
+ None => 0, // We can treat "0,2,..." as "0+0,2+0,..."
+ };
+
+ if split.next().is_some() {
+ return Err(format_err!("Failed to parse {:?}: not a valid range.", range));
+ }
+
+ let begin = prev + offset;
+ prev = begin + length;
+ code_points.extend(begin..=prev);
+ }
+ Ok(CharSet::new(code_points))
+ }
+
pub fn contains(&self, c: u32) -> bool {
match self.ranges.binary_search_by(|r| {
if r.end() < c {
@@ -102,6 +136,16 @@
Err(_) => false,
}
}
+
+ pub fn is_empty(&self) -> bool {
+ self.ranges.is_empty()
+ }
+}
+
+impl Default for CharSet {
+ fn default() -> Self {
+ CharSet::new(vec![])
+ }
}
#[cfg(test)]
@@ -127,4 +171,20 @@
assert!(charset.contains(10000));
assert!(!charset.contains(10001));
}
+
+ #[test]
+ fn test_charset_from_string() -> Result<(), Error> {
+ let s = "0,2,11,19+94".to_string();
+ let charset = CharSet::from_string(s)?;
+ assert!([0, 2, 13, 32, 54, 126].into_iter().all(|c| charset.contains(*c)));
+ assert!([1, 11, 19, 127, 10000].into_iter().all(|c| !charset.contains(*c)));
+ Ok(())
+ }
+
+ #[test]
+ fn test_charset_from_string_not_a_number() {
+ for s in &["0,p,11", "q+1", "3+r"] {
+ assert!(CharSet::from_string(s.to_string()).is_err())
+ }
+ }
}
diff --git a/src/fonts/src/font_service/manifest.rs b/src/fonts/src/font_service/manifest.rs
index 54fa72a..c3b7881 100644
--- a/src/fonts/src/font_service/manifest.rs
+++ b/src/fonts/src/font_service/manifest.rs
@@ -3,8 +3,10 @@
// found in the LICENSE file.
use {
+ crate::font_service::font_info::CharSet,
failure::{self, format_err, ResultExt},
fidl_fuchsia_fonts::{GenericFontFamily, Slant, Width, WEIGHT_NORMAL},
+ fuchsia_url::pkg_url::PkgUrl,
lazy_static::lazy_static,
regex::Regex,
serde::de::{self, Deserialize, Deserializer, Error},
@@ -68,6 +70,12 @@
deserialize_with = "deserialize_languages"
)]
pub languages: LanguageSet,
+
+ #[serde(default = "default_package", deserialize_with = "deserialize_package")]
+ pub package: Option<PkgUrl>,
+
+ #[serde(default, deserialize_with = "deserialize_code_points")]
+ pub code_points: CharSet,
}
fn default_fallback() -> bool {
@@ -98,6 +106,10 @@
LanguageSet::new()
}
+fn default_package() -> Option<PkgUrl> {
+ None
+}
+
lazy_static! {
static ref SEPARATOR_REGEX: Regex = Regex::new(r"[_ ]").unwrap();
}
@@ -204,6 +216,21 @@
deserializer.deserialize_any(LanguageSetVisitor)
}
+fn deserialize_package<'d, D>(deserializer: D) -> Result<Option<PkgUrl>, D::Error>
+where
+ D: Deserializer<'d>,
+{
+ Some(PkgUrl::deserialize(deserializer)).transpose()
+}
+
+fn deserialize_code_points<'d, D>(deserializer: D) -> Result<CharSet, D::Error>
+where
+ D: Deserializer<'d>,
+{
+ let s = String::deserialize(deserializer)?;
+ CharSet::from_string(s).map_err(|e| D::Error::custom(format!("{:?}", e)))
+}
+
impl FontsManifest {
pub fn load_from_file(path: &Path) -> Result<FontsManifest, failure::Error> {
let path = fs::canonicalize(path)?;
diff --git a/src/fonts/src/font_service/mod.rs b/src/fonts/src/font_service/mod.rs
index 92671d2..ad8295b 100644
--- a/src/fonts/src/font_service/mod.rs
+++ b/src/fonts/src/font_service/mod.rs
@@ -29,7 +29,6 @@
fuchsia_component::server::{ServiceFs, ServiceObj},
futures::prelude::*,
itertools::Itertools,
- log,
std::{collections::BTreeMap, iter, path::Path, sync::Arc},
unicase::UniCase,
};
@@ -72,17 +71,17 @@
Ok(())
}
- pub fn load_manifest(&mut self, manifest_path: &Path) -> Result<(), Error> {
+ pub async fn load_manifest(&mut self, manifest_path: &Path) -> Result<(), Error> {
fx_vlog!(1, "Loading manifest {:?}", manifest_path);
let manifest = FontsManifest::load_from_file(&manifest_path)?;
- self.add_fonts_from_manifest(manifest).with_context(|_| {
+ self.add_fonts_from_manifest(manifest).await.with_context(|_| {
format!("Failed to load fonts from {}", manifest_path.to_string_lossy())
})?;
Ok(())
}
- fn add_fonts_from_manifest(&mut self, mut manifest: FontsManifest) -> Result<(), Error> {
+ async fn add_fonts_from_manifest(&mut self, mut manifest: FontsManifest) -> Result<(), Error> {
let font_info_loader = FontInfoLoader::new()?;
for mut family_manifest in manifest.families.drain(..) {
@@ -116,27 +115,42 @@
}
};
- for font_manifest in family_manifest.fonts.drain(..) {
- let asset_id = self.assets.add_or_get_asset_id(font_manifest.asset.as_path());
+ for mut font_manifest in family_manifest.fonts.drain(..) {
+ let asset_path = font_manifest.asset.as_path();
+ let asset_id =
+ self.assets.add_or_get_asset_id(asset_path, font_manifest.package.as_ref());
- let buffer = self.assets.get_asset(asset_id).with_context(|_| {
- format!("Failed to load font from {}", font_manifest.asset.to_string_lossy())
- })?;
+ // Read `code_points` from file if not provided by manifest.
+ if font_manifest.code_points.is_empty() {
+ if !asset_path.exists() {
+ return Err(format_err!(
+ "Unable to load code point info for '{}'. Manifest entry has no \
+ code_points field and the file does not exist.",
+ asset_path.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 buffer = self.assets.get_asset(asset_id).await.with_context(|_| {
+ format!("Failed to load font from {}", asset_path.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 {}",
+ asset_path.to_string_lossy()
+ )
+ })?;
+
+ font_manifest.code_points = info.char_set;
+ }
+
let typeface = Arc::new(Typeface::new(
asset_id,
font_manifest,
- info.char_set,
family_manifest.generic_family,
- ));
+ )?);
family.faces.add_typeface(typeface.clone());
if family_manifest.fallback {
self.fallback_collection.add_typeface(typeface);
@@ -207,7 +221,7 @@
.unique_by(|family| &family.name)
}
- fn match_request(
+ async fn match_request(
&self,
mut request: fonts::TypefaceRequest,
) -> Result<fonts::TypefaceResponse, Error> {
@@ -238,20 +252,21 @@
typeface = self.fallback_collection.match_request(&request)?;
}
- let typeface_response = typeface
- .ok_or("Couldn't match a typeface")
- .and_then(|font| match self.assets.get_asset(font.asset_id) {
- Ok(buffer) => Result::Ok(fonts::TypefaceResponse {
- buffer: Some(buffer),
- buffer_id: Some(font.asset_id),
- font_index: Some(font.font_index),
- }),
- Err(err) => {
- log::error!("Failed to load font file: {}", err);
- Err("Failed to load font file")
- }
- })
- .unwrap_or_else(|_| fonts::TypefaceResponse::new_empty());
+ let typeface_response = match typeface {
+ Some(font) => self
+ .assets
+ .get_asset(font.asset_id)
+ .await
+ .and_then(|buffer| {
+ Ok(fonts::TypefaceResponse {
+ buffer: Some(buffer),
+ buffer_id: Some(font.asset_id),
+ font_index: Some(font.font_index),
+ })
+ })
+ .unwrap_or_else(|_| fonts::TypefaceResponse::new_empty()),
+ None => fonts::TypefaceResponse::new_empty(),
+ };
// Note that not finding a typeface is not an error, as long as the query was legal.
Ok(typeface_response)
@@ -269,8 +284,11 @@
)
}
- fn get_typeface_by_id(&self, id: u32) -> Result<fonts::TypefaceResponse, fonts_exp::Error> {
- match self.assets.get_asset(id) {
+ async fn get_typeface_by_id(
+ &self,
+ id: u32,
+ ) -> Result<fonts::TypefaceResponse, fonts_exp::Error> {
+ match self.assets.get_asset(id).await {
Ok(buffer) => {
let response = fonts::TypefaceResponse {
buffer: Some(buffer),
@@ -451,7 +469,7 @@
// TODO(I18N-12): Remove when all clients have migrated to GetTypeface
GetFont { request, responder } => {
let request = request.into_typeface_request();
- let mut response = self.match_request(request)?.into_font_response();
+ let mut response = self.match_request(request).await?.into_font_response();
Ok(responder.send(response.as_mut().map(OutOfLine))?)
}
// TODO(I18N-12): Remove when all clients have migrated to GetFontFamilyInfo
@@ -461,7 +479,7 @@
Ok(responder.send(font_info.as_mut().map(OutOfLine))?)
}
GetTypeface { request, responder } => {
- let response = self.match_request(request)?;
+ let response = self.match_request(request).await?;
// TODO(kpozin): OutOfLine?
Ok(responder.send(response)?)
}
@@ -481,7 +499,7 @@
match request {
GetTypefaceById { id, responder } => {
- let mut response = self.get_typeface_by_id(id);
+ let mut response = self.get_typeface_by_id(id).await;
Ok(responder.send(&mut response)?)
}
GetTypefacesByFamily { family, responder } => {
diff --git a/src/fonts/src/font_service/typeface/collection.rs b/src/fonts/src/font_service/typeface/collection.rs
index 3e36988..9e32b04 100644
--- a/src/fonts/src/font_service/typeface/collection.rs
+++ b/src/fonts/src/font_service/typeface/collection.rs
@@ -153,6 +153,8 @@
char_set: &[u32],
generic_family: Option<GenericFontFamily>,
) -> Typeface {
+ // Prevent error if char_set is empty
+ let char_set = if char_set.is_empty() { &[0] } else { char_set };
Typeface::new(
0,
manifest::Font {
@@ -162,10 +164,12 @@
weight,
width,
languages: languages.iter().map(|s| s.to_string()).collect(),
+ code_points: CharSet::new(char_set.to_vec()),
+ package: None,
},
- CharSet::new(char_set.to_vec()),
generic_family,
)
+ .unwrap() // Safe because char_set is not empty
}
fn request_typeface<'a, 'b>(
diff --git a/src/fonts/src/font_service/typeface/typeface.rs b/src/fonts/src/font_service/typeface/typeface.rs
index c4afa81..b5ce9ae 100644
--- a/src/fonts/src/font_service/typeface/typeface.rs
+++ b/src/fonts/src/font_service/typeface/typeface.rs
@@ -4,6 +4,7 @@
use {
crate::font_service::{font_info::CharSet, manifest::Font},
+ failure::{format_err, Error},
fidl_fuchsia_fonts::{FamilyName, GenericFontFamily, Slant, Style2, TypefaceRequest, Width},
fidl_fuchsia_fonts_experimental::TypefaceInfo,
fidl_fuchsia_intl::LocaleId,
@@ -23,22 +24,26 @@
}
impl Typeface {
+ /// Create a new `Typeface`, copying all fields except `asset_id` and `generic_family` from
+ /// `manifest_font`.
pub fn new(
asset_id: u32,
manifest_font: Font,
- char_set: CharSet,
generic_family: Option<GenericFontFamily>,
- ) -> Typeface {
- Typeface {
+ ) -> Result<Typeface, Error> {
+ if manifest_font.code_points.is_empty() {
+ return Err(format_err!("Can't create Typeface from Font with empty CharSet."));
+ }
+ Ok(Typeface {
asset_id,
font_index: manifest_font.index,
weight: manifest_font.weight,
width: manifest_font.width,
slant: manifest_font.slant,
languages: manifest_font.languages.iter().map(|x| x.to_string()).collect(),
- char_set,
+ char_set: manifest_font.code_points,
generic_family,
- }
+ })
}
/// Returns value in the range `[0, 2 * request_languages.len()]`. The language code is used for
@@ -140,3 +145,28 @@
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use {
+ super::*,
+ fidl_fuchsia_fonts::{Slant, Width, WEIGHT_NORMAL},
+ std::path::PathBuf,
+ };
+
+ #[test]
+ fn test_typeface_new_empty_char_set_is_error() {
+ let font = Font {
+ asset: PathBuf::default(),
+ index: 0,
+ slant: Slant::Upright,
+ weight: WEIGHT_NORMAL,
+ width: Width::Normal,
+ languages: vec![],
+ package: None,
+ code_points: CharSet::new(vec![]),
+ };
+
+ assert!(Typeface::new(0, font, None).is_err())
+ }
+}
diff --git a/src/fonts/src/main.rs b/src/fonts/src/main.rs
index 239a37a..4d2b701 100644
--- a/src/fonts/src/main.rs
+++ b/src/fonts/src/main.rs
@@ -42,18 +42,20 @@
let mut service = FontService::new();
if !options.opt_present("n") {
- service.load_manifest(&PathBuf::from(FONT_MANIFEST_PATH))?;
+ let font_manifest_path = PathBuf::from(FONT_MANIFEST_PATH);
+ service.load_manifest(&font_manifest_path).await?;
let font_manifest_path = PathBuf::from(VENDOR_FONT_MANIFEST_PATH);
if font_manifest_path.exists() {
- service.load_manifest(&font_manifest_path)?;
+ service.load_manifest(&font_manifest_path).await?;
}
} else {
fx_vlog!(1, "no-default-fonts set, not loading fonts from default location");
}
for m in options.opt_strs("m") {
- service.load_manifest(&PathBuf::from(m.as_str()))?;
+ let path_buf = PathBuf::from(m.as_str());
+ service.load_manifest(&path_buf).await?;
}
service.check_can_start()?;
diff --git a/src/fonts/tests/ephemeral_manifest.json b/src/fonts/tests/ephemeral_manifest.json
new file mode 100644
index 0000000..115c5ca
--- /dev/null
+++ b/src/fonts/tests/ephemeral_manifest.json
@@ -0,0 +1,15 @@
+{
+ "families": [
+ {
+ "family": "Ephemeral",
+ "fallback": true,
+ "fonts": [
+ {
+ "asset": "Ephemeral.ttf",
+ "package": "fuchsia-pkg://fuchsia.com/font_package_ephemeral_ttf",
+ "code_points": "0,1,48+9,38,2+25"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/fonts/tests/font_provider_test.rs b/src/fonts/tests/font_provider_test.rs
index 98e6022..af5619a 100644
--- a/src/fonts/tests/font_provider_test.rs
+++ b/src/fonts/tests/font_provider_test.rs
@@ -3,9 +3,6 @@
// found in the LICENSE file.
#![feature(async_await)]
-// This is only needed because GN's invocation of the Rust compiler doesn't recognize the test_
-// methods as entry points, so it complains about the helper methods being "dead code".
-#![cfg(test)]
const FONTS_CMX: &str = "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx";
@@ -543,6 +540,71 @@
Ok(())
}
+
+ #[cfg(test)]
+ mod ephemeral {
+ use super::*;
+
+ fn start_provider_with_ephemeral_fonts() -> Result<(App, fonts::ProviderProxy), Error> {
+ let mut launch_options = LaunchOptions::new();
+ launch_options.add_dir_to_namespace(
+ "/test_fonts".to_string(),
+ std::fs::File::open("/pkg/data/testdata/test_fonts")?,
+ )?;
+
+ let launcher = launcher().context("Failed to open launcher service")?;
+ let app = launch_with_options(
+ &launcher,
+ FONTS_CMX.to_string(),
+ Some(vec![
+ "--no-default-fonts".to_string(),
+ "--font-manifest".to_string(),
+ "/test_fonts/ephemeral_manifest.json".to_string(),
+ ]),
+ launch_options,
+ )
+ .context("Failed to launch fonts::Provider")?;
+ let font_provider = app
+ .connect_to_service::<fonts::ProviderMarker>()
+ .context("Failed to connect to fonts::Provider")?;
+
+ Ok((app, font_provider))
+ }
+
+ #[fasync::run_singlethreaded(test)]
+ async fn test_ephemeral_get_font_family_info() -> Result<(), Error> {
+ let (_app, font_provider) = start_provider_with_ephemeral_fonts()?;
+
+ let mut family = fonts::FamilyName { name: "Ephemeral".to_string() };
+
+ let response = font_provider.get_font_family_info(&mut family).await?;
+
+ assert_eq!(response.name, Some(family));
+ Ok(())
+ }
+
+ #[fasync::run_singlethreaded(test)]
+ async fn test_ephemeral_get_typeface() -> Result<(), Error> {
+ let (_app, font_provider) = start_provider_with_ephemeral_fonts()?;
+
+ let family = Some(fonts::FamilyName { name: "Ephemeral".to_string() });
+ let query = Some(fonts::TypefaceQuery {
+ family,
+ style: None,
+ code_points: None,
+ languages: None,
+ fallback_family: None,
+ });
+ let request = fonts::TypefaceRequest { query, flags: None };
+
+ let response = font_provider.get_typeface(request).await?;
+
+ assert!(response.buffer.is_some(), "{:?}", response);
+ assert_eq!(response.buffer_id.unwrap(), 0, "{:?}", response);
+ assert_eq!(response.font_index.unwrap(), 0, "{:?}", response);
+ Ok(())
+ }
+ }
}
#[cfg(test)]
diff --git a/src/fonts/tests/mock_font_resolver.rs b/src/fonts/tests/mock_font_resolver.rs
new file mode 100644
index 0000000..3c455fb
--- /dev/null
+++ b/src/fonts/tests/mock_font_resolver.rs
@@ -0,0 +1,73 @@
+// 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.
+
+#![feature(async_await)]
+
+use {
+ failure::Error,
+ fidl::endpoints::ServerEnd,
+ fidl_fuchsia_io::{
+ DirectoryMarker, MODE_TYPE_DIRECTORY, OPEN_FLAG_DIRECTORY, OPEN_RIGHT_READABLE,
+ },
+ fidl_fuchsia_pkg::{FontResolverRequest, FontResolverRequestStream},
+ fuchsia_async as fasync,
+ fuchsia_component::server::ServiceFs,
+ fuchsia_syslog::{self as syslog, fx_vlog, macros::*},
+ fuchsia_url::pkg_url::PkgUrl,
+ fuchsia_vfs_pseudo_fs::{
+ directory::entry::DirectoryEntry, file::simple::read_only, pseudo_directory,
+ },
+ fuchsia_zircon::Status,
+ futures::{StreamExt, TryStreamExt},
+};
+
+#[fasync::run_singlethreaded]
+async fn main() -> Result<(), Error> {
+ syslog::init_with_tags(&["mock_font_resolver"])?;
+ fx_log_info!("Starting mock FontResolver service.");
+
+ let mut fs = ServiceFs::new_local();
+ fs.dir("svc").add_fidl_service(move |stream| {
+ fasync::spawn_local(async move {
+ run_resolver_service(stream).await.expect("Failed to run mock FontResolver.")
+ });
+ });
+ fs.take_and_serve_directory_handle()?;
+ fs.collect::<()>().await;
+ Ok(())
+}
+
+async fn run_resolver_service(mut stream: FontResolverRequestStream) -> Result<(), Error> {
+ while let Some(request) = stream.try_next().await? {
+ fx_vlog!(1, "FontResolver got request {:?}", request);
+ let FontResolverRequest::Resolve { package_url, directory_request, responder } = request;
+ let status = resolve(package_url, directory_request).await;
+ responder.send(Status::from(status).into_raw())?;
+ }
+ Ok(())
+}
+
+async fn resolve(
+ package_url: String,
+ directory_request: ServerEnd<DirectoryMarker>,
+) -> Result<(), Status> {
+ PkgUrl::parse(&package_url).map_err(|_| Err(Status::INVALID_ARGS))?;
+
+ let mut root = pseudo_directory! {
+ "Ephemeral.ttf" => read_only(|| Ok(b"not actually a font".to_vec())),
+ };
+
+ let flags = OPEN_RIGHT_READABLE | OPEN_FLAG_DIRECTORY;
+ let mode = MODE_TYPE_DIRECTORY;
+ let mut path = std::iter::empty();
+ let node = ServerEnd::from(directory_request.into_channel());
+
+ root.open(flags, mode, &mut path, node);
+
+ fasync::spawn(async move {
+ root.await;
+ });
+
+ Ok(())
+}