Add basic support for web fonts. Synchronous loading only

for now, and only deals with TTF format fonts.

For an example, try loading http://icons.marekventur.de
This commit is contained in:
Glenn Watson 2014-07-08 14:55:38 +10:00
parent 4b689caa5d
commit 4e949cb58c
11 changed files with 288 additions and 42 deletions

View file

@ -11,6 +11,8 @@ use std::collections::HashMap;
use sync::Arc;
use font_template::{FontTemplate, FontTemplateDescriptor};
use platform::font_template::FontTemplateData;
use servo_net::resource_task::{ResourceTask, load_whole_resource};
use url::Url;
/// A list of font templates that make up a given font family.
struct FontFamily {
@ -42,11 +44,23 @@ impl FontFamily {
None
}
fn add_template(&mut self, identifier: &str, maybe_data: Option<Vec<u8>>) {
for template in self.templates.iter() {
if template.identifier() == identifier {
return;
}
}
let template = FontTemplate::new(identifier, maybe_data);
self.templates.push(template);
}
}
/// Commands that the FontContext sends to the font cache task.
pub enum Command {
GetFontTemplate(String, FontTemplateDescriptor, Sender<Reply>),
AddWebFont(Url, String, Sender<()>),
Exit(Sender<()>),
}
@ -61,7 +75,9 @@ struct FontCache {
port: Receiver<Command>,
generic_fonts: HashMap<String, String>,
local_families: HashMap<String, FontFamily>,
web_families: HashMap<String, FontFamily>,
font_context: FontContextHandle,
resource_task: ResourceTask,
}
impl FontCache {
@ -80,6 +96,23 @@ impl FontCache {
result.send(GetFontTemplateReply(font_template));
}
AddWebFont(url, family_name, result) => {
let maybe_resource = load_whole_resource(&self.resource_task, url.clone());
match maybe_resource {
Ok((_, bytes)) => {
if !self.web_families.contains_key(&family_name) {
let family = FontFamily::new();
self.web_families.insert(family_name.clone(), family);
}
let family = self.web_families.get_mut(&family_name);
family.add_template(format!("{}", url).as_slice(), Some(bytes));
},
Err(msg) => {
fail!(msg);
}
}
result.send(());
}
Exit(result) => {
result.send(());
break;
@ -105,9 +138,8 @@ impl FontCache {
}
}
fn find_font_in_family<'a>(&'a mut self,
family_name: &String,
desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> {
fn find_font_in_local_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor)
-> Option<Arc<FontTemplateData>> {
// TODO(Issue #188): look up localized font family names if canonical name not found
// look up canonical name
if self.local_families.contains_key(family_name) {
@ -116,8 +148,7 @@ impl FontCache {
if s.templates.len() == 0 {
get_variations_for_family(family_name.as_slice(), |path| {
let template = FontTemplate::new(path.as_slice());
s.templates.push(template);
s.add_template(path.as_slice(), None);
});
}
@ -135,16 +166,31 @@ impl FontCache {
}
}
fn find_font_in_web_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor)
-> Option<Arc<FontTemplateData>> {
if self.web_families.contains_key(family_name) {
let family = self.web_families.get_mut(family_name);
let maybe_font = family.find_font_for_style(desc, &self.font_context);
maybe_font
} else {
None
}
}
fn get_font_template(&mut self, family: &String, desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> {
let transformed_family_name = self.transform_family(family);
self.find_font_in_family(&transformed_family_name, desc)
let mut maybe_template = self.find_font_in_web_family(&transformed_family_name, desc);
if maybe_template.is_none() {
maybe_template = self.find_font_in_local_family(&transformed_family_name, desc);
}
maybe_template
}
fn get_last_resort_template(&mut self, desc: &FontTemplateDescriptor) -> Arc<FontTemplateData> {
let last_resort = get_last_resort_font_families();
for family in last_resort.iter() {
let maybe_font_in_family = self.find_font_in_family(family, desc);
let maybe_font_in_family = self.find_font_in_local_family(family, desc);
if maybe_font_in_family.is_some() {
return maybe_font_in_family.unwrap();
}
@ -162,7 +208,7 @@ pub struct FontCacheTask {
}
impl FontCacheTask {
pub fn new() -> FontCacheTask {
pub fn new(resource_task: ResourceTask) -> FontCacheTask {
let (chan, port) = channel();
spawn(proc() {
@ -178,7 +224,9 @@ impl FontCacheTask {
port: port,
generic_fonts: generic_fonts,
local_families: HashMap::new(),
web_families: HashMap::new(),
font_context: FontContextHandle::new(),
resource_task: resource_task,
};
cache.refresh_local_families();
@ -205,6 +253,12 @@ impl FontCacheTask {
}
}
pub fn add_web_font(&mut self, url: Url, family: &str) {
let (response_chan, response_port) = channel();
self.chan.send(AddWebFont(url, family.to_string(), response_chan));
response_port.recv();
}
pub fn exit(&self) {
let (response_chan, response_port) = channel();
self.chan.send(Exit(response_chan));

View file

@ -42,21 +42,42 @@ impl PartialEq for FontTemplateDescriptor {
pub struct FontTemplate {
identifier: String,
descriptor: Option<FontTemplateDescriptor>,
data: Option<Weak<FontTemplateData>>,
weak_ref: Option<Weak<FontTemplateData>>,
strong_ref: Option<Arc<FontTemplateData>>, // GWTODO: Add code path to unset the strong_ref for web fonts!
}
/// Holds all of the template information for a font that
/// is common, regardless of the number of instances of
/// this font handle per thread.
impl FontTemplate {
pub fn new(identifier: &str) -> FontTemplate {
pub fn new(identifier: &str, maybe_bytes: Option<Vec<u8>>) -> FontTemplate {
let maybe_data = match maybe_bytes {
Some(_) => Some(FontTemplateData::new(identifier, maybe_bytes)),
None => None,
};
let maybe_strong_ref = match maybe_data {
Some(data) => Some(Arc::new(data)),
None => None,
};
let maybe_weak_ref = match maybe_strong_ref {
Some(ref strong_ref) => Some(strong_ref.downgrade()),
None => None,
};
FontTemplate {
identifier: identifier.to_string(),
descriptor: None,
data: None,
weak_ref: maybe_weak_ref,
strong_ref: maybe_strong_ref,
}
}
pub fn identifier<'a>(&'a self) -> &'a str {
self.identifier.as_slice()
}
/// Get the data for creating a font if it matches a given descriptor.
pub fn get_if_matches(&mut self, fctx: &FontContextHandle,
requested_desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> {
@ -75,11 +96,9 @@ impl FontTemplate {
},
None => {
let data = self.get_data();
let handle = FontHandleMethods::new_from_template(fctx, data.clone(), None);
let handle: FontHandle = match handle {
Ok(handle) => handle,
Err(()) => fail!("TODO - Handle failure to create a font from template."),
};
let handle: Result<FontHandle, ()> = FontHandleMethods::new_from_template(fctx, data.clone(), None);
match handle {
Ok(handle) => {
let actual_desc = FontTemplateDescriptor::new(handle.boldness(),
handle.is_italic());
let desc_match = actual_desc == *requested_desc;
@ -91,6 +110,12 @@ impl FontTemplate {
None
}
}
Err(()) => {
debug!("Unable to create a font from template {}", self.identifier);
None
}
}
}
}
}
@ -98,7 +123,7 @@ impl FontTemplate {
/// exist, it will return a clone, otherwise it will load the
/// font data and store a weak reference to it internally.
pub fn get_data(&mut self) -> Arc<FontTemplateData> {
let maybe_data = match self.data {
let maybe_data = match self.weak_ref {
Some(ref data) => data.upgrade(),
None => None,
};
@ -106,8 +131,9 @@ impl FontTemplate {
match maybe_data {
Some(data) => data,
None => {
let template_data = Arc::new(FontTemplateData::new(self.identifier.as_slice()));
self.data = Some(template_data.downgrade());
assert!(self.strong_ref.is_none());
let template_data = Arc::new(FontTemplateData::new(self.identifier.as_slice(), None));
self.weak_ref = Some(template_data.downgrade());
template_data
}
}

View file

@ -31,6 +31,7 @@ extern crate servo_util = "util";
extern crate servo_msg = "msg";
extern crate style;
extern crate sync;
extern crate url;
// Eventually we would like the shaper to be pluggable, as many operating systems have their own
// shapers. For now, however, this is a hard dependency.

View file

@ -15,10 +15,17 @@ pub struct FontTemplateData {
}
impl FontTemplateData {
pub fn new(identifier: &str) -> FontTemplateData {
pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
let bytes = match font_data {
Some(bytes) => {
bytes
},
None => {
// TODO: Handle file load failure!
let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap();
let bytes = file.read_to_end().unwrap();
file.read_to_end().unwrap()
},
};
FontTemplateData {
bytes: bytes,

View file

@ -15,10 +15,17 @@ pub struct FontTemplateData {
}
impl FontTemplateData {
pub fn new(identifier: &str) -> FontTemplateData {
pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
let bytes = match font_data {
Some(bytes) => {
bytes
},
None => {
// TODO: Handle file load failure!
let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap();
let bytes = file.read_to_end().unwrap();
file.read_to_end().unwrap()
},
};
FontTemplateData {
bytes: bytes,

View file

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use core_graphics::data_provider::CGDataProvider;
use core_graphics::font::CGFont;
use core_text::font::CTFont;
use core_text;
@ -15,10 +17,20 @@ pub struct FontTemplateData {
}
impl FontTemplateData {
pub fn new(identifier: &str) -> FontTemplateData {
let ctfont_result = core_text::font::new_from_name(identifier.as_slice(), 0.0);
pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
let ctfont = match font_data {
Some(bytes) => {
let fontprov = CGDataProvider::from_buffer(bytes.as_slice());
let cgfont = CGFont::from_data_provider(fontprov);
core_text::font::new_from_CGFont(&cgfont, 0.0)
},
None => {
core_text::font::new_from_name(identifier.as_slice(), 0.0).unwrap()
}
};
FontTemplateData {
ctfont: ctfont_result.unwrap(),
ctfont: ctfont,
identifier: identifier.to_string(),
}
}

View file

@ -60,6 +60,8 @@ use std::mem;
use std::ptr;
use std::task::TaskBuilder;
use style::{AuthorOrigin, Stylesheet, Stylist};
use style::CSSFontFaceRule;
use style::TtfFormat;
use sync::{Arc, Mutex};
use url::Url;
@ -450,7 +452,26 @@ impl LayoutTask {
}
fn handle_add_stylesheet(&mut self, sheet: Stylesheet) {
self.stylist.add_stylesheet(sheet, AuthorOrigin)
// Find all font-face rules and notify the font cache of them.
// GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!)
// GWTODO: Need to handle font-face nested within media rules.
for rule in sheet.rules.iter() {
match rule {
&CSSFontFaceRule(ref font_face_rule) => {
for source in font_face_rule.sources.iter() {
match source.format {
TtfFormat => {
self.font_cache_task.add_web_font(source.url.clone(), font_face_rule.family.as_slice());
},
_ => {}
}
}
},
_ => {}
}
}
self.stylist.add_stylesheet(sheet, AuthorOrigin);
}
/// Retrieves the flow tree root from the root node.

View file

@ -124,7 +124,7 @@ pub fn run(opts: opts::Opts) {
} else {
ImageCacheTask::new(resource_task.clone())
};
let font_cache_task = FontCacheTask::new();
let font_cache_task = FontCacheTask::new(resource_task.clone());
let constellation_chan = Constellation::<layout::layout_task::LayoutTask>::start(
compositor_chan,
opts,

View file

@ -0,0 +1,112 @@
use cssparser::ast::*;
use cssparser::parse_declaration_list;
use errors::{ErrorLoggerIterator, log_css_error};
use std::ascii::StrAsciiExt;
use parsing_utils::one_component_value;
use stylesheets::{CSSRule, CSSFontFaceRule};
use url::Url;
use servo_util::url::parse_url;
#[deriving(PartialEq)]
pub enum FontFaceFormat {
UnknownFormat,
WoffFormat,
TtfFormat,
SvgFormat,
EotFormat,
}
pub struct FontFaceSource {
pub url: Url,
pub format: FontFaceFormat,
}
pub struct FontFaceRule {
pub family: String,
pub sources: Vec<FontFaceSource>,
}
pub fn parse_font_face_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>, base_url: &Url) {
let mut font_face_rule = FontFaceRule {
family: "".to_string(),
sources: vec!()
};
let block = match rule.block {
Some(block) => block,
None => {
log_css_error(rule.location, "Invalid @font-face rule");
return
}
};
for item in ErrorLoggerIterator(parse_declaration_list(block.move_iter())) {
match item {
DeclAtRule(rule) => log_css_error(
rule.location, format!("Unsupported at-rule in declaration list: @{:s}", rule.name).as_slice()),
Declaration(Declaration{ location: location, name: name, value: value, important: _}) => {
let name_lower = name.as_slice().to_ascii_lower();
match name_lower.as_slice() {
"font-family" => {
match one_component_value(value.as_slice()) {
Some(&String(ref string_value)) => {
font_face_rule.family = string_value.clone();
},
_ => {
log_css_error(location, format!("Unsupported font-family string {:s}", name).as_slice());
}
}
},
"src" => {
for component_value in value.as_slice().skip_whitespace() {
match component_value {
&URL(ref string_value) => {
let font_url = parse_url(string_value.as_slice(), Some(base_url.clone()));
let src = FontFaceSource { url: font_url, format: UnknownFormat };
font_face_rule.sources.push(src);
},
&Function(ref string_value, ref values) => {
match string_value.as_slice() {
"format" => {
let format = one_component_value(values.as_slice()).and_then(|c| {
match c {
&String(ref format_string) => Some(format_string.as_slice().to_ascii_lower()),
_ => None,
}
});
match font_face_rule.sources.mut_last() {
Some(source) => {
source.format = match format.unwrap_or("".to_string()).as_slice() {
"embedded-opentype" => EotFormat,
"woff" => WoffFormat,
"truetype" | "opentype" => TtfFormat,
"svg" => SvgFormat,
_ => UnknownFormat,
}
}
None => {}
};
},
"local" => {
log_css_error(location, "local font face not supported yet!");
},
_ => {
log_css_error(location, format!("Unsupported declaration {}", string_value).as_slice());
}
}
},
_ => {}
}
}
},
_ => {
log_css_error(location, format!("Unsupported declaration {:s}", name).as_slice());
}
}
}
}
}
parent_rules.push(CSSFontFaceRule(font_face_rule));
}

View file

@ -32,7 +32,7 @@ extern crate servo_util = "util";
// Public API
pub use stylesheets::{Stylesheet, CSSRule, StyleRule};
pub use stylesheets::{Stylesheet, CSSRule, StyleRule, CSSFontFaceRule};
pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin};
pub use selector_matching::{MatchedProperty, matches_compound_selector};
pub use properties::{cascade, cascade_anonymous};
@ -47,6 +47,7 @@ pub use selectors::{NamespaceConstraint, Selector, CompoundSelector, SimpleSelec
pub use selectors::{parse_selector_list};
pub use namespaces::NamespaceMap;
pub use media_queries::{MediaRule, MediaQueryList, MediaQuery, Device, MediaType, MediaQueryType};
pub use font_face::{FontFaceFormat, FontFaceRule, FontFaceSource, TtfFormat};
mod stylesheets;
mod errors;
@ -57,3 +58,4 @@ mod namespaces;
mod node;
mod media_queries;
mod parsing_utils;
mod font_face;

View file

@ -16,6 +16,7 @@ use errors::{ErrorLoggerIterator, log_css_error};
use namespaces::{NamespaceMap, parse_namespace_rule};
use media_queries::{MediaRule, parse_media_rule};
use media_queries;
use font_face::{FontFaceRule, parse_font_face_rule};
pub struct Stylesheet {
@ -28,6 +29,7 @@ pub struct Stylesheet {
pub enum CSSRule {
CSSStyleRule(StyleRule),
CSSMediaRule(MediaRule),
CSSFontFaceRule(FontFaceRule),
}
@ -143,6 +145,7 @@ pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule,
parent_rules: &mut Vec<CSSRule>, namespaces: &NamespaceMap, base_url: &Url) {
match lower_name {
"media" => parse_media_rule(rule, parent_rules, namespaces, base_url),
"font-face" => parse_font_face_rule(rule, parent_rules, base_url),
_ => log_css_error(rule.location,
format!("Unsupported at-rule: @{:s}", lower_name).as_slice())
}
@ -156,7 +159,8 @@ pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device,
CSSStyleRule(ref rule) => callback(rule),
CSSMediaRule(ref rule) => if rule.media_queries.evaluate(device) {
iter_style_rules(rule.rules.as_slice(), device, |s| callback(s))
}
},
CSSFontFaceRule(_) => {},
}
}
}