Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gfx: Implement `letter-spacing` per CSS 2.1 § 16.4. #4325

Merged
merged 1 commit into from Dec 12, 2014
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

gfx: Implement `letter-spacing` per CSS 2.1 § 16.4.

The ligature disabling code has been manually verified, but I was unable
to reftest it. (The only way I could think of would be to create an
Ahem-like font with a ligature table, but that would be an awful lot of
work.)

Near as I can tell, the method used to apply the spacing (manually
inserting extra advance post-shaping) matches Gecko.
  • Loading branch information
pcwalton committed Dec 12, 2014
commit 07bc97e3e2d217cfd36021e63698b76579c99af1
@@ -13,9 +13,10 @@ use style::computed_values::{font_variant, font_weight};
use style::style_structs::Font as FontStyle;
use sync::Arc;

use servo_util::geometry::Au;
use collections::hash::Hash;
use platform::font_context::FontContextHandle;
use platform::font::{FontHandle, FontTable};
use servo_util::geometry::Au;
use text::glyph::{GlyphStore, GlyphId};
use text::shaping::ShaperMethods;
use text::{Shaper, TextRun};
@@ -95,37 +96,85 @@ pub struct Font {
pub requested_pt_size: Au,
pub actual_pt_size: Au,
pub shaper: Option<Shaper>,
pub shape_cache: HashCache<String, Arc<GlyphStore>>,
pub glyph_advance_cache: HashCache<u32, FractionalPixel>,
pub shape_cache: HashCache<ShapeCacheEntry,Arc<GlyphStore>>,
pub glyph_advance_cache: HashCache<u32,FractionalPixel>,
}

bitflags! {
flags ShapingFlags: u8 {
#[doc="Set if the text is entirely whitespace."]
const IS_WHITESPACE_SHAPING_FLAG = 0x01,
#[doc="Set if we are to ignore ligatures."]
const IGNORE_LIGATURES_SHAPING_FLAG = 0x02
}
}

/// Various options that control text shaping.
#[deriving(Clone, Eq, PartialEq, Hash)]
pub struct ShapingOptions {
/// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property.
/// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null.
pub letter_spacing: Option<Au>,
/// Various flags.
pub flags: ShapingFlags,
}

/// An entry in the shape cache.
#[deriving(Clone, Eq, PartialEq, Hash)]
pub struct ShapeCacheEntry {
text: String,
options: ShapingOptions,
}

#[deriving(Clone, Eq, PartialEq, Hash)]
struct ShapeCacheEntryRef<'a> {
text: &'a str,
options: &'a ShapingOptions,
}

impl<'a> Equiv<ShapeCacheEntry> for ShapeCacheEntryRef<'a> {
fn equiv(&self, other: &ShapeCacheEntry) -> bool {
self.text == other.text.as_slice() && *self.options == other.options
}
}

impl Font {
pub fn shape_text(&mut self, text: &str, is_whitespace: bool) -> Arc<GlyphStore> {
self.make_shaper();
pub fn shape_text(&mut self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
self.make_shaper(options);

let shaper = &self.shaper;
match self.shape_cache.find_equiv(text) {
let lookup_key = ShapeCacheEntryRef {
text: text,
options: options,
};
match self.shape_cache.find_equiv(&lookup_key) {
None => {}
Some(glyphs) => return (*glyphs).clone(),
}

let mut glyphs = GlyphStore::new(text.char_len() as int, is_whitespace);
shaper.as_ref().unwrap().shape_text(text, &mut glyphs);
let mut glyphs = GlyphStore::new(text.char_len() as int,
options.flags.contains(IS_WHITESPACE_SHAPING_FLAG));
shaper.as_ref().unwrap().shape_text(text, options, &mut glyphs);

let glyphs = Arc::new(glyphs);
self.shape_cache.insert(text.to_string(), glyphs.clone());
self.shape_cache.insert(ShapeCacheEntry {
text: text.to_string(),
options: *options,
}, glyphs.clone());
glyphs
}

fn make_shaper<'a>(&'a mut self) -> &'a Shaper {
fn make_shaper<'a>(&'a mut self, options: &ShapingOptions) -> &'a Shaper {
// fast path: already created a shaper
match self.shaper {
Some(ref shaper) => {
let s: &'a Shaper = shaper;
return s;
Some(ref mut shaper) => {
shaper.set_options(options);
return shaper
},
None => {}
}

let shaper = Shaper::new(self);
let shaper = Shaper::new(self, options);
self.shaper = Some(shaper);
self.shaper.as_ref().unwrap()
}
@@ -149,7 +198,8 @@ impl Font {
self.handle.glyph_index(codepoint)
}

pub fn glyph_h_kerning(&mut self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
pub fn glyph_h_kerning(&mut self, first_glyph: GlyphId, second_glyph: GlyphId)
-> FractionalPixel {
self.handle.glyph_h_kerning(first_glyph, second_glyph)
}

@@ -175,11 +225,11 @@ impl FontGroup {
}
}

pub fn create_textrun(&self, text: String) -> TextRun {
pub fn create_textrun(&self, text: String, options: &ShapingOptions) -> TextRun {
assert!(self.fonts.len() > 0);

// TODO(Issue #177): Actually fall back through the FontGroup when a font is unsuitable.
TextRun::new(&mut *self.fonts.get(0).borrow_mut(), text.clone())
TextRun::new(&mut *self.fonts.get(0).borrow_mut(), text.clone(), options)
}
}

@@ -2,7 +2,7 @@
* 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/. */

#![feature(globs, macro_rules, phase, unsafe_destructor)]
#![feature(globs, macro_rules, phase, unsafe_destructor, default_type_params)]

#![deny(unused_imports)]
#![deny(unused_variables)]
@@ -4,7 +4,8 @@

extern crate harfbuzz;

use font::{Font, FontHandleMethods, FontTableMethods, FontTableTag};
use font::{Font, FontHandleMethods, FontTableMethods, FontTableTag, IGNORE_LIGATURES_SHAPING_FLAG};
use font::{ShapingOptions};
use platform::font::FontTable;
use text::glyph::{CharIndex, GlyphStore, GlyphId, GlyphData};
use text::shaping::ShaperMethods;
@@ -18,9 +19,11 @@ use harfbuzz::{hb_bool_t};
use harfbuzz::{hb_buffer_add_utf8};
use harfbuzz::{hb_buffer_destroy};
use harfbuzz::{hb_buffer_get_glyph_positions};
use harfbuzz::{hb_buffer_get_length};
use harfbuzz::{hb_buffer_set_direction};
use harfbuzz::{hb_face_destroy};
use harfbuzz::{hb_face_t, hb_font_t};
use harfbuzz::{hb_feature_t};
use harfbuzz::{hb_font_create};
use harfbuzz::{hb_font_destroy, hb_buffer_create};
use harfbuzz::{hb_font_funcs_create};
@@ -47,6 +50,9 @@ use std::ptr;
static NO_GLYPH: i32 = -1;
static CONTINUATION_BYTE: i32 = -2;

static LIGA: u32 = ((b'l' as u32) << 24) | ((b'i' as u32) << 16) | ((b'g' as u32) << 8) |
(b'a' as u32);

pub struct ShapedGlyphData {
count: int,
glyph_infos: *mut hb_glyph_info_t,
@@ -131,10 +137,16 @@ impl ShapedGlyphData {
}
}

struct FontAndShapingOptions {
font: *mut Font,
options: ShapingOptions,
}

pub struct Shaper {
hb_face: *mut hb_face_t,
hb_font: *mut hb_font_t,
hb_funcs: *mut hb_font_funcs_t,
font_and_shaping_options: Box<FontAndShapingOptions>,
}

#[unsafe_destructor]
@@ -154,13 +166,18 @@ impl Drop for Shaper {
}

impl Shaper {
pub fn new(font: &mut Font) -> Shaper {
pub fn new(font: &mut Font, options: &ShapingOptions) -> Shaper {
unsafe {
// Indirection for Rust Issue #6248, dynamic freeze scope artificially extended
let font_ptr = font as *mut Font;
let hb_face: *mut hb_face_t = hb_face_create_for_tables(get_font_table_func,
font_ptr as *mut c_void,
None);
let mut font_and_shaping_options = box FontAndShapingOptions {
font: font,
options: *options,
};
let hb_face: *mut hb_face_t =
hb_face_create_for_tables(get_font_table_func,
(&mut *font_and_shaping_options)
as *mut FontAndShapingOptions
as *mut c_void,
None);
let hb_font: *mut hb_font_t = hb_font_create(hb_face);

// Set points-per-em. if zero, performs no hinting in that direction.
@@ -178,16 +195,21 @@ impl Shaper {
hb_font_funcs_set_glyph_func(hb_funcs, glyph_func, ptr::null_mut(), None);
hb_font_funcs_set_glyph_h_advance_func(hb_funcs, glyph_h_advance_func, ptr::null_mut(), None);
hb_font_funcs_set_glyph_h_kerning_func(hb_funcs, glyph_h_kerning_func, ptr::null_mut(), ptr::null_mut());
hb_font_set_funcs(hb_font, hb_funcs, font_ptr as *mut c_void, None);
hb_font_set_funcs(hb_font, hb_funcs, font as *mut Font as *mut c_void, None);

Shaper {
hb_face: hb_face,
hb_font: hb_font,
hb_funcs: hb_funcs,
font_and_shaping_options: font_and_shaping_options,
}
}
}

pub fn set_options(&mut self, options: &ShapingOptions) {
self.font_and_shaping_options.options = *options
}

fn float_to_fixed(f: f64) -> i32 {
float_to_fixed(16, f)
}
@@ -200,7 +222,7 @@ impl Shaper {
impl ShaperMethods for Shaper {
/// Calculate the layout metrics associated with the given text when painted in a specific
/// font.
fn shape_text(&self, text: &str, glyphs: &mut GlyphStore) {
fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
unsafe {
let hb_buffer: *mut hb_buffer_t = hb_buffer_create();
hb_buffer_set_direction(hb_buffer, HB_DIRECTION_LTR);
@@ -211,15 +233,29 @@ impl ShaperMethods for Shaper {
0,
text.len() as c_int);

hb_shape(self.hb_font, hb_buffer, ptr::null_mut(), 0);
self.save_glyph_results(text, glyphs, hb_buffer);
let mut features = Vec::new();
if options.flags.contains(IGNORE_LIGATURES_SHAPING_FLAG) {
features.push(hb_feature_t {
_tag: LIGA,
_value: 0,
_start: 0,
_end: hb_buffer_get_length(hb_buffer),
})
}

hb_shape(self.hb_font, hb_buffer, features.as_mut_ptr(), features.len() as u32);
self.save_glyph_results(text, options, glyphs, hb_buffer);
hb_buffer_destroy(hb_buffer);
}
}
}

impl Shaper {
fn save_glyph_results(&self, text: &str, glyphs: &mut GlyphStore, buffer: *mut hb_buffer_t) {
fn save_glyph_results(&self,
text: &str,
options: &ShapingOptions,
glyphs: &mut GlyphStore,
buffer: *mut hb_buffer_t) {
let glyph_data = ShapedGlyphData::new(buffer);
let glyph_count = glyph_data.len();
let byte_max = text.len() as int;
@@ -401,8 +437,9 @@ impl Shaper {
// (i.e., pretend there are no combining character sequences).
// 1-to-1 mapping of character to glyph also treated as ligature start.
let shape = glyph_data.get_entry_for_glyph(glyph_span.begin(), &mut y_pos);
let advance = self.advance_for_shaped_glyph(shape.advance, options);
let data = GlyphData::new(shape.codepoint,
shape.advance,
advance,
shape.offset,
false,
true,
@@ -450,6 +487,13 @@ impl Shaper {
// lookup table for finding detailed glyphs by associated char index.
glyphs.finalize_changes();
}

fn advance_for_shaped_glyph(&self, advance: Au, options: &ShapingOptions) -> Au {
match options.letter_spacing {
None => advance,
Some(spacing) => advance + spacing,
}
}
}

/// Callbacks from Harfbuzz when font map and glyph advance lookup needed.
@@ -504,13 +548,19 @@ extern fn glyph_h_kerning_func(_: *mut hb_font_t,
}

// Callback to get a font table out of a font.
extern fn get_font_table_func(_: *mut hb_face_t, tag: hb_tag_t, user_data: *mut c_void) -> *mut hb_blob_t {
extern fn get_font_table_func(_: *mut hb_face_t,
tag: hb_tag_t,
user_data: *mut c_void)
-> *mut hb_blob_t {
unsafe {
let font: *const Font = user_data as *const Font;
assert!(font.is_not_null());
// NB: These asserts have security implications.
let font_and_shaping_options: *const FontAndShapingOptions =
user_data as *const FontAndShapingOptions;
assert!(font_and_shaping_options.is_not_null());
assert!((*font_and_shaping_options).font.is_not_null());

// TODO(Issue #197): reuse font table data, which will change the unsound trickery here.
match (*font).get_table_for_tag(tag as FontTableTag) {
match (*(*font_and_shaping_options).font).get_table_for_tag(tag as FontTableTag) {
None => ptr::null_mut(),
Some(ref font_table) => {
let skinny_font_table_ptr: *const FontTable = font_table; // private context
@@ -7,13 +7,14 @@
//!
//! Currently, only harfbuzz bindings are implemented.

use font::ShapingOptions;
use text::glyph::GlyphStore;

pub use text::shaping::harfbuzz::Shaper;

pub mod harfbuzz;

pub trait ShaperMethods {
fn shape_text(&self, text: &str, glyphs: &mut GlyphStore);
fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore);
}

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.