DirectWrite font backend for Windows

This commit is contained in:
Vladimir Vukicevic 2016-11-09 13:50:23 -05:00
parent d16f312464
commit db357b0334
9 changed files with 572 additions and 179 deletions

View file

@ -53,15 +53,12 @@ core-foundation = "0.2"
core-graphics = "0.4"
core-text = "2.0"
[target.'cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))'.dependencies]
freetype = {git = "https://github.com/servo/rust-freetype"}
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
freetype = {git = "https://github.com/servo/rust-freetype"}
servo-fontconfig = "0.2.1"
[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies]
simd = {git = "https://github.com/huonw/simd"}
[target.'cfg(target_os = "windows")'.dependencies]
winapi = "0.2"
gdi32-sys = "0.2"
dwrote = {git = "https://github.com/vvuk/dwrote-rs"}

View file

@ -29,8 +29,7 @@ extern crate bitflags;
#[cfg(target_os = "macos")] extern crate core_text;
// Windows-specific library dependencies
#[cfg(target_os = "windows")] extern crate gdi32;
#[cfg(target_os = "windows")] extern crate winapi;
#[cfg(target_os = "windows")] extern crate dwrote;
extern crate euclid;
extern crate fnv;
@ -38,10 +37,11 @@ extern crate fnv;
// Platforms that use Freetype/Fontconfig library dependencies
#[cfg(any(target_os = "linux", target_os = "android"))]
extern crate fontconfig;
extern crate fontsan;
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
#[cfg(any(target_os = "linux", target_os = "android"))]
extern crate freetype;
extern crate fontsan;
extern crate gfx_traits;
// Eventually we would like the shaper to be pluggable, as many operating systems have their own

View file

@ -2,19 +2,19 @@
* 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/. */
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use platform::freetype::{font, font_context};
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use platform::freetype::{font_list, font_template};
#[cfg(target_os = "windows")]
pub use platform::windows::{font_list, font_template};
pub use platform::windows::{font, font_context, font_list, font_template};
#[cfg(target_os = "macos")]
pub use platform::macos::{font, font_context, font_list, font_template};
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
#[cfg(any(target_os = "linux", target_os = "android"))]
mod freetype {
use libc::c_char;
use std::ffi::CStr;
@ -46,6 +46,8 @@ mod macos {
#[cfg(target_os = "windows")]
mod windows {
pub mod font;
pub mod font_context;
pub mod font_list;
pub mod font_template;
}

View file

@ -0,0 +1,204 @@
/* 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 font::{FontHandleMethods, FontMetrics, FontTableMethods};
use font::{FontTableTag, FractionalPixel};
use platform::font_template::{FontTemplateData};
use platform::windows::font_list::{font_from_atom};
use platform::windows::font_context::{FontContextHandle};
use std::sync::Arc;
use style::computed_values::{font_stretch, font_weight};
use text::glyph::GlyphId;
use dwrote::{Font, FontFace};
use dwrote::{FontWeight, FontStretch, FontStyle};
// 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
}
}
#[derive(Debug)]
pub struct FontHandle {
font_data: Arc<FontTemplateData>,
font: Font,
face: FontFace,
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, ()>
{
if let Some(_) = template.bytes {
// FIXME we should load from template.bytes
Err(())
} else {
let font = font_from_atom(&template.identifier);
let face = font.create_font_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(),
font: font,
face: face,
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.font.family_name()
}
fn face_name(&self) -> String {
self.font.face_name()
}
fn is_italic(&self) -> bool {
match self.font.style() {
FontStyle::Normal => false,
FontStyle::Oblique | FontStyle::Italic => true,
}
}
fn boldness(&self) -> font_weight::T {
match self.font.weight() {
FontWeight::Thin => font_weight::T::Weight100,
FontWeight::ExtraLight => font_weight::T::Weight200,
FontWeight::Light => font_weight::T::Weight300,
// slightly lighter gray
FontWeight::SemiLight => font_weight::T::Weight300,
FontWeight::Regular => font_weight::T::Weight400,
FontWeight::Medium => font_weight::T::Weight500,
FontWeight::SemiBold => font_weight::T::Weight600,
FontWeight::Bold => font_weight::T::Weight700,
FontWeight::ExtraBold => font_weight::T::Weight800,
FontWeight::Black => font_weight::T::Weight900,
// slightly blacker black
FontWeight::ExtraBlack => font_weight::T::Weight900,
}
}
fn stretchiness(&self) -> font_stretch::T {
match self.font.stretch() {
FontStretch::Undefined => font_stretch::T::normal,
FontStretch::UltraCondensed => font_stretch::T::ultra_condensed,
FontStretch::ExtraCondensed => font_stretch::T::extra_condensed,
FontStretch::Condensed => font_stretch::T::condensed,
FontStretch::SemiCondensed => font_stretch::T::semi_condensed,
FontStretch::Normal => font_stretch::T::normal,
FontStretch::SemiExpanded => font_stretch::T::semi_expanded,
FontStretch::Expanded => font_stretch::T::expanded,
FontStretch::ExtraExpanded => font_stretch::T::extra_expanded,
FontStretch::UltraExpanded => font_stretch::T::ultra_expanded,
}
}
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(dm.lineGap 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 })
}
}

View file

@ -0,0 +1,21 @@
/* 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/. */
use heapsize::HeapSizeOf;
#[derive(Clone, Debug)]
pub struct FontContextHandle;
impl FontContextHandle {
// *shrug*
pub fn new() -> FontContextHandle {
FontContextHandle {}
}
}
impl HeapSizeOf for FontContextHandle {
fn heap_size_of_children(&self) -> usize {
0
}
}

View file

@ -2,73 +2,70 @@
* 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 gdi32;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::ptr;
use winapi::{LOGFONTW, LPARAM, OUT_TT_ONLY_PRECIS, VOID};
use winapi::{c_int, DWORD, LF_FACESIZE};
use dwrote::{Font, FontDescriptor, FontCollection};
use servo_atoms::Atom;
use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::atomic::{Ordering, AtomicUsize};
lazy_static! {
static ref FONT_ATOM_COUNTER: AtomicUsize = AtomicUsize::new(1);
static ref FONT_ATOM_MAP: Mutex<HashMap<Atom, FontDescriptor>> = Mutex::new(HashMap::new());
}
pub static SANS_SERIF_FONT_FAMILY: &'static str = "Arial";
pub fn system_default_family(_: &str) -> Option<String> {
None
Some("Verdana".to_owned())
}
pub fn last_resort_font_families() -> Vec<String> {
vec!("Arial".to_owned())
}
unsafe extern "system" fn enum_font_callback(lpelfe: *const LOGFONTW,
_: *const VOID,
_: DWORD,
lparam: LPARAM) -> c_int {
let name = (*lpelfe).lfFaceName;
let term_pos = name.iter().position(|c| *c == 0).unwrap();
let name = OsString::from_wide(&name[0..term_pos]).into_string().unwrap();
let fonts = lparam as *mut Vec<String>;
let fonts = &mut *fonts;
fonts.push(name);
1
}
pub fn for_each_available_family<F>(mut callback: F) where F: FnMut(String) {
let mut fonts = Vec::new();
let mut config = LOGFONTW {
lfHeight: 0,
lfWidth: 0,
lfEscapement: 0,
lfOrientation: 0,
lfWeight: 0,
lfItalic: 0,
lfUnderline: 0,
lfStrikeOut: 0,
lfCharSet: 0,
lfOutPrecision: OUT_TT_ONLY_PRECIS as u8,
lfClipPrecision: 0,
lfQuality: 0,
lfPitchAndFamily: 0,
lfFaceName: [0; LF_FACESIZE],
};
unsafe {
let hdc = gdi32::CreateCompatibleDC(ptr::null_mut());
gdi32::EnumFontFamiliesExW(hdc,
&mut config,
Some(enum_font_callback),
&mut fonts as *mut Vec<String> as LPARAM,
0);
gdi32::DeleteDC(hdc);
}
for family in fonts {
callback(family);
let system_fc = FontCollection::system();
for family in system_fc.families_iter() {
callback(family.name());
}
}
// for_each_variation is supposed to return a string that can be
// atomized and then uniquely used to return back to this font.
// Some platforms use the full postscript name (MacOS X), or
// a font filename.
//
// For windows we're going to use just a basic integer value that
// we'll stringify, and then put them all in a HashMap with
// the actual FontDescriptor there.
pub fn for_each_variation<F>(family_name: &str, mut callback: F) where F: FnMut(String) {
callback(family_name.to_owned());
let system_fc = FontCollection::system();
if let Some(family) = system_fc.get_font_family_by_name(family_name) {
let count = family.get_font_count();
for i in 0..count {
let font = family.get_font(i);
let index = FONT_ATOM_COUNTER.fetch_add(1, Ordering::Relaxed);
let index_str = format!("{}", index);
let atom = Atom::from(index_str.clone());
{
let descriptor = font.to_descriptor();
let mut fonts = FONT_ATOM_MAP.lock().unwrap();
fonts.insert(atom, descriptor);
}
callback(index_str);
}
}
}
pub fn descriptor_from_atom(ident: &Atom) -> FontDescriptor {
let fonts = FONT_ATOM_MAP.lock().unwrap();
fonts.get(ident).unwrap().clone()
}
pub fn font_from_atom(ident: &Atom) -> Font {
let fonts = FONT_ATOM_MAP.lock().unwrap();
FontCollection::system().get_font_from_descriptor(fonts.get(ident).unwrap()).unwrap()
}

View file

@ -2,88 +2,56 @@
* 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 gdi32;
use app_units::Au;
use servo_atoms::Atom;
use std::ffi::OsString;
use std::io::Error;
use std::os::windows::ffi::OsStrExt;
use std::ptr;
use serde;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::io;
use std::ops::Deref;
use std::sync::Mutex;
use webrender_traits::NativeFontHandle;
use winapi::{DWORD, LF_FACESIZE, LOGFONTW, OUT_TT_ONLY_PRECIS, WCHAR};
const GDI_ERROR: DWORD = 0xffffffff;
use dwrote::{Font};
use platform::windows::font_list::{descriptor_from_atom, font_from_atom};
#[derive(Deserialize, Serialize, Debug)]
pub struct FontTemplateData {
pub bytes: Vec<u8>,
pub bytes: Option<Vec<u8>>,
pub identifier: Atom,
}
impl FontTemplateData {
pub fn new(identifier: Atom,
font_data: Option<Vec<u8>>) -> Result<FontTemplateData, Error> {
let bytes = match font_data {
Some(bytes) => {
bytes
},
None => {
assert!(identifier.len() < LF_FACESIZE);
let name = OsString::from(identifier.as_ref());
let buffer: Vec<WCHAR> = name.encode_wide().collect();
let mut string: [WCHAR; LF_FACESIZE] = [0; LF_FACESIZE];
for (src, dest) in buffer.iter().zip(string.iter_mut()) {
*dest = *src;
}
let config = LOGFONTW {
lfHeight: 0,
lfWidth: 0,
lfEscapement: 0,
lfOrientation: 0,
lfWeight: 0,
lfItalic: 0,
lfUnderline: 0,
lfStrikeOut: 0,
lfCharSet: 0,
lfOutPrecision: OUT_TT_ONLY_PRECIS as u8,
lfClipPrecision: 0,
lfQuality: 0,
lfPitchAndFamily: 0,
lfFaceName: string,
};
unsafe {
let hdc = gdi32::CreateCompatibleDC(ptr::null_mut());
let hfont = gdi32::CreateFontIndirectW(&config as *const _);
gdi32::SelectObject(hdc, hfont as *mut _);
let size = gdi32::GetFontData(hdc, 0, 0, ptr::null_mut(), 0);
assert!(size != GDI_ERROR);
let mut buffer: Vec<u8> = vec![0; size as usize];
let actual_size = gdi32::GetFontData(hdc, 0, 0, buffer.as_mut_ptr() as *mut _, size);
assert!(actual_size == size);
gdi32::DeleteDC(hdc);
gdi32::DeleteObject(hfont as *mut _);
buffer
}
}
};
font_data: Option<Vec<u8>>) -> Result<FontTemplateData, io::Error> {
Ok(FontTemplateData {
bytes: bytes,
bytes: font_data,
identifier: identifier,
})
}
pub fn bytes(&self) -> Vec<u8> {
self.bytes.clone()
if self.bytes.is_some() {
self.bytes.as_ref().unwrap().clone()
} else {
let font = font_from_atom(&self.identifier);
let face = font.create_font_face();
let files = face.get_files();
assert!(files.len() > 0);
files[0].get_font_file_bytes()
}
}
pub fn bytes_if_in_memory(&self) -> Option<Vec<u8>> {
Some(self.bytes())
self.bytes.clone()
}
pub fn native_font(&self) -> Option<NativeFontHandle> {
None
if self.bytes.is_some() {
panic!("Can't create fonts yet");
}
let descriptor = descriptor_from_atom(&self.identifier);
Some(descriptor)
}
}