Make macOS font rendering not depend on FreeType
This commit is contained in:
parent
a523d71e3c
commit
9c470e77c1
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
|||
|
||||
[lib]
|
||||
name = "pathfinder_partitioner"
|
||||
crate-type = ["dylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
bincode = "0.8"
|
||||
|
|
Loading…
Reference in New Issue