Factor out more code from `lorem-ipsum` into a new `GlyphStore` abstraction

This commit is contained in:
Patrick Walton 2017-02-21 15:51:39 -08:00
parent abacb5a4c9
commit 68f5694b6b
4 changed files with 131 additions and 87 deletions

View File

@ -24,9 +24,9 @@ use pathfinder::charmap::CodepointRanges;
use pathfinder::coverage::CoverageBuffer;
use pathfinder::error::RasterError;
use pathfinder::otf::Font;
use pathfinder::outline::{GlyphSubpixelBounds, OutlineBuilder, Outlines};
use pathfinder::outline::GlyphSubpixelBounds;
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
use pathfinder::typesetter::{GlyphPosition, Typesetter};
use pathfinder::typesetter::{GlyphPosition, GlyphStore, Typesetter};
use std::char;
use std::env;
use std::f32;
@ -107,10 +107,8 @@ fn main() {
};
let units_per_em = font.units_per_em() as f32;
let mut typesetter =
Typesetter::new(device_pixel_size.width as f32 * units_per_em / INITIAL_POINT_SIZE,
&font,
units_per_em);
let page_width = device_pixel_size.width as f32 * units_per_em / INITIAL_POINT_SIZE;
let mut typesetter = Typesetter::new(page_width, &font, units_per_em);
typesetter.add_text(&font, font.units_per_em() as f32, &text);
let renderer = Renderer::new();
@ -118,41 +116,23 @@ fn main() {
let mut translation = Point2D::new(0, 0);
let mut dirty = true;
let mut glyph_ids: Vec<_> = typesetter.glyph_positions.iter().map(|gp| gp.glyph_id).collect();
let glyph_store = typesetter.create_glyph_store(&font).unwrap();
// Make sure the characters include `[A-Za-z0-9 ./,]`, for the FPS display.
let mut chars: Vec<char> = text.chars().collect();
chars.extend(" ./,:()".chars());
chars.extend(('A' as u32..('Z' as u32 + 1)).flat_map(char::from_u32));
chars.extend(('a' as u32..('z' as u32 + 1)).flat_map(char::from_u32));
chars.extend(('0' as u32..('9' as u32 + 1)).flat_map(char::from_u32));
chars.sort();
let codepoint_ranges = CodepointRanges::from_sorted_chars(&chars);
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges.ranges).unwrap();
glyph_ids.extend(glyph_mapping.iter().map(|(_, glyph_id)| glyph_id));
glyph_ids.sort();
glyph_ids.dedup();
let mut outline_builder = OutlineBuilder::new();
let mut glyph_indices = vec![];
for glyph_id in glyph_ids {
let glyph_index = outline_builder.add_glyph(&font, glyph_id).unwrap();
while glyph_id as usize >= glyph_indices.len() {
glyph_indices.push(0)
}
glyph_indices[glyph_id as usize] = glyph_index
}
let outlines = outline_builder.create_buffers().unwrap();
// Set up the FPS glyph store.
let mut fps_chars: Vec<char> = vec![];
fps_chars.extend(" ./,:()".chars());
fps_chars.extend(('A' as u32..('Z' as u32 + 1)).flat_map(char::from_u32));
fps_chars.extend(('a' as u32..('z' as u32 + 1)).flat_map(char::from_u32));
fps_chars.extend(('0' as u32..('9' as u32 + 1)).flat_map(char::from_u32));
fps_chars.sort();
let fps_codepoint_ranges = CodepointRanges::from_sorted_chars(&fps_chars);
let fps_glyph_store = GlyphStore::from_codepoints(&fps_codepoint_ranges, &font).unwrap();
while !window.should_close() {
if dirty {
let redraw_result = renderer.redraw(point_size,
&font,
&outlines,
&glyph_indices,
&glyph_store,
&typesetter.glyph_positions,
&device_pixel_size,
&translation);
@ -178,9 +158,8 @@ fn main() {
let timing = renderer.get_timing_in_ms();
renderer.draw_fps(&font,
&outlines,
&fps_glyph_store,
&device_pixel_size,
&glyph_indices,
draw_time,
accum_time,
timing,
@ -424,8 +403,7 @@ impl Renderer {
fn redraw(&self,
point_size: f32,
font: &Font,
outlines: &Outlines,
glyph_indices: &[u16],
glyph_store: &GlyphStore,
glyph_positions: &[GlyphPosition],
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>)
@ -435,8 +413,7 @@ impl Renderer {
let cached_glyphs = self.determine_visible_glyphs(&mut atlas_builder,
font,
outlines,
glyph_indices,
glyph_store,
glyph_positions,
device_pixel_size,
translation,
@ -448,7 +425,7 @@ impl Renderer {
let events = match self.rasterizer.draw_atlas(&self.main_compute_image,
&rect,
&atlas,
outlines,
&glyph_store.outlines,
&self.main_coverage_buffer) {
Ok(events) => Some(events),
Err(RasterError::NoGlyphsToDraw) => None,
@ -473,9 +450,8 @@ impl Renderer {
if events.is_some() {
self.draw_glyphs(&font,
outlines,
glyph_store,
&self.main_composite_vertex_array,
glyph_indices,
glyph_positions,
&cached_glyphs,
device_pixel_size,
@ -495,8 +471,7 @@ impl Renderer {
fn determine_visible_glyphs(&self,
atlas_builder: &mut AtlasBuilder,
font: &Font,
outlines: &Outlines,
glyph_indices: &[u16],
glyph_store: &GlyphStore,
glyph_positions: &[GlyphPosition],
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>,
@ -504,8 +479,8 @@ impl Renderer {
-> Vec<CachedGlyph> {
let mut glyphs = vec![];
for glyph_position in glyph_positions {
let glyph_index = glyph_indices[glyph_position.glyph_id as usize];
let glyph_rect = outlines.glyph_subpixel_bounds(glyph_index, point_size);
let glyph_index = glyph_store.glyph_index(glyph_position.glyph_id).unwrap();
let glyph_rect = glyph_store.outlines.glyph_subpixel_bounds(glyph_index, point_size);
if let Some(subpixel) = self.subpixel_for_glyph_if_visible(font,
glyph_position,
&glyph_rect,
@ -521,7 +496,7 @@ impl Renderer {
glyphs.iter().map(|&(glyph_index, subpixel)| {
let subpixel_offset = (subpixel as f32) / (SUBPIXEL_GRANULARITY as f32);
let origin = atlas_builder.pack_glyph(&outlines,
let origin = atlas_builder.pack_glyph(&glyph_store.outlines,
glyph_index,
point_size,
subpixel_offset).unwrap();
@ -576,9 +551,8 @@ impl Renderer {
fn draw_glyphs(&self,
font: &Font,
outlines: &Outlines,
glyph_store: &GlyphStore,
vertex_array: &CompositeVertexArray,
glyph_indices: &[u16],
glyph_positions: &[GlyphPosition],
cached_glyphs: &[CachedGlyph],
device_pixel_size: &Size2D<u32>,
@ -594,8 +568,7 @@ impl Renderer {
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vertex_array.index_buffer);
let vertex_count = self.upload_quads_for_text(font,
outlines,
glyph_indices,
glyph_store,
glyph_positions,
cached_glyphs,
device_pixel_size,
@ -636,8 +609,7 @@ impl Renderer {
fn upload_quads_for_text(&self,
font: &Font,
outlines: &Outlines,
glyph_indices: &[u16],
glyph_store: &GlyphStore,
glyph_positions: &[GlyphPosition],
cached_glyphs: &[CachedGlyph],
device_pixel_size: &Size2D<u32>,
@ -649,12 +621,8 @@ impl Renderer {
let (mut vertices, mut indices) = (vec![], vec![]);
for glyph_position in glyph_positions {
let glyph_index = match glyph_indices.get(glyph_position.glyph_id as usize) {
Some(&index) => index,
None => glyph_indices[0],
};
let glyph_rect = outlines.glyph_subpixel_bounds(glyph_index, point_size);
let glyph_index = glyph_store.glyph_index(glyph_position.glyph_id).unwrap();
let glyph_rect = glyph_store.outlines.glyph_subpixel_bounds(glyph_index, point_size);
let subpixel = if use_subpixel_positioning {
match self.subpixel_for_glyph_if_visible(font,
@ -717,9 +685,8 @@ impl Renderer {
fn draw_fps(&self,
font: &Font,
outlines: &Outlines,
fps_glyph_store: &GlyphStore,
device_pixel_size: &Size2D<u32>,
glyph_indices: &[u16],
draw_time: f64,
accum_time: f64,
composite_time: f64,
@ -766,22 +733,12 @@ impl Renderer {
let mut fps_typesetter = Typesetter::new(f32::INFINITY, &font, font.units_per_em() as f32);
fps_typesetter.add_text(&font, font.units_per_em() as f32, &fps_text);
let mut fps_glyph_indices: Vec<_> =
fps_typesetter.glyph_positions.iter().map(|glyph_pos| {
match glyph_indices.get(glyph_pos.glyph_id as usize) {
Some(&index) => index,
None => glyph_indices[0],
}
}).collect();
fps_glyph_indices.sort();
fps_glyph_indices.dedup();
let shelf_height = font.shelf_height(FPS_DISPLAY_POINT_SIZE);
let mut fps_atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
let mut fps_glyphs = vec![];
for &fps_glyph_index in &fps_glyph_indices {
let origin = fps_atlas_builder.pack_glyph(&outlines,
for &fps_glyph_index in &fps_glyph_store.all_glyph_indices {
let origin = fps_atlas_builder.pack_glyph(&fps_glyph_store.outlines,
fps_glyph_index,
FPS_DISPLAY_POINT_SIZE,
0.0).unwrap();
@ -798,7 +755,7 @@ impl Renderer {
self.rasterizer.draw_atlas(&self.fps_compute_image,
&rect,
&fps_atlas,
outlines,
&fps_glyph_store.outlines,
&self.fps_coverage_buffer).unwrap();
self.rasterizer.queue().flush().unwrap();
@ -810,9 +767,8 @@ impl Renderer {
device_pixel_size.height as i32 - FPS_PADDING - fps_line_spacing);
self.draw_glyphs(font,
outlines,
&fps_glyph_store,
&self.fps_composite_vertex_array,
glyph_indices,
&fps_typesetter.glyph_positions,
&fps_glyphs,
device_pixel_size,

View File

@ -49,6 +49,14 @@ impl CodepointRange {
}
impl CodepointRanges {
/// Creates an empty set of codepoint ranges.
#[inline]
pub fn empty() -> CodepointRanges {
CodepointRanges {
ranges: vec![],
}
}
/// Creates codepoint ranges from a sorted array of characters, collapsing duplicates.
///
/// This is useful when creating an atlas from a string. The array can be readily produced with
@ -141,7 +149,7 @@ impl GlyphMapping {
glyph_index: 0,
},
end: GlyphRangesIndex {
range_index: 0,
range_index: -1,
glyph_index: 0,
},
codepoint: 0,
@ -152,11 +160,11 @@ impl GlyphMapping {
GlyphMappingIter {
start: GlyphRangesIndex {
range_index: 0,
glyph_index: self.ranges[0].glyphs.start,
glyph_index: self.ranges[0].glyphs.start as i32,
},
end: GlyphRangesIndex {
range_index: (self.ranges.len() - 1) as u16,
glyph_index: self.ranges.last().unwrap().glyphs.end,
range_index: self.ranges.len() as i32 - 1,
glyph_index: self.ranges.last().unwrap().glyphs.end as i32,
},
codepoint: self.ranges[0].codepoint_start,
ranges: &self.ranges,
@ -221,18 +229,20 @@ impl<'a> Iterator for GlyphMappingIter<'a> {
return None
}
let item = (self.codepoint, self.start.glyph_index);
let item = (self.codepoint, self.start.glyph_index as u16);
self.codepoint += 1;
self.start.glyph_index += 1;
while self.start.glyph_index > self.ranges[self.start.range_index as usize].glyphs.end {
while self.start.glyph_index > self.ranges[self.start.range_index as usize].glyphs.end as
i32 {
self.start.range_index += 1;
if self.start.range_index > self.end.range_index {
break
}
self.start.glyph_index = self.ranges[self.start.range_index as usize].glyphs.start;
self.start.glyph_index = self.ranges[self.start.range_index as usize].glyphs.start as
i32;
self.codepoint = self.ranges[self.start.range_index as usize].codepoint_start;
}
@ -242,8 +252,8 @@ impl<'a> Iterator for GlyphMappingIter<'a> {
#[derive(Clone, Copy, Debug)]
struct GlyphRangesIndex {
range_index: u16,
glyph_index: u16,
range_index: i32,
glyph_index: i32,
}
impl MappedGlyphRange {

View File

@ -12,6 +12,7 @@
use compute_shader;
use gl::types::GLenum;
use otf;
use std::io;
/// An OpenGL error with the given code.
@ -63,3 +64,12 @@ pub enum RasterError {
UnsupportedImageFormat,
}
/// An error in glyph store creation. See `Typesetter::create_glyph_store()`.
#[derive(Debug)]
pub enum GlyphStoreCreationError {
/// An error occurred when looking up a glyph ID for a character in the font.
OtfError(otf::Error),
/// An error occurred when uploading the outlines to the GPU.
GlError(GlError),
}

View File

@ -15,9 +15,12 @@
//! control over line spacing. Use Cocoa's `NSLayoutManager`, Pango, etc. for real use.
use charmap::CodepointRanges;
use error::GlyphStoreCreationError;
use euclid::Point2D;
use otf::Font;
use outline::{OutlineBuilder, Outlines};
use shaper;
use std::u16;
pub struct Typesetter {
pub glyph_positions: Vec<GlyphPosition>,
@ -79,6 +82,14 @@ impl Typesetter {
pub fn glyph_positions(&self) -> &[GlyphPosition] {
&self.glyph_positions
}
pub fn create_glyph_store(&self, font: &Font) -> Result<GlyphStore, GlyphStoreCreationError> {
let glyph_ids = self.glyph_positions
.iter()
.map(|glyph_position| glyph_position.glyph_id)
.collect();
GlyphStore::from_glyph_ids(glyph_ids, font)
}
}
#[repr(C)]
@ -89,3 +100,60 @@ pub struct GlyphPosition {
pub glyph_id: u16,
}
pub struct GlyphStore {
pub outlines: Outlines,
pub glyph_id_to_glyph_index: Vec<u16>,
pub all_glyph_indices: Vec<u16>,
}
impl GlyphStore {
fn from_glyph_ids(mut glyph_ids: Vec<u16>, font: &Font)
-> Result<GlyphStore, GlyphStoreCreationError> {
glyph_ids.sort();
glyph_ids.dedup();
let last_glyph_id = match glyph_ids.last() {
Some(&id) => id + 1,
None => 0,
};
let mut outline_builder = OutlineBuilder::new();
let mut glyph_id_to_glyph_index = vec![u16::MAX; last_glyph_id as usize];
let mut all_glyph_indices = vec![];
for glyph_id in glyph_ids {
let glyph_index = try!(outline_builder.add_glyph(font, glyph_id)
.map_err(GlyphStoreCreationError::OtfError));
glyph_id_to_glyph_index[glyph_id as usize] = glyph_index;
all_glyph_indices.push(glyph_index);
}
let outlines = try!(outline_builder.create_buffers()
.map_err(GlyphStoreCreationError::GlError));
all_glyph_indices.sort();
all_glyph_indices.dedup();
Ok(GlyphStore {
outlines: outlines,
glyph_id_to_glyph_index: glyph_id_to_glyph_index,
all_glyph_indices: all_glyph_indices,
})
}
pub fn from_codepoints(codepoints: &CodepointRanges, font: &Font)
-> Result<GlyphStore, GlyphStoreCreationError> {
let mapping = try!(font.glyph_mapping_for_codepoint_ranges(&codepoints.ranges)
.map_err(GlyphStoreCreationError::OtfError));
let glyph_ids = mapping.iter().map(|(_, glyph_id)| glyph_id).collect();
GlyphStore::from_glyph_ids(glyph_ids, font)
}
#[inline]
pub fn glyph_index(&self, glyph_id: u16) -> Option<u16> {
match self.glyph_id_to_glyph_index.get(glyph_id as usize) {
None | Some(&u16::MAX) => None,
Some(&index) => Some(index),
}
}
}