servo/components/gfx/platform/macos/font.rs
Patrick Walton 18fbf8cf30 gfx: Clamp the font size we supply to Core Text to 0.01pt.
Core Text treats a font size of 0.0 as 12.0, which is obviously not what
we want.

Improves Twitter.
Improves Reddit /r/rust.

Closes #10492.
2016-04-11 13:52:41 -07:00

214 lines
7.6 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/. */
/// Implementation of Quartz (CoreGraphics) fonts.
extern crate core_foundation;
extern crate core_graphics;
extern crate core_text;
use app_units::Au;
use core_foundation::base::CFIndex;
use core_foundation::data::CFData;
use core_foundation::string::UniChar;
use core_graphics::font::CGGlyph;
use core_graphics::geometry::CGRect;
use core_text::font::CTFont;
use core_text::font_descriptor::{SymbolicTraitAccessors, TraitAccessors};
use core_text::font_descriptor::{kCTFontDefaultOrientation};
use font::FontTableTag;
use font::FractionalPixel;
use font::{FontHandleMethods, FontMetrics, FontTableMethods};
use platform::font_template::FontTemplateData;
use platform::macos::font_context::FontContextHandle;
use std::ptr;
use std::sync::Arc;
use style::computed_values::{font_stretch, font_weight};
use text::glyph::GlyphId;
pub struct FontTable {
data: CFData,
}
// assumes 72 points per inch, and 96 px per inch
fn px_to_pt(px: f64) -> f64 {
px / 96. * 72.
}
// assumes 72 points per inch, and 96 px per inch
fn pt_to_px(pt: f64) -> f64 {
pt / 72. * 96.
}
fn au_from_pt(pt: f64) -> Au {
Au::from_f64_px(pt_to_px(pt))
}
impl FontTable {
pub fn wrap(data: CFData) -> FontTable {
FontTable { data: data }
}
}
impl FontTableMethods for FontTable {
fn with_buffer<F>(&self, blk: F) where F: FnOnce(*const u8, usize) {
blk(self.data.bytes().as_ptr(), self.data.len() as usize);
}
}
#[derive(Debug)]
pub struct FontHandle {
pub font_data: Arc<FontTemplateData>,
pub ctfont: CTFont,
}
impl FontHandleMethods for FontHandle {
fn new_from_template(_fctx: &FontContextHandle,
template: Arc<FontTemplateData>,
pt_size: Option<Au>)
-> Result<FontHandle, ()> {
let size = match pt_size {
Some(s) => s.to_f64_px(),
None => 0.0
};
match template.ctfont(size) {
Some(ref ctfont) => {
Ok(FontHandle {
font_data: template.clone(),
ctfont: ctfont.clone_with_font_size(size),
})
}
None => {
Err(())
}
}
}
fn template(&self) -> Arc<FontTemplateData> {
self.font_data.clone()
}
fn family_name(&self) -> String {
self.ctfont.family_name()
}
fn face_name(&self) -> String {
self.ctfont.face_name()
}
fn is_italic(&self) -> bool {
self.ctfont.symbolic_traits().is_italic()
}
fn boldness(&self) -> font_weight::T {
let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0]
let normalized = (normalized + 1.0) / 2.0 * 9.0; // [0.0, 9.0]
match normalized {
v if v < 1.0 => font_weight::T::Weight100,
v if v < 2.0 => font_weight::T::Weight200,
v if v < 3.0 => font_weight::T::Weight300,
v if v < 4.0 => font_weight::T::Weight400,
v if v < 5.0 => font_weight::T::Weight500,
v if v < 6.0 => font_weight::T::Weight600,
v if v < 7.0 => font_weight::T::Weight700,
v if v < 8.0 => font_weight::T::Weight800,
_ => font_weight::T::Weight900,
}
}
fn stretchiness(&self) -> font_stretch::T {
let normalized = self.ctfont.all_traits().normalized_width(); // [-1.0, 1.0]
let normalized = (normalized + 1.0) / 2.0 * 9.0; // [0.0, 9.0]
match normalized {
v if v < 1.0 => font_stretch::T::ultra_condensed,
v if v < 2.0 => font_stretch::T::extra_condensed,
v if v < 3.0 => font_stretch::T::condensed,
v if v < 4.0 => font_stretch::T::semi_condensed,
v if v < 5.0 => font_stretch::T::normal,
v if v < 6.0 => font_stretch::T::semi_expanded,
v if v < 7.0 => font_stretch::T::expanded,
v if v < 8.0 => font_stretch::T::extra_expanded,
_ => font_stretch::T::ultra_expanded,
}
}
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
let characters: [UniChar; 1] = [codepoint as UniChar];
let mut glyphs: [CGGlyph; 1] = [0 as CGGlyph];
let count: CFIndex = 1;
let result = self.ctfont.get_glyphs_for_characters(&characters[0],
&mut glyphs[0],
count);
if !result {
// No glyph for this character
return None;
}
assert!(glyphs[0] != 0); // FIXME: error handling
return Some(glyphs[0] as GlyphId);
}
fn glyph_h_kerning(&self, _first_glyph: GlyphId, _second_glyph: GlyphId)
-> FractionalPixel {
// TODO: Implement on mac
0.0
}
fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
let glyphs = [glyph as CGGlyph];
let advance = self.ctfont.get_advances_for_glyphs(kCTFontDefaultOrientation,
&glyphs[0],
ptr::null_mut(),
1);
Some(advance as FractionalPixel)
}
fn metrics(&self) -> FontMetrics {
let bounding_rect: CGRect = self.ctfont.bounding_box();
let ascent = self.ctfont.ascent() as f64;
let descent = self.ctfont.descent() as f64;
let em_size = Au::from_f64_px(self.ctfont.pt_size() as f64);
let leading = self.ctfont.leading() as f64;
let scale = px_to_pt(self.ctfont.pt_size() as f64) / (ascent + descent);
let line_gap = (ascent + descent + leading + 0.5).floor();
let max_advance_width = au_from_pt(bounding_rect.size.width as f64);
let average_advance = self.glyph_index('0')
.and_then(|idx| self.glyph_h_advance(idx))
.map(Au::from_f64_px)
.unwrap_or(max_advance_width);
let metrics = FontMetrics {
underline_size: au_from_pt(self.ctfont.underline_thickness() as f64),
// TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table
// directly.
//
// see also: https://bugs.webkit.org/show_bug.cgi?id=16768
// see also: https://bugreports.qt-project.org/browse/QTBUG-13364
underline_offset: au_from_pt(self.ctfont.underline_position() as f64),
strikeout_size: Au(0), // FIXME(Issue #942)
strikeout_offset: Au(0), // FIXME(Issue #942)
leading: au_from_pt(leading),
x_height: au_from_pt(self.ctfont.x_height() as f64),
em_size: em_size,
ascent: au_from_pt(ascent * scale),
descent: au_from_pt(descent * scale),
max_advance: max_advance_width,
average_advance: average_advance,
line_gap: Au::from_f64_px(line_gap),
};
debug!("Font metrics (@{} pt): {:?}", self.ctfont.pt_size() as f64, metrics);
metrics
}
fn table_for_tag(&self, tag: FontTableTag) -> Option<Box<FontTable>> {
let result: Option<CFData> = self.ctfont.get_font_table(tag);
result.and_then(|data| {
Some(box FontTable::wrap(data))
})
}
}