Implement basic kerning via the 'kern' table
This commit is contained in:
parent
5ecfadc3be
commit
1e50bf78ba
|
@ -101,9 +101,9 @@ fn main() {
|
||||||
println!("cpu,{}", time_per_glyph);
|
println!("cpu,{}", time_per_glyph);
|
||||||
|
|
||||||
let atlas_size = Size2D::new(ATLAS_SIZE, ATLAS_SIZE);
|
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)
|
.create_image(Format::R8, buffer::Protection::WriteOnly, &atlas_size)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ use otf::glyf::{GlyfTable, Point};
|
||||||
use otf::head::HeadTable;
|
use otf::head::HeadTable;
|
||||||
use otf::hhea::HheaTable;
|
use otf::hhea::HheaTable;
|
||||||
use otf::hmtx::{HmtxTable, HorizontalMetrics};
|
use otf::hmtx::{HmtxTable, HorizontalMetrics};
|
||||||
|
use otf::kern::KernTable;
|
||||||
use otf::loca::LocaTable;
|
use otf::loca::LocaTable;
|
||||||
use outline::GlyphBounds;
|
use outline::GlyphBounds;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -28,6 +29,7 @@ mod glyf;
|
||||||
mod head;
|
mod head;
|
||||||
mod hhea;
|
mod hhea;
|
||||||
mod hmtx;
|
mod hmtx;
|
||||||
|
mod kern;
|
||||||
mod loca;
|
mod loca;
|
||||||
|
|
||||||
const CMAP: u32 = ((b'c' as u32) << 24) |
|
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'm' as u32) << 16) |
|
||||||
((b't' as u32) << 8) |
|
((b't' as u32) << 8) |
|
||||||
(b'x' as u32);
|
(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) |
|
const LOCA: u32 = ((b'l' as u32) << 24) |
|
||||||
((b'o' as u32) << 16) |
|
((b'o' as u32) << 16) |
|
||||||
((b'c' as u32) << 8) |
|
((b'c' as u32) << 8) |
|
||||||
|
@ -87,6 +93,7 @@ pub struct Font<'a> {
|
||||||
|
|
||||||
glyf: Option<GlyfTable<'a>>,
|
glyf: Option<GlyfTable<'a>>,
|
||||||
loca: Option<LocaTable<'a>>,
|
loca: Option<LocaTable<'a>>,
|
||||||
|
kern: Option<KernTable<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -167,6 +174,7 @@ impl<'a> Font<'a> {
|
||||||
let (mut cmap_table, mut head_table) = (None, None);
|
let (mut cmap_table, mut head_table) = (None, None);
|
||||||
let (mut hhea_table, mut hmtx_table) = (None, None);
|
let (mut hhea_table, mut hmtx_table) = (None, None);
|
||||||
let (mut glyf_table, mut loca_table) = (None, None);
|
let (mut glyf_table, mut loca_table) = (None, None);
|
||||||
|
let mut kern_table = None;
|
||||||
|
|
||||||
for _ in 0..num_tables {
|
for _ in 0..num_tables {
|
||||||
let table_id = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
|
let table_id = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
|
||||||
|
@ -184,6 +192,7 @@ impl<'a> Font<'a> {
|
||||||
HMTX => &mut hmtx_table,
|
HMTX => &mut hmtx_table,
|
||||||
GLYF => &mut glyf_table,
|
GLYF => &mut glyf_table,
|
||||||
LOCA => &mut loca_table,
|
LOCA => &mut loca_table,
|
||||||
|
KERN => &mut kern_table,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -212,6 +221,7 @@ impl<'a> Font<'a> {
|
||||||
|
|
||||||
glyf: glyf_table.map(GlyfTable::new),
|
glyf: glyf_table.map(GlyfTable::new),
|
||||||
loca: loca_table,
|
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> {
|
pub fn metrics_for_glyph(&self, glyph_id: u16) -> Result<HorizontalMetrics, Error> {
|
||||||
self.hmtx.metrics_for_glyph(&self.hhea, glyph_id)
|
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.
|
/// Errors that can occur when parsing OpenType fonts.
|
||||||
|
|
|
@ -24,20 +24,34 @@ use otf::Font;
|
||||||
/// For proper operation, the given `glyph_mapping` must include all the glyphs necessary to render
|
/// For proper operation, the given `glyph_mapping` must include all the glyphs necessary to render
|
||||||
/// the string.
|
/// the string.
|
||||||
pub fn shape_text(font: &Font, glyph_mapping: &GlyphMapping, string: &str) -> Vec<GlyphPos> {
|
pub fn shape_text(font: &Font, glyph_mapping: &GlyphMapping, string: &str) -> Vec<GlyphPos> {
|
||||||
string.chars().map(|ch| {
|
let mut chars = string.chars().peekable();
|
||||||
let glyph_id = glyph_mapping.glyph_for(ch as u32).unwrap_or(0);
|
let mut next_glyph_id = None;
|
||||||
let metrics = font.metrics_for_glyph(glyph_id);
|
let mut result = vec![];
|
||||||
|
|
||||||
let advance = match metrics {
|
while let Some(ch) = chars.next() {
|
||||||
Err(_) => 0,
|
let glyph_id = match next_glyph_id.take() {
|
||||||
Ok(metrics) => metrics.advance_width,
|
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,
|
glyph_id: glyph_id,
|
||||||
advance: advance,
|
advance: advance,
|
||||||
}
|
})
|
||||||
}).collect()
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The position of a glyph after shaping.
|
/// The position of a glyph after shaping.
|
||||||
|
@ -46,6 +60,6 @@ pub struct GlyphPos {
|
||||||
/// The glyph ID to emit.
|
/// The glyph ID to emit.
|
||||||
pub glyph_id: u16,
|
pub glyph_id: u16,
|
||||||
/// The amount to move the cursor forward *after* emitting this glyph.
|
/// The amount to move the cursor forward *after* emitting this glyph.
|
||||||
pub advance: u16,
|
pub advance: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,13 @@ fn bench_add_glyphs(bencher: &mut Bencher) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let font = Font::new(file.as_slice()).unwrap();
|
let font = Font::new(file.as_slice()).unwrap();
|
||||||
let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)];
|
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");
|
.expect("Couldn't find glyph ranges");
|
||||||
|
|
||||||
bencher.iter(|| {
|
bencher.iter(|| {
|
||||||
let mut outline_builder = OutlineBuilder::new();
|
let mut outline_builder = OutlineBuilder::new();
|
||||||
for glyph_id in glyph_ranges.iter() {
|
for (_, glyph_id) in glyph_mapping.iter() {
|
||||||
outline_builder.add_glyph(&font, glyph_id).unwrap()
|
outline_builder.add_glyph(&font, glyph_id).unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ use rect_packer::RectPacker;
|
||||||
use euclid::{Rect, Size2D};
|
use euclid::{Rect, Size2D};
|
||||||
use std::cmp;
|
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()
|
let objects: Vec<_> = objects.iter()
|
||||||
.map(|&(width, height)| Size2D::new(width, height))
|
.map(|&(width, height)| Size2D::new(width, height))
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -18,12 +19,12 @@ fn pack_objects(available_width: u32, objects: Vec<(u32, u32)>) -> (RectPacker,
|
||||||
let rects = objects.iter()
|
let rects = objects.iter()
|
||||||
.map(|object| Rect::new(rect_packer.pack(object).unwrap(), *object))
|
.map(|object| Rect::new(rect_packer.pack(object).unwrap(), *object))
|
||||||
.collect();
|
.collect();
|
||||||
(rect_packer, rects)
|
(rect_packer, rects, available_width)
|
||||||
}
|
}
|
||||||
|
|
||||||
quickcheck! {
|
quickcheck! {
|
||||||
fn objects_dont_overlap(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
|
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 (i, a) in rects.iter().enumerate() {
|
||||||
for b in &rects[(i + 1)..] {
|
for b in &rects[(i + 1)..] {
|
||||||
assert!(!a.intersects(b))
|
assert!(!a.intersects(b))
|
||||||
|
@ -33,12 +34,12 @@ quickcheck! {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn objects_dont_exceed_available_width(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
|
fn objects_dont_exceed_available_width(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
|
||||||
let (rect_packer, rects) = pack_objects(available_width, objects);
|
let (_, rects, available_width) = pack_objects(available_width, objects);
|
||||||
rects.iter().all(|rect| rect.max_x() <= rect_packer.available_width())
|
rects.iter().all(|rect| rect.max_x() <= available_width)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn objects_dont_cross_shelves(available_width: u32, objects: Vec<(u32, u32)>) -> bool {
|
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| {
|
rects.iter().all(|rect| {
|
||||||
let shelf_height = rect_packer.shelf_height();
|
let shelf_height = rect_packer.shelf_height();
|
||||||
rect.is_empty() || rect.origin.y / shelf_height == (rect.max_y() - 1) / shelf_height
|
rect.is_empty() || rect.origin.y / shelf_height == (rect.max_y() - 1) / shelf_height
|
||||||
|
|
Loading…
Reference in New Issue