blob: d80bd5fdf670e1f234194937cd75d6c0c5f87f7f [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 failure::{self, format_err, ResultExt};
use fidl_fuchsia_fonts as fonts;
use serde::de::{self, Deserialize, Deserializer, Error};
use serde_derive::Deserialize;
use serde_json;
use std::fmt;
use std::fs::{self, File};
use std::io::Read;
use std::path::{Path, PathBuf};
// Following structs are used to parse manifest.json.
#[derive(Debug, Deserialize)]
pub struct FontsManifest {
pub fallback: Option<String>,
pub families: Vec<Family>,
}
#[derive(Debug, Deserialize)]
pub struct Family {
pub family: String,
pub aliases: Option<Vec<String>>,
pub fonts: Vec<Font>,
#[serde(default = "default_fallback")]
pub fallback: bool,
#[serde(
default = "default_fallback_group",
deserialize_with = "deserialize_fallback_group"
)]
pub fallback_group: fonts::FallbackGroup,
}
pub type LanguageSet = Vec<String>;
#[derive(Debug, Deserialize)]
pub struct Font {
pub asset: PathBuf,
#[serde(default = "default_index")]
pub index: u32,
#[serde(default = "default_slant", deserialize_with = "deserialize_slant")]
pub slant: fonts::Slant,
#[serde(default = "default_weight")]
pub weight: u32,
#[serde(default = "default_width")]
pub width: u32,
#[serde(
default = "default_language",
deserialize_with = "deserialize_language"
)]
pub language: LanguageSet,
}
fn default_fallback() -> bool {
false
}
fn default_fallback_group() -> fonts::FallbackGroup {
fonts::FallbackGroup::None
}
fn default_index() -> u32 {
0
}
fn default_slant() -> fonts::Slant {
fonts::Slant::Upright
}
fn default_weight() -> u32 {
400
}
fn default_width() -> u32 {
5
}
fn default_language() -> LanguageSet {
LanguageSet::new()
}
fn deserialize_fallback_group<'d, D>(deserializer: D) -> Result<fonts::FallbackGroup, D::Error>
where
D: Deserializer<'d>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"serif" => Ok(fonts::FallbackGroup::Serif),
"sans_serif" | "sans-serif" => Ok(fonts::FallbackGroup::SansSerif),
"monospace" => Ok(fonts::FallbackGroup::Monospace),
"cursive" => Ok(fonts::FallbackGroup::Cursive),
"fantasy" => Ok(fonts::FallbackGroup::Fantasy),
x => Err(D::Error::custom(format!(
"unknown value for fallback_group in manifest: {}",
x
))),
}
}
fn deserialize_slant<'d, D>(deserializer: D) -> Result<fonts::Slant, D::Error>
where
D: Deserializer<'d>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"upright" => Ok(fonts::Slant::Upright),
"italic" => Ok(fonts::Slant::Italic),
x => Err(D::Error::custom(format!(
"unknown value for slant in manifest: {}",
x
))),
}
}
// Helper used to deserialize language field for a font. Language field can
// contain either a single string or an array of strings.
fn deserialize_language<'d, D>(deserializer: D) -> Result<LanguageSet, D::Error>
where
D: Deserializer<'d>,
{
struct LanguageSetVisitor;
impl<'de> de::Visitor<'de> for LanguageSetVisitor {
type Value = Vec<String>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or list of strings")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(vec![s.to_owned()])
}
fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
where
S: de::SeqAccess<'de>,
{
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))
}
}
deserializer.deserialize_any(LanguageSetVisitor)
}
impl FontsManifest {
pub fn load_from_file(path: &Path) -> Result<FontsManifest, failure::Error> {
let path = fs::canonicalize(path)?;
let base_dir = path
.parent()
.ok_or(format_err!("Invalid manifest path: {}", path.display()))?;
let mut f = File::open(path.clone())?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
let mut manifest: FontsManifest = serde_json::from_str(&contents)
.context(format!("Failed to load {}", path.display()))?;
// Make sure all paths are absolute.
for family in manifest.families.iter_mut() {
for font in family.fonts.iter_mut() {
if font.asset.is_relative() {
font.asset = base_dir.join(font.asset.clone());
}
}
}
Ok(manifest)
}
}