From 68f5694b6b645060e99d758a0af34f558111e6fa Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 21 Feb 2017 15:51:39 -0800 Subject: [PATCH] Factor out more code from `lorem-ipsum` into a new `GlyphStore` abstraction --- examples/lorem-ipsum.rs | 112 ++++++++++++---------------------------- src/charmap.rs | 28 ++++++---- src/error.rs | 10 ++++ src/typesetter.rs | 68 ++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 87 deletions(-) diff --git a/examples/lorem-ipsum.rs b/examples/lorem-ipsum.rs index 80dc66bb..0fa5a3ab 100644 --- a/examples/lorem-ipsum.rs +++ b/examples/lorem-ipsum.rs @@ -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 = 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 = 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, translation: &Point2D) @@ -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, translation: &Point2D, @@ -504,8 +479,8 @@ impl Renderer { -> Vec { 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, @@ -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, @@ -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, - 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, diff --git a/src/charmap.rs b/src/charmap.rs index af85567c..db42c754 100644 --- a/src/charmap.rs +++ b/src/charmap.rs @@ -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 { diff --git a/src/error.rs b/src/error.rs index b6a9847a..be8d4398 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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), +} + diff --git a/src/typesetter.rs b/src/typesetter.rs index 6368f778..8a063c75 100644 --- a/src/typesetter.rs +++ b/src/typesetter.rs @@ -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, @@ -79,6 +82,14 @@ impl Typesetter { pub fn glyph_positions(&self) -> &[GlyphPosition] { &self.glyph_positions } + + pub fn create_glyph_store(&self, font: &Font) -> Result { + 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, + pub all_glyph_indices: Vec, +} + +impl GlyphStore { + fn from_glyph_ids(mut glyph_ids: Vec, font: &Font) + -> Result { + 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 { + 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 { + match self.glyph_id_to_glyph_index.get(glyph_id as usize) { + None | Some(&u16::MAX) => None, + Some(&index) => Some(index), + } + } +} +