Make macOS font rendering not depend on FreeType

This commit is contained in:
Patrick Walton 2017-10-20 12:10:57 -07:00
parent a523d71e3c
commit 9c470e77c1
7 changed files with 448 additions and 216 deletions

View File

@ -248,7 +248,7 @@ fn partition_font(request: Json<PartitionFontRequest>)
File::open(path).expect("Couldn't find builtin font!")
.read_to_end(&mut data)
.expect("Couldn't read builtin font!");
data
Arc::new(data)
}
None => return Err(PartitionFontError::UnknownBuiltinFont),
}
@ -262,7 +262,7 @@ fn partition_font(request: Json<PartitionFontRequest>)
// Sanitize.
match fontsan::process(&unsafe_otf_data) {
Ok(otf_data) => otf_data,
Ok(otf_data) => Arc::new(otf_data),
Err(_) => return Err(PartitionFontError::FontSanitizationFailed),
}
}

View File

@ -3,14 +3,30 @@ name = "pathfinder_font_renderer"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[features]
default = []
freetype = ["freetype-sys"]
[dependencies]
app_units = "0.5"
euclid = "0.15"
freetype-sys = "0.6"
log = "0.3"
[dependencies.freetype-sys]
version = "0.6"
optional = true
[dependencies.pathfinder_path_utils]
path = "../path-utils"
[target.'not(cfg(target_os = "macos"))'.dependencies]
freetype-sys = "0.6"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.11"
[target.'cfg(target_os = "macos")'.dependencies.core-text]
git = "https://github.com/servo/core-text-rs.git"
[dev-dependencies]
env_logger = "0.4"

View File

@ -0,0 +1,173 @@
// pathfinder/font-renderer/src/core_graphics.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use core_graphics_sys::data_provider::CGDataProvider;
use core_graphics_sys::font::{CGFont, CGGlyph};
use core_graphics_sys::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CGPoint, CGRect};
use core_graphics_sys::geometry::{CGSize, CG_ZERO_POINT};
use core_graphics_sys::path::{CGPath, CGPathElementType};
use core_text::font::CTFont;
use core_text;
use euclid::{Point2D, Size2D};
use pathfinder_path_utils::PathCommand;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::sync::Arc;
use {FontInstanceKey, FontKey, GlyphDimensions, GlyphKey};
pub struct FontContext {
core_graphics_fonts: BTreeMap<FontKey, CGFont>,
core_text_fonts: BTreeMap<FontInstanceKey, CTFont>,
}
impl FontContext {
pub fn new() -> FontContext {
FontContext {
core_graphics_fonts: BTreeMap::new(),
core_text_fonts: BTreeMap::new(),
}
}
pub fn add_font_from_memory(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, _: u32)
-> Result<(), ()> {
match self.core_graphics_fonts.entry(*font_key) {
Entry::Occupied(_) => Ok(()),
Entry::Vacant(entry) => {
let data_provider = CGDataProvider::from_buffer(&**bytes);
let core_graphics_font = try!(CGFont::from_data_provider(data_provider));
entry.insert(core_graphics_font);
Ok(())
}
}
}
pub fn delete_font(&mut self, font_key: &FontKey) {
self.core_graphics_fonts.remove(font_key);
let core_text_font_keys: Vec<_> = self.core_text_fonts
.keys()
.filter(|key| key.font_key == *font_key)
.cloned()
.collect();
for core_text_font_key in &core_text_font_keys {
self.core_text_fonts.remove(core_text_font_key);
}
}
fn ensure_core_text_font(&mut self, font_instance_key: &FontInstanceKey)
-> Result<CTFont, ()> {
match self.core_text_fonts.entry(*font_instance_key) {
Entry::Occupied(entry) => Ok((*entry.get()).clone()),
Entry::Vacant(entry) => {
let core_graphics_font = match self.core_graphics_fonts
.get(&font_instance_key.font_key) {
None => return Err(()),
Some(core_graphics_font) => core_graphics_font,
};
let core_text_font = try!(font_instance_key.instantiate(&core_graphics_font));
entry.insert(core_text_font.clone());
Ok(core_text_font)
}
}
}
pub fn glyph_dimensions(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Option<GlyphDimensions> {
let core_graphics_font = match self.core_graphics_fonts.get(&font_instance.font_key) {
None => return None,
Some(core_graphics_font) => core_graphics_font,
};
let glyph = glyph_key.glyph_index as CGGlyph;
let mut bounding_boxes = [CGRect::new(&CG_ZERO_POINT, &CGSize::new(0.0, 0.0))];
let mut advances = [0];
if !core_graphics_font.get_glyph_b_boxes(&[glyph], &mut bounding_boxes) ||
!core_graphics_font.get_glyph_advances(&[glyph], &mut advances) {
return None
}
Some(GlyphDimensions {
origin: Point2D::new(bounding_boxes[0].origin.x.round() as i32,
bounding_boxes[0].origin.y.round() as i32),
size: Size2D::new(bounding_boxes[0].size.width.round() as u32,
bounding_boxes[0].size.height.round() as u32),
advance: advances[0] as f32,
})
}
pub fn glyph_outline(&mut self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Result<GlyphOutline, ()> {
//println!("GlyphOutline({:?})", font_instance);
let core_text_font = try!(self.ensure_core_text_font(font_instance));
let path = try!(core_text_font.create_path_for_glyph(glyph_key.glyph_index as CGGlyph,
&CG_AFFINE_TRANSFORM_IDENTITY));
Ok(GlyphOutline::new(&path))
}
}
impl FontInstanceKey {
fn instantiate(&self, core_graphics_font: &CGFont) -> Result<CTFont, ()> {
Ok(core_text::font::new_from_CGFont(core_graphics_font, self.size.to_f64_px()))
}
}
pub struct GlyphOutline {
commands: Vec<PathCommand>,
index: usize,
}
impl Iterator for GlyphOutline {
type Item = PathCommand;
fn next(&mut self) -> Option<PathCommand> {
match self.commands.get(self.index) {
None => None,
Some(command) => {
self.index += 1;
Some(*command)
}
}
}
}
impl GlyphOutline {
fn new(path: &CGPath) -> GlyphOutline {
let mut commands = vec![];
path.apply(&|element| {
let points = element.points();
commands.push(match element.element_type {
CGPathElementType::MoveToPoint => {
PathCommand::MoveTo(convert_point(&points[0]))
}
CGPathElementType::AddLineToPoint => {
PathCommand::LineTo(convert_point(&points[0]))
}
CGPathElementType::AddQuadCurveToPoint => {
PathCommand::CurveTo(convert_point(&points[0]), convert_point(&points[1]))
}
CGPathElementType::AddCurveToPoint => {
// FIXME(pcwalton)
PathCommand::CurveTo(convert_point(&points[0]), convert_point(&points[2]))
}
CGPathElementType::CloseSubpath => PathCommand::ClosePath,
});
});
//println!("{:?}", commands);
return GlyphOutline {
commands: commands,
index: 0,
};
fn convert_point(core_graphics_point: &CGPoint) -> Point2D<f32> {
Point2D::new(core_graphics_point.x as f32, core_graphics_point.y as f32)
}
}
}

View File

@ -0,0 +1,228 @@
// pathfinder/font-renderer/src/freetype/mod.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use FontKey;
use app_units::Au;
use euclid::{Point2D, Size2D};
use freetype_sys::{FT_BBox, FT_Done_Face, FT_F26Dot6, FT_Face, FT_GLYPH_FORMAT_OUTLINE};
use freetype_sys::{FT_GlyphSlot, FT_Init_FreeType, FT_Int32, FT_LOAD_NO_HINTING, FT_Library};
use freetype_sys::{FT_Load_Glyph, FT_Long, FT_New_Memory_Face, FT_Outline_Get_CBox};
use freetype_sys::{FT_Set_Char_Size, FT_UInt};
use pathfinder_path_utils::PathCommand;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::marker::PhantomData;
use std::mem;
use std::ptr;
use std::sync::Arc;
use outline::OutlineStream;
mod outline;
#[cfg(test)]
mod tests;
// Default to no hinting.
//
// TODO(pcwalton): Make this configurable.
const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_NO_HINTING;
const DPI: u32 = 72;
pub struct FontContext {
library: FT_Library,
faces: BTreeMap<FontKey, Face>,
}
impl FontContext {
pub fn new() -> FontContext {
let mut library: FT_Library = ptr::null_mut();
unsafe {
let result = FT_Init_FreeType(&mut library);
assert!(result == 0, "Unable to initialize FreeType");
}
FontContext {
library: library,
faces: BTreeMap::new(),
}
}
pub fn add_font_from_memory(&mut self,
font_key: &FontKey
bytes: Arc<Vec<u8>>,
font_index: u32)
-> Result<(), ()> {
match self.faces.entry(*font_key) {
Entry::Occupied(_) => Ok(()),
Entry::Vacant(entry) => {
unsafe {
let mut face = Face {
face: ptr::null_mut(),
bytes: bytes,
};
let result = FT_New_Memory_Face(self.library,
face.bytes.as_ptr(),
face.bytes.len() as FT_Long,
font_index as FT_Long,
&mut face.face);
if result == 0 && !face.face.is_null() {
entry.insert(face);
Ok(())
} else {
Err(())
}
}
}
}
}
pub fn delete_font(&mut self, font_key: &FontKey) {
self.faces.remove(font_key);
}
pub fn glyph_dimensions(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Option<GlyphDimensions> {
self.load_glyph(font_instance, glyph_key).and_then(|glyph_slot| {
self.glyph_dimensions_from_slot(font_instance, glyph_key, glyph_slot)
})
}
pub fn glyph_outline<'a>(&'a mut self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Result<GlyphOutline<'a>, ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
unsafe {
GlyphOutline {
stream: OutlineStream::new(&(*glyph_slot).outline, 72.0),
phantom: PhantomData,
}
}
})
}
fn load_glyph(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Option<FT_GlyphSlot> {
let face = match self.faces.get(&font_instance.font_key) {
None => return None,
Some(face) => face,
};
unsafe {
let point_size = (font_instance.size.to_f64_px() / (DPI as f64)).to_ft_f26dot6();
FT_Set_Char_Size(face.face, point_size, 0, DPI, 0);
if FT_Load_Glyph(face.face, glyph_key.glyph_index as FT_UInt, GLYPH_LOAD_FLAGS) != 0 {
return None
}
let slot = (*face.face).glyph;
if (*slot).format != FT_GLYPH_FORMAT_OUTLINE {
return None
}
Some(slot)
}
}
fn glyph_dimensions_from_slot(&self,
font_instance: &FontInstanceKey,
glyph_key: &GlyphKey,
glyph_slot: FT_GlyphSlot)
-> Option<GlyphDimensions> {
unsafe {
let metrics = &(*glyph_slot).metrics;
// This matches what WebRender does.
if metrics.horiAdvance == 0 {
return None
}
let bounding_box = self.bounding_box_from_slot(font_instance, glyph_key, glyph_slot);
Some(GlyphDimensions {
origin: Point2D::new((bounding_box.xMin >> 6) as i32,
(bounding_box.yMax >> 6) as i32),
size: Size2D::new(((bounding_box.xMax - bounding_box.xMin) >> 6) as u32,
((bounding_box.yMax - bounding_box.yMin) >> 6) as u32),
advance: metrics.horiAdvance as f32 / 64.0,
})
}
}
// Returns the bounding box for a glyph, accounting for subpixel positioning as appropriate.
//
// TODO(pcwalton): Subpixel positioning.
fn bounding_box_from_slot(&self, _: &FontInstanceKey, _: &GlyphKey, glyph_slot: FT_GlyphSlot)
-> FT_BBox {
let mut bounding_box: FT_BBox;
unsafe {
bounding_box = mem::zeroed();
FT_Outline_Get_CBox(&(*glyph_slot).outline, &mut bounding_box);
};
// Outset the box to device pixel boundaries. This matches what WebRender does.
bounding_box.xMin &= !0x3f;
bounding_box.yMin &= !0x3f;
bounding_box.xMax = (bounding_box.xMax + 0x3f) & !0x3f;
bounding_box.yMax = (bounding_box.yMax + 0x3f) & !0x3f;
bounding_box
}
}
pub struct GlyphOutline<'a> {
stream: OutlineStream<'static>,
phantom: PhantomData<&'a ()>,
}
impl<'a> Iterator for GlyphOutline<'a> {
type Item = PathCommand;
fn next(&mut self) -> Option<PathCommand> {
self.stream.next()
}
}
struct Face {
face: FT_Face,
bytes: Arc<Vec<u8>>,
}
impl Drop for Face {
fn drop(&mut self) {
unsafe {
FT_Done_Face(self.face);
}
}
}
trait FromFtF26Dot6 {
fn from_ft_f26dot6(value: FT_F26Dot6) -> Self;
}
impl FromFtF26Dot6 for f32 {
fn from_ft_f26dot6(value: FT_F26Dot6) -> f32 {
(value as f32) / 64.0
}
}
trait ToFtF26Dot6 {
fn to_ft_f26dot6(&self) -> FT_F26Dot6;
}
impl ToFtF26Dot6 for f64 {
fn to_ft_f26dot6(&self) -> FT_F26Dot6 {
(*self * 64.0 + 0.5) as FT_F26Dot6
}
}
impl ToFtF26Dot6 for Au {
fn to_ft_f26dot6(&self) -> FT_F26Dot6 {
self.to_f64_px().to_ft_f26dot6()
}
}

View File

@ -1,8 +1,15 @@
// pathfinder/font-renderer/lib.rs
// pathfinder/font-renderer/src/lib.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
extern crate app_units;
extern crate euclid;
extern crate freetype_sys;
extern crate pathfinder_path_utils;
#[allow(unused_imports)]
@ -12,181 +19,29 @@ extern crate log;
#[cfg(test)]
extern crate env_logger;
#[cfg(all(target_os = "macos", not(feature = "freetype")))]
extern crate core_graphics as core_graphics_sys;
#[cfg(all(target_os = "macos", not(feature = "freetype")))]
extern crate core_text;
#[cfg(any(target_os = "linux", feature = "freetype"))]
extern crate freetype_sys;
use app_units::Au;
use euclid::{Point2D, Size2D};
use freetype_sys::{FT_BBox, FT_Done_Face, FT_F26Dot6, FT_Face, FT_GLYPH_FORMAT_OUTLINE};
use freetype_sys::{FT_GlyphSlot, FT_Init_FreeType, FT_Int32, FT_LOAD_NO_HINTING, FT_Library};
use freetype_sys::{FT_Load_Glyph, FT_Long, FT_New_Memory_Face, FT_Outline_Get_CBox};
use freetype_sys::{FT_Set_Char_Size, FT_UInt};
use pathfinder_path_utils::PathCommand;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::marker::PhantomData;
use std::mem;
use std::ptr;
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
use outline::OutlineStream;
#[cfg(all(target_os = "macos", not(feature = "freetype")))]
pub use core_graphics::FontContext;
#[cfg(any(target_os = "linux", feature = "freetype"))]
pub use freetype::FontContext;
mod outline;
#[cfg(all(target_os = "macos", not(feature = "freetype")))]
mod core_graphics;
#[cfg(test)]
mod tests;
// Default to no hinting.
//
// TODO(pcwalton): Make this configurable.
const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_NO_HINTING;
const DPI: u32 = 72;
pub struct FontContext {
library: FT_Library,
faces: BTreeMap<FontKey, Face>,
}
impl FontContext {
pub fn new() -> FontContext {
let mut library: FT_Library = ptr::null_mut();
unsafe {
let result = FT_Init_FreeType(&mut library);
assert!(result == 0, "Unable to initialize FreeType");
}
FontContext {
library: library,
faces: BTreeMap::new(),
}
}
pub fn add_font_from_memory(&mut self, font_key: &FontKey, bytes: Vec<u8>, font_index: u32)
-> Result<(), ()> {
match self.faces.entry(*font_key) {
Entry::Occupied(_) => Ok(()),
Entry::Vacant(entry) => {
unsafe {
let mut face = Face {
face: ptr::null_mut(),
bytes: bytes,
};
let result = FT_New_Memory_Face(self.library,
face.bytes.as_ptr(),
face.bytes.len() as FT_Long,
font_index as FT_Long,
&mut face.face);
if result == 0 && !face.face.is_null() {
entry.insert(face);
Ok(())
} else {
Err(())
}
}
}
}
}
pub fn delete_font(&mut self, font_key: &FontKey) {
self.faces.remove(font_key);
}
pub fn glyph_dimensions(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Option<GlyphDimensions> {
self.load_glyph(font_instance, glyph_key).and_then(|glyph_slot| {
self.glyph_dimensions_from_slot(font_instance, glyph_key, glyph_slot)
})
}
pub fn glyph_outline<'a>(&'a mut self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Result<GlyphOutline<'a>, ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
unsafe {
GlyphOutline {
stream: OutlineStream::new(&(*glyph_slot).outline, 72.0),
phantom: PhantomData,
}
}
})
}
fn load_glyph(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Option<FT_GlyphSlot> {
let face = match self.faces.get(&font_instance.font_key) {
None => return None,
Some(face) => face,
};
unsafe {
let point_size = (font_instance.size.to_f64_px() / (DPI as f64)).to_ft_f26dot6();
FT_Set_Char_Size(face.face, point_size, 0, DPI, 0);
if FT_Load_Glyph(face.face, glyph_key.glyph_index as FT_UInt, GLYPH_LOAD_FLAGS) != 0 {
return None
}
let slot = (*face.face).glyph;
if (*slot).format != FT_GLYPH_FORMAT_OUTLINE {
return None
}
Some(slot)
}
}
fn glyph_dimensions_from_slot(&self,
font_instance: &FontInstanceKey,
glyph_key: &GlyphKey,
glyph_slot: FT_GlyphSlot)
-> Option<GlyphDimensions> {
unsafe {
let metrics = &(*glyph_slot).metrics;
// This matches what WebRender does.
if metrics.horiAdvance == 0 {
return None
}
let bounding_box = self.bounding_box_from_slot(font_instance, glyph_key, glyph_slot);
Some(GlyphDimensions {
origin: Point2D::new((bounding_box.xMin >> 6) as i32,
(bounding_box.yMax >> 6) as i32),
size: Size2D::new(((bounding_box.xMax - bounding_box.xMin) >> 6) as u32,
((bounding_box.yMax - bounding_box.yMin) >> 6) as u32),
advance: metrics.horiAdvance as f32 / 64.0,
})
}
}
// Returns the bounding box for a glyph, accounting for subpixel positioning as appropriate.
//
// TODO(pcwalton): Subpixel positioning.
fn bounding_box_from_slot(&self, _: &FontInstanceKey, _: &GlyphKey, glyph_slot: FT_GlyphSlot)
-> FT_BBox {
let mut bounding_box: FT_BBox;
unsafe {
bounding_box = mem::zeroed();
FT_Outline_Get_CBox(&(*glyph_slot).outline, &mut bounding_box);
};
// Outset the box to device pixel boundaries. This matches what WebRender does.
bounding_box.xMin &= !0x3f;
bounding_box.yMin &= !0x3f;
bounding_box.xMax = (bounding_box.xMax + 0x3f) & !0x3f;
bounding_box.yMax = (bounding_box.yMax + 0x3f) & !0x3f;
bounding_box
}
}
pub struct GlyphOutline<'a> {
stream: OutlineStream<'static>,
phantom: PhantomData<&'a ()>,
}
impl<'a> Iterator for GlyphOutline<'a> {
type Item = PathCommand;
fn next(&mut self) -> Option<PathCommand> {
self.stream.next()
}
}
#[cfg(any(target_os = "linux", feature = "freetype"))]
mod freetype;
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct FontKey {
@ -202,7 +57,7 @@ impl FontKey {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct FontInstanceKey {
pub font_key: FontKey,
pub size: Au,
@ -218,7 +73,7 @@ impl FontInstanceKey {
}
}
// TODO(pcwalton): Subpixel offsets.
// FIXME(pcwalton): Subpixel offsets?
#[derive(Clone, Copy, PartialEq)]
pub struct GlyphKey {
pub glyph_index: u32,
@ -240,42 +95,3 @@ pub struct GlyphDimensions {
pub size: Size2D<u32>,
pub advance: f32,
}
struct Face {
face: FT_Face,
bytes: Vec<u8>,
}
impl Drop for Face {
fn drop(&mut self) {
unsafe {
FT_Done_Face(self.face);
}
}
}
trait FromFtF26Dot6 {
fn from_ft_f26dot6(value: FT_F26Dot6) -> Self;
}
impl FromFtF26Dot6 for f32 {
fn from_ft_f26dot6(value: FT_F26Dot6) -> f32 {
(value as f32) / 64.0
}
}
trait ToFtF26Dot6 {
fn to_ft_f26dot6(&self) -> FT_F26Dot6;
}
impl ToFtF26Dot6 for f64 {
fn to_ft_f26dot6(&self) -> FT_F26Dot6 {
(*self * 64.0 + 0.5) as FT_F26Dot6
}
}
impl ToFtF26Dot6 for Au {
fn to_ft_f26dot6(&self) -> FT_F26Dot6 {
self.to_f64_px().to_ft_f26dot6()
}
}

View File

@ -5,7 +5,6 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[lib]
name = "pathfinder_partitioner"
crate-type = ["dylib", "rlib"]
[dependencies]
bincode = "0.8"