From 6261c87b89275c3d64d058b83576acfef07cb0d3 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 9 Feb 2017 20:02:32 -0800 Subject: [PATCH] Read the OS/2 metrics to determine the right line spacing --- examples/lorem-ipsum.rs | 3 ++- src/lib.rs | 35 ++++++++++++++++++++++++-- src/otf/hhea.rs | 9 ++++++- src/otf/mod.rs | 44 ++++++++++++++++++++++++++++++--- src/otf/os_2.rs | 54 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 src/otf/os_2.rs diff --git a/examples/lorem-ipsum.rs b/examples/lorem-ipsum.rs index 2f462d27..11ddc04b 100644 --- a/examples/lorem-ipsum.rs +++ b/examples/lorem-ipsum.rs @@ -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() { diff --git a/src/lib.rs b/src/lib.rs index fb14411f..6956bcc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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))] diff --git a/src/otf/hhea.rs b/src/otf/hhea.rs index 39ebc85d..d7f267a2 100644 --- a/src/otf/hhea.rs +++ b/src/otf/hhea.rs @@ -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::().map_err(Error::eof)); + let _descender = try!(reader.read_i16::().map_err(Error::eof)); + let line_gap = try!(reader.read_i16::().map_err(Error::eof)); + // Read the number of `hmtx` entries. - try!(reader.jump(mem::size_of::() * 15).map_err(Error::eof)); + try!(reader.jump(mem::size_of::() * 12).map_err(Error::eof)); let number_of_h_metrics = try!(reader.read_u16::().map_err(Error::eof)); Ok(HheaTable { + line_gap: line_gap, number_of_h_metrics: number_of_h_metrics, }) } diff --git a/src/otf/mod.rs b/src/otf/mod.rs index 9fcdafdd..4b069690 100644 --- a/src/otf/mod.rs +++ b/src/otf/mod.rs @@ -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>, loca: Option>, @@ -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::().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. diff --git a/src/otf/os_2.rs b/src/otf/os_2.rs new file mode 100644 index 00000000..2e7ce1f2 --- /dev/null +++ b/src/otf/os_2.rs @@ -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 or the MIT license +// , 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 { + 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::().map_err(Error::eof)); + + // Skip to the line gap. + try!(reader.jump(mem::size_of::() * 15).map_err(Error::eof)); + try!(reader.jump(10).map_err(Error::eof)); + if version == 0 { + try!(reader.jump(mem::size_of::() * 2).map_err(Error::eof)); + } else { + try!(reader.jump(mem::size_of::() * 5).map_err(Error::eof)); + } + try!(reader.jump(mem::size_of::() * 3).map_err(Error::eof)); + + // Read the line spacing information. + let typo_ascender = try!(reader.read_i16::().map_err(Error::eof)); + let typo_descender = try!(reader.read_i16::().map_err(Error::eof)); + let typo_line_gap = try!(reader.read_i16::().map_err(Error::eof)); + + Ok(Os2Table { + typo_ascender: typo_ascender, + typo_descender: typo_descender, + typo_line_gap: typo_line_gap, + }) + } +} + +