servo/components/gfx/platform/windows/font.rs
Jon Leighton f22e5ef3bd Lazy load fonts in a FontGroup
This is a step towards fixing #17267. To fix that, we need to be able to
try various different fallback fonts in turn, which would become
unweildy with the prior eager-loading strategy.

Prior to this change, FontGroup loaded up all Font instances, including
the fallback font, before any of them were checked for the presence of
the glyphs we're trying to render.

So for the following CSS:

    font-family: Helvetica, Arial;

The FontGroup would contain a Font instance for Helvetica, and a Font
instance for Arial, and a Font instance for the fallback font.

It may be that Helvetica contains glyphs for every character in the
document, and therefore Arial and the fallback font are not needed at
all.

This change makes the strategy lazy, so that we'll only create a Font
for Arial if we cannot find a glyph within Helvetica. I've also
substantially refactored the existing code in the process and added
some documentation along the way.
2018-02-22 16:36:05 +01:00

382 lines
13 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// NOTE: https://www.chromium.org/directwrite-font-proxy has useful
// information for an approach that we'll likely need to take when the
// renderer moves to a sandboxed process.
use app_units::Au;
use dwrote;
use dwrote::{Font, FontFace, FontFile};
use dwrote::{FontWeight, FontStretch, FontStyle};
use font::{FontHandleMethods, FontMetrics, FontTableMethods};
use font::{FontTableTag, FractionalPixel};
use platform::font_template::FontTemplateData;
use platform::windows::font_context::FontContextHandle;
use platform::windows::font_list::font_from_atom;
use servo_atoms::Atom;
use std::sync::Arc;
use style::computed_values::font_stretch::T as StyleFontStretch;
use style::computed_values::font_weight::T as StyleFontWeight;
use text::glyph::GlyphId;
use truetype;
// 1em = 12pt = 16px, assuming 72 points per inch and 96 px per inch
fn pt_to_px(pt: f64) -> f64 { pt / 72. * 96. }
fn em_to_px(em: f64) -> f64 { em * 16. }
fn au_from_em(em: f64) -> Au { Au::from_f64_px(em_to_px(em)) }
fn au_from_pt(pt: f64) -> Au { Au::from_f64_px(pt_to_px(pt)) }
pub struct FontTable {
data: Vec<u8>,
}
impl FontTable {
pub fn wrap(data: &[u8]) -> FontTable {
FontTable { data: data.to_vec() }
}
}
impl FontTableMethods for FontTable {
fn buffer(&self) -> &[u8] {
&self.data
}
}
fn make_tag(tag_bytes: &[u8]) -> FontTableTag {
assert_eq!(tag_bytes.len(), 4);
unsafe { *(tag_bytes.as_ptr() as *const FontTableTag) }
}
macro_rules! try_lossy(($result:expr) => ($result.map_err(|_| (()))?));
// Given a set of records, figure out the string indices for the family and face
// names. We want name_id 1 and 2, and we need to use platform_id == 1 and
// language_id == 0 to avoid limitations in the truetype crate. We *could* just
// do our own parsing here, and use the offset/length data and pull the values out
// ourselves.
fn get_family_face_indices(records: &[truetype::naming_table::Record]) -> Option<(usize, usize)> {
let mut family_name_index = None;
let mut face_name_index = None;
for i in 0..records.len() {
// the truetype crate can only decode mac platform format names
if records[i].platform_id != 1 {
continue;
}
if records[i].language_id != 0 {
continue;
}
if records[i].name_id == 1 {
family_name_index = Some(i);
} else if records[i].name_id == 2 {
face_name_index = Some(i);
}
}
if family_name_index.is_some() && face_name_index.is_some() {
Some((family_name_index.unwrap(), face_name_index.unwrap()))
} else {
None
}
}
// We need the font (DWriteFont) in order to be able to query things like
// the family name, face name, weight, etc. On Windows 10, the
// DWriteFontFace3 interface provides this on the FontFace, but that's only
// available on Win10+.
//
// Instead, we do the parsing work using the truetype crate for raw fonts.
// We're just extracting basic info, so this is sufficient for now.
#[derive(Debug)]
struct FontInfo {
family_name: String,
face_name: String,
weight: StyleFontWeight,
stretch: StyleFontStretch,
style: FontStyle,
}
impl FontInfo {
fn new_from_face(face: &FontFace) -> Result<FontInfo, ()> {
use std::cmp::{min, max};
use std::io::Cursor;
use truetype::{NamingTable, Value, WindowsMetrics};
let name_table_bytes = face.get_font_table(make_tag(b"name"));
let os2_table_bytes = face.get_font_table(make_tag(b"OS/2"));
if name_table_bytes.is_none() || os2_table_bytes.is_none() {
return Err(());
}
let mut name_table_cursor = Cursor::new(name_table_bytes.as_ref().unwrap());
let names = try_lossy!(NamingTable::read(&mut name_table_cursor));
let (family, face) = match names {
NamingTable::Format0(ref table) => {
if let Some((family_index, face_index)) = get_family_face_indices(&table.records) {
let strings = table.strings().unwrap();
let family = strings[family_index].clone();
let face = strings[face_index].clone();
((family, face))
} else {
return Err(());
}
},
NamingTable::Format1(ref table) => {
if let Some((family_index, face_index)) = get_family_face_indices(&table.records) {
let strings = table.strings().unwrap();
let family = strings[family_index].clone();
let face = strings[face_index].clone();
((family, face))
} else {
return Err(());
}
}
};
let mut os2_table_cursor = Cursor::new(os2_table_bytes.as_ref().unwrap());
let metrics = try_lossy!(WindowsMetrics::read(&mut os2_table_cursor));
let (weight_val, width_val, italic_bool) = match metrics {
WindowsMetrics::Version0(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version1(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version2(ref m) |
WindowsMetrics::Version3(ref m) |
WindowsMetrics::Version4(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version5(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
};
let weight =
StyleFontWeight::from_int(
min(9, max(1, weight_val as i32 / 100)) * 100
).unwrap();
let stretch = match min(9, max(1, width_val)) {
1 => StyleFontStretch::UltraCondensed,
2 => StyleFontStretch::ExtraCondensed,
3 => StyleFontStretch::Condensed,
4 => StyleFontStretch::SemiCondensed,
5 => StyleFontStretch::Normal,
6 => StyleFontStretch::SemiExpanded,
7 => StyleFontStretch::Expanded,
8 => StyleFontStretch::ExtraExpanded,
9 => StyleFontStretch::UltraExpanded,
_ => return Err(()),
};
let style = if italic_bool {
FontStyle::Italic
} else {
FontStyle::Normal
};
Ok(FontInfo {
family_name: family,
face_name: face,
weight: weight,
stretch: stretch,
style: style,
})
}
fn new_from_font(font: &Font) -> Result<FontInfo, ()> {
let style = font.style();
let weight = StyleFontWeight(match font.weight() {
FontWeight::Thin => 100,
FontWeight::ExtraLight => 200,
FontWeight::Light => 300,
// slightly grayer gray
FontWeight::SemiLight => 300,
FontWeight::Regular => 400,
FontWeight::Medium => 500,
FontWeight::SemiBold => 600,
FontWeight::Bold => 700,
FontWeight::ExtraBold => 800,
FontWeight::Black => 900,
// slightly blacker black
FontWeight::ExtraBlack => 900,
});
let stretch = match font.stretch() {
FontStretch::Undefined => StyleFontStretch::Normal,
FontStretch::UltraCondensed => StyleFontStretch::UltraCondensed,
FontStretch::ExtraCondensed => StyleFontStretch::ExtraCondensed,
FontStretch::Condensed => StyleFontStretch::Condensed,
FontStretch::SemiCondensed => StyleFontStretch::SemiCondensed,
FontStretch::Normal => StyleFontStretch::Normal,
FontStretch::SemiExpanded => StyleFontStretch::SemiExpanded,
FontStretch::Expanded => StyleFontStretch::Expanded,
FontStretch::ExtraExpanded => StyleFontStretch::ExtraExpanded,
FontStretch::UltraExpanded => StyleFontStretch::UltraExpanded,
};
Ok(FontInfo {
family_name: font.family_name(),
face_name: font.face_name(),
style: style,
weight: weight,
stretch: stretch,
})
}
}
#[derive(Debug)]
pub struct FontHandle {
font_data: Arc<FontTemplateData>,
face: FontFace,
info: FontInfo,
em_size: f32,
du_per_em: f32,
du_to_px: f32,
scaled_du_to_px: f32,
}
impl FontHandle {
}
impl FontHandleMethods for FontHandle {
fn new_from_template(_: &FontContextHandle, template: Arc<FontTemplateData>, pt_size: Option<Au>)
-> Result<Self, ()>
{
let (info, face) = if let Some(ref raw_font) = template.bytes {
let font_file = FontFile::new_from_data(&raw_font);
if font_file.is_none() {
// failed to load raw font
return Err(());
}
let face = font_file.unwrap().create_face(0, dwrote::DWRITE_FONT_SIMULATIONS_NONE);
let info = FontInfo::new_from_face(&face)?;
(info, face)
} else {
let font = font_from_atom(&template.identifier);
let face = font.create_font_face();
let info = FontInfo::new_from_font(&font)?;
(info, face)
};
let pt_size = pt_size.unwrap_or(au_from_pt(12.));
let du_per_em = face.metrics().designUnitsPerEm as f32;
let em_size = pt_size.to_f32_px() / 16.;
let design_units_per_pixel = du_per_em / 16.;
let design_units_to_pixels = 1. / design_units_per_pixel;
let scaled_design_units_to_pixels = em_size / design_units_per_pixel;
Ok(FontHandle {
font_data: template.clone(),
face: face,
info: info,
em_size: em_size,
du_per_em: du_per_em,
du_to_px: design_units_to_pixels,
scaled_du_to_px: scaled_design_units_to_pixels,
})
}
fn template(&self) -> Arc<FontTemplateData> {
self.font_data.clone()
}
fn family_name(&self) -> String {
self.info.family_name.clone()
}
fn face_name(&self) -> Option<String> {
Some(self.info.face_name.clone())
}
fn is_italic(&self) -> bool {
match self.info.style {
FontStyle::Normal => false,
FontStyle::Oblique | FontStyle::Italic => true,
}
}
fn boldness(&self) -> StyleFontWeight {
self.info.weight
}
fn stretchiness(&self) -> StyleFontStretch {
self.info.stretch
}
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
let glyph = self.face.get_glyph_indices(&[codepoint as u32])[0];
if glyph == 0 {
return None;
}
Some(glyph as GlyphId)
}
fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
if glyph == 0 {
return None;
}
let gm = self.face.get_design_glyph_metrics(&[glyph as u16], false)[0];
let f = (gm.advanceWidth as f32 * self.scaled_du_to_px) as FractionalPixel;
Some(f)
}
/// Can this font do basic horizontal LTR shaping without Harfbuzz?
fn can_do_fast_shaping(&self) -> bool {
// TODO copy CachedKernTable from the MacOS X implementation to
// somehwere global and use it here. We could also implement the
// IDirectWriteFontFace1 interface and use the glyph kerning pair
// methods there.
false
}
fn glyph_h_kerning(&self, _: GlyphId, _: GlyphId) -> FractionalPixel {
0.0
}
fn metrics(&self) -> FontMetrics {
let dm = self.face.metrics();
let au_from_du = |du| -> Au { Au::from_f32_px(du as f32 * self.du_to_px) };
let au_from_du_s = |du| -> Au { Au:: from_f32_px(du as f32 * self.scaled_du_to_px) };
// anything that we calculate and don't just pull out of self.face.metrics
// is pulled out here for clarity
let leading = dm.ascent - dm.capHeight;
let metrics = FontMetrics {
underline_size: au_from_du(dm.underlineThickness as i32),
underline_offset: au_from_du_s(dm.underlinePosition as i32),
strikeout_size: au_from_du(dm.strikethroughThickness as i32),
strikeout_offset: au_from_du_s(dm.strikethroughPosition as i32),
leading: au_from_du_s(leading as i32),
x_height: au_from_du_s(dm.xHeight as i32),
em_size: au_from_em(self.em_size as f64),
ascent: au_from_du_s(dm.ascent as i32),
descent: au_from_du_s(dm.descent as i32),
max_advance: au_from_pt(0.0), // FIXME
average_advance: au_from_pt(0.0), // FIXME
line_gap: au_from_du_s((dm.ascent + dm.descent + dm.lineGap as u16) as i32),
};
debug!("Font metrics (@{} pt): {:?}", self.em_size * 12., metrics);
metrics
}
fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
self.face.get_font_table(tag).map(|bytes| FontTable { data: bytes })
}
fn identifier(&self) -> Atom {
self.font_data.identifier.clone()
}
}