Implement basic kerning via the 'kern' table

This commit is contained in:
Patrick Walton 2017-02-08 17:10:49 -08:00
parent 5ecfadc3be
commit 1e50bf78ba
6 changed files with 156 additions and 21 deletions

View File

@ -101,9 +101,9 @@ fn main() {
println!("cpu,{}", time_per_glyph);
let atlas_size = Size2D::new(ATLAS_SIZE, ATLAS_SIZE);
let coverage_buffer = CoverageBuffer::new(&rasterizer.device, &atlas_size).unwrap();
let coverage_buffer = CoverageBuffer::new(rasterizer.device(), &atlas_size).unwrap();
let image = rasterizer.device
let image = rasterizer.device()
.create_image(Format::R8, buffer::Protection::WriteOnly, &atlas_size)
.unwrap();

97
src/otf/kern.rs Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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 byteorder::{BigEndian, ReadBytesExt};
use otf::{Error, FontTable};
use std::mem;
use util::Jump;
bitflags! {
flags Coverage: u16 {
const HORIZONTAL = 1 << 0,
const MINIMUM = 1 << 1,
const CROSS_STREAM = 1 << 2,
const OVERRIDE = 1 << 3,
}
}
#[derive(Clone, Copy)]
pub struct KernTable<'a> {
table: FontTable<'a>,
horizontal_table: &'a [u8],
}
impl<'a> KernTable<'a> {
pub fn new(table: FontTable) -> Result<KernTable, Error> {
let mut kern_reader = table.bytes;
let version = try!(kern_reader.read_u16::<BigEndian>().map_err(Error::eof));
if version != 0 {
return Err(Error::UnknownFormat)
}
let n_tables = try!(kern_reader.read_u16::<BigEndian>().map_err(Error::eof));
let mut horizontal_table = None;
for table_index in 0..n_tables {
let mut table_reader = kern_reader;
let _version = try!(table_reader.read_u16::<BigEndian>().map_err(Error::eof));
let length = try!(table_reader.read_u16::<BigEndian>().map_err(Error::eof));
let coverage = try!(table_reader.read_u16::<BigEndian>().map_err(Error::eof));
let coverage_flags = Coverage::from_bits_truncate(coverage);
if coverage_flags.contains(HORIZONTAL) && !coverage_flags.contains(MINIMUM) &&
!coverage_flags.contains(CROSS_STREAM) && (coverage >> 8) == 0 {
let length = length as usize - mem::size_of::<u16>() * 3;
horizontal_table = Some(&table_reader[0..length]);
break
}
try!(kern_reader.jump(length as usize).map_err(Error::eof));
}
match horizontal_table {
Some(horizontal_table) => {
Ok(KernTable {
table: table,
horizontal_table: horizontal_table,
})
}
None => Err(Error::UnknownFormat),
}
}
pub fn kerning_for_glyph_pair(&self, left_glyph_id: u16, right_glyph_id: u16)
-> Result<i16, Error> {
let mut table_reader = self.horizontal_table;
let n_pairs = try!(table_reader.read_u16::<BigEndian>().map_err(Error::eof));
try!(table_reader.jump(mem::size_of::<[u16; 3]>()).map_err(Error::eof));
let (mut low, mut high) = (0, n_pairs as u32);
while low < high {
let mut reader = table_reader;
let mid = (low + high) / 2;
try!(reader.jump(mid as usize * mem::size_of::<[u16; 3]>()).map_err(Error::eof));
let left = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let right = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let value = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
if left_glyph_id < left || (left_glyph_id == left && right_glyph_id < right) {
high = mid
} else if left_glyph_id > left || (left_glyph_id == left && right_glyph_id > right) {
low = mid + 1
} else {
return Ok(value)
}
}
Ok(0)
}
}

View File

@ -17,6 +17,7 @@ use otf::glyf::{GlyfTable, Point};
use otf::head::HeadTable;
use otf::hhea::HheaTable;
use otf::hmtx::{HmtxTable, HorizontalMetrics};
use otf::kern::KernTable;
use otf::loca::LocaTable;
use outline::GlyphBounds;
use std::mem;
@ -28,6 +29,7 @@ mod glyf;
mod head;
mod hhea;
mod hmtx;
mod kern;
mod loca;
const CMAP: u32 = ((b'c' as u32) << 24) |
@ -50,6 +52,10 @@ const HMTX: u32 = ((b'h' as u32) << 24) |
((b'm' as u32) << 16) |
((b't' as u32) << 8) |
(b'x' as u32);
const KERN: u32 = ((b'k' as u32) << 24) |
((b'e' as u32) << 16) |
((b'r' as u32) << 8) |
(b'n' as u32);
const LOCA: u32 = ((b'l' as u32) << 24) |
((b'o' as u32) << 16) |
((b'c' as u32) << 8) |
@ -87,6 +93,7 @@ pub struct Font<'a> {
glyf: Option<GlyfTable<'a>>,
loca: Option<LocaTable<'a>>,
kern: Option<KernTable<'a>>,
}
#[doc(hidden)]
@ -167,6 +174,7 @@ impl<'a> Font<'a> {
let (mut cmap_table, mut head_table) = (None, None);
let (mut hhea_table, mut hmtx_table) = (None, None);
let (mut glyf_table, mut loca_table) = (None, None);
let mut kern_table = None;
for _ in 0..num_tables {
let table_id = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
@ -184,6 +192,7 @@ impl<'a> Font<'a> {
HMTX => &mut hmtx_table,
GLYF => &mut glyf_table,
LOCA => &mut loca_table,
KERN => &mut kern_table,
_ => continue,
};
@ -212,6 +221,7 @@ impl<'a> Font<'a> {
glyf: glyf_table.map(GlyfTable::new),
loca: loca_table,
kern: kern_table.and_then(|table| KernTable::new(table).ok()),
})
}
@ -360,6 +370,19 @@ impl<'a> Font<'a> {
pub fn metrics_for_glyph(&self, glyph_id: u16) -> Result<HorizontalMetrics, Error> {
self.hmtx.metrics_for_glyph(&self.hhea, glyph_id)
}
/// Returns the kerning between the given two glyph IDs in font units.
///
/// Positive values move glyphs farther apart; negative values move glyphs closer together.
///
/// Zero is returned if no kerning is available in the font.
#[inline]
pub fn kerning_for_glyph_pair(&self, left_glyph_id: u16, right_glyph_id: u16) -> i16 {
match self.kern {
None => 0,
Some(kern) => kern.kerning_for_glyph_pair(left_glyph_id, right_glyph_id).unwrap_or(0),
}
}
}
/// Errors that can occur when parsing OpenType fonts.

View File

@ -24,20 +24,34 @@ use otf::Font;
/// For proper operation, the given `glyph_mapping` must include all the glyphs necessary to render
/// the string.
pub fn shape_text(font: &Font, glyph_mapping: &GlyphMapping, string: &str) -> Vec<GlyphPos> {
string.chars().map(|ch| {
let glyph_id = glyph_mapping.glyph_for(ch as u32).unwrap_or(0);
let metrics = font.metrics_for_glyph(glyph_id);
let mut chars = string.chars().peekable();
let mut next_glyph_id = None;
let mut result = vec![];
let advance = match metrics {
Err(_) => 0,
Ok(metrics) => metrics.advance_width,
while let Some(ch) = chars.next() {
let glyph_id = match next_glyph_id.take() {
None => glyph_mapping.glyph_for(ch as u32).unwrap_or(0),
Some(next_glyph_id) => next_glyph_id,
};
GlyphPos {
let mut advance = match font.metrics_for_glyph(glyph_id) {
Err(_) => 0,
Ok(metrics) => metrics.advance_width as i16,
};
if let Some(&next_char) = chars.peek() {
let next_glyph = glyph_mapping.glyph_for(next_char as u32).unwrap_or(0);
next_glyph_id = Some(next_glyph);
advance += font.kerning_for_glyph_pair(glyph_id, next_glyph)
}
result.push(GlyphPos {
glyph_id: glyph_id,
advance: advance,
}
}).collect()
})
}
result
}
/// The position of a glyph after shaping.
@ -46,6 +60,6 @@ pub struct GlyphPos {
/// The glyph ID to emit.
pub glyph_id: u16,
/// The amount to move the cursor forward *after* emitting this glyph.
pub advance: u16,
pub advance: i16,
}

View File

@ -15,13 +15,13 @@ fn bench_add_glyphs(bencher: &mut Bencher) {
unsafe {
let font = Font::new(file.as_slice()).unwrap();
let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)];
let glyph_ranges = font.glyph_ranges_for_codepoint_ranges(&codepoint_ranges)
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges)
.expect("Couldn't find glyph ranges");
bencher.iter(|| {
let mut outline_builder = OutlineBuilder::new();
for glyph_id in glyph_ranges.iter() {
outline_builder.add_glyph(&font, glyph_id).unwrap()
for (_, glyph_id) in glyph_mapping.iter() {
outline_builder.add_glyph(&font, glyph_id).unwrap();
}
});
}

View File

@ -5,7 +5,8 @@ use rect_packer::RectPacker;
use euclid::{Rect, Size2D};
use std::cmp;
fn pack_objects(available_width: u32, objects: Vec<(u32, u32)>) -> (RectPacker, Vec<Rect<u32>>) {
fn pack_objects(available_width: u32, objects: Vec<(u32, u32)>)
-> (RectPacker, Vec<Rect<u32>>, u32) {
let objects: Vec<_> = objects.iter()
.map(|&(width, height)| Size2D::new(width, height))
.collect();
@ -18,12 +19,12 @@ fn pack_objects(available_width: u32, objects: Vec<(u32, u32)>) -> (RectPacker,
let rects = objects.iter()
.map(|object| Rect::new(rect_packer.pack(object).unwrap(), *object))
.collect();
(rect_packer, rects)
(rect_packer, rects, available_width)
}
quickcheck! {
fn objects_dont_overlap(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
let (_, rects) = pack_objects(available_width, objects);
let (_, rects, _) = pack_objects(available_width, objects);
for (i, a) in rects.iter().enumerate() {
for b in &rects[(i + 1)..] {
assert!(!a.intersects(b))
@ -33,12 +34,12 @@ quickcheck! {
}
fn objects_dont_exceed_available_width(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
let (rect_packer, rects) = pack_objects(available_width, objects);
rects.iter().all(|rect| rect.max_x() <= rect_packer.available_width())
let (_, rects, available_width) = pack_objects(available_width, objects);
rects.iter().all(|rect| rect.max_x() <= available_width)
}
fn objects_dont_cross_shelves(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
let (rect_packer, rects) = pack_objects(available_width, objects);
let (rect_packer, rects, _) = pack_objects(available_width, objects);
rects.iter().all(|rect| {
let shelf_height = rect_packer.shelf_height();
rect.is_empty() || rect.origin.y / shelf_height == (rect.max_y() - 1) / shelf_height