Read the OS/2 metrics to determine the right line spacing

This commit is contained in:
Patrick Walton 2017-02-09 20:02:32 -08:00
parent 55e1502eb2
commit 6261c87b89
5 changed files with 138 additions and 7 deletions

View File

@ -117,7 +117,8 @@ fn main() {
let space_advance = font.metrics_for_glyph(glyph_mapping.glyph_for(' ' as u32).unwrap())
.unwrap()
.advance_width as u32;
let line_spacing = font.units_per_em() as u32;
let line_spacing = (font.ascender() as i32 - font.descender() as i32 + font.line_gap() as i32)
as u32;
let (mut current_x, mut current_y) = (0, line_spacing);
for word in text.split_whitespace() {

View File

@ -10,8 +10,39 @@
//! A high-performance GPU rasterizer for OpenType fonts.
//!
//! Pathfinder rasterizes glyphs from a `.ttf`, `.ttc`, or `.otf` file loaded in memory to an atlas
//! texture.
//! ## Introduction
//!
//! Pathfinder is a fast, practical GPU-based rasterizer for OpenType fonts using OpenGL 4.3. It
//! features:
//!
//! * Very low setup time. Glyph outlines can go from the `.otf` file to the GPU in a form ready
//! for rasterization in less than a microsecond. There is no expensive tessellation or
//! preprocessing step.
//!
//! * High quality antialiasing. Unlike techniques that rely on multisample antialiasing,
//! Pathfinder computes exact fractional trapezoidal area coverage on a per-pixel basis.
//!
//! * Fast rendering, even at small pixel sizes. On typical systems, Pathfinder should easily
//! exceed the performance of the best CPU rasterizers.
//!
//! * Low memory consumption. The only memory overhead over the glyph and outline storage itself is
//! that of a coverage buffer which typically consumes somewhere between 4MB-16MB and can be
//! discarded under memory pressure. Outlines are stored on-GPU in a compressed format and usually
//! take up only a few dozen kilobytes.
//!
//! * Portability to most GPUs manufactured in the last few years, including integrated GPUs.
//!
//! ## Usage
//!
//! See `examples/generate-atlas.rs` for a simple example.
//!
//! Typically, the steps to use Pathfidner are:
//!
//! 1. Create a `Rasterizer` object. This holds the OpenGL state.
//!
//! 2. Open the font from disk (or elsewhere), and call `Font::new()` (or
//! `Font::from_collection_index` in the case of a `.ttc` or `.dfont` collection) to load it.
//!
#![cfg_attr(test, feature(test))]

View File

@ -15,6 +15,7 @@ use util::Jump;
#[derive(Clone, Debug)]
pub struct HheaTable {
pub line_gap: i16,
pub number_of_h_metrics: u16,
}
@ -29,11 +30,17 @@ impl HheaTable {
return Err(Error::UnsupportedHheaVersion)
}
// Read the height-related metrics.
let _ascender = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
let _descender = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
let line_gap = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
// Read the number of `hmtx` entries.
try!(reader.jump(mem::size_of::<u16>() * 15).map_err(Error::eof));
try!(reader.jump(mem::size_of::<u16>() * 12).map_err(Error::eof));
let number_of_h_metrics = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
Ok(HheaTable {
line_gap: line_gap,
number_of_h_metrics: number_of_h_metrics,
})
}

View File

@ -19,6 +19,7 @@ use otf::hhea::HheaTable;
use otf::hmtx::{HmtxTable, HorizontalMetrics};
use otf::kern::KernTable;
use otf::loca::LocaTable;
use otf::os_2::Os2Table;
use outline::GlyphBounds;
use std::mem;
use std::u16;
@ -31,6 +32,7 @@ mod hhea;
mod hmtx;
mod kern;
mod loca;
mod os_2;
const CMAP: u32 = ((b'c' as u32) << 24) |
((b'm' as u32) << 16) |
@ -60,6 +62,10 @@ const LOCA: u32 = ((b'l' as u32) << 24) |
((b'o' as u32) << 16) |
((b'c' as u32) << 8) |
(b'a' as u32);
const OS_2: u32 = ((b'O' as u32) << 24) |
((b'S' as u32) << 16) |
((b'/' as u32) << 8) |
(b'2' as u32);
const TTCF: u32 = ((b't' as u32) << 24) |
((b't' as u32) << 16) |
((b'c' as u32) << 8) |
@ -90,6 +96,7 @@ pub struct Font<'a> {
head: HeadTable,
hhea: HheaTable,
hmtx: HmtxTable<'a>,
os_2: Os2Table,
glyf: Option<GlyfTable<'a>>,
loca: Option<LocaTable<'a>>,
@ -173,8 +180,8 @@ 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;
let (mut glyf_table, mut kern_table) = (None, None);
let (mut loca_table, mut os_2_table) = (None, None);
for _ in 0..num_tables {
let table_id = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
@ -191,8 +198,9 @@ impl<'a> Font<'a> {
HHEA => &mut hhea_table,
HMTX => &mut hmtx_table,
GLYF => &mut glyf_table,
LOCA => &mut loca_table,
KERN => &mut kern_table,
LOCA => &mut loca_table,
OS_2 => &mut os_2_table,
_ => continue,
};
@ -218,6 +226,7 @@ impl<'a> Font<'a> {
head: try!(HeadTable::new(try!(head_table.ok_or(Error::RequiredTableMissing)))),
hhea: try!(HheaTable::new(try!(hhea_table.ok_or(Error::RequiredTableMissing)))),
hmtx: HmtxTable::new(try!(hmtx_table.ok_or(Error::RequiredTableMissing))),
os_2: try!(Os2Table::new(try!(os_2_table.ok_or(Error::RequiredTableMissing)))),
glyf: glyf_table.map(GlyfTable::new),
loca: loca_table,
@ -383,6 +392,33 @@ impl<'a> Font<'a> {
Some(kern) => kern.kerning_for_glyph_pair(left_glyph_id, right_glyph_id).unwrap_or(0),
}
}
/// Returns the distance from the baseline to the top of the text box in font units.
///
/// The following expression computes the baseline-to-baseline height:
/// `font.ascender() - font.descender() + font.line_gap()`.
#[inline]
pub fn ascender(&self) -> i16 {
self.os_2.typo_ascender
}
/// Returns the distance from the baseline to the bottom of the text box in font units.
///
/// The following expression computes the baseline-to-baseline height:
/// `font.ascender() - font.descender() + font.line_gap()`.
#[inline]
pub fn descender(&self) -> i16 {
self.os_2.typo_descender
}
/// Returns the recommended extra gap between lines in font units.
///
/// The following expression computes the baseline-to-baseline height:
/// `font.ascender() - font.descender() + font.line_gap()`.
#[inline]
pub fn line_gap(&self) -> i16 {
self.os_2.typo_line_gap
}
}
/// Errors that can occur when parsing OpenType fonts.
@ -412,6 +448,8 @@ pub enum Error {
UnsupportedHeadVersion,
/// We don't support the declared version of the font's horizontal metrics.
UnsupportedHheaVersion,
/// We don't support the declared version of the font's OS/2 and Windows table.
UnsupportedOs2Version,
/// A required table is missing.
RequiredTableMissing,
/// The glyph is a composite glyph.

54
src/otf/os_2.rs Normal file
View File

@ -0,0 +1,54 @@
// 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;
#[derive(Clone, Debug)]
pub struct Os2Table {
pub typo_ascender: i16,
pub typo_descender: i16,
pub typo_line_gap: i16,
}
impl Os2Table {
pub fn new(table: FontTable) -> Result<Os2Table, Error> {
let mut reader = table.bytes;
// We should be compatible with all versions. If this is greater than version 5, follow
// Postel's law and hope for the best.
let version = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
// Skip to the line gap.
try!(reader.jump(mem::size_of::<u16>() * 15).map_err(Error::eof));
try!(reader.jump(10).map_err(Error::eof));
if version == 0 {
try!(reader.jump(mem::size_of::<u32>() * 2).map_err(Error::eof));
} else {
try!(reader.jump(mem::size_of::<u32>() * 5).map_err(Error::eof));
}
try!(reader.jump(mem::size_of::<u16>() * 3).map_err(Error::eof));
// Read the line spacing information.
let typo_ascender = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
let typo_descender = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
let typo_line_gap = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
Ok(Os2Table {
typo_ascender: typo_ascender,
typo_descender: typo_descender,
typo_line_gap: typo_line_gap,
})
}
}