Move some code from `lorem-ipsum` into a new `Typesetter` object so others can use it
This commit is contained in:
parent
49408b95cb
commit
abacb5a4c9
|
@ -20,15 +20,16 @@ use glfw::{Action, Context, Key, OpenGlProfileHint, SwapInterval, WindowEvent};
|
||||||
use glfw::{WindowHint, WindowMode};
|
use glfw::{WindowHint, WindowMode};
|
||||||
use memmap::{Mmap, Protection};
|
use memmap::{Mmap, Protection};
|
||||||
use pathfinder::atlas::AtlasBuilder;
|
use pathfinder::atlas::AtlasBuilder;
|
||||||
use pathfinder::charmap::{CodepointRanges, GlyphMapping};
|
use pathfinder::charmap::CodepointRanges;
|
||||||
use pathfinder::coverage::CoverageBuffer;
|
use pathfinder::coverage::CoverageBuffer;
|
||||||
use pathfinder::error::RasterError;
|
use pathfinder::error::RasterError;
|
||||||
use pathfinder::otf::Font;
|
use pathfinder::otf::Font;
|
||||||
use pathfinder::outline::{GlyphSubpixelBounds, OutlineBuilder, Outlines};
|
use pathfinder::outline::{GlyphSubpixelBounds, OutlineBuilder, Outlines};
|
||||||
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
|
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
|
||||||
use pathfinder::shaper;
|
use pathfinder::typesetter::{GlyphPosition, Typesetter};
|
||||||
use std::char;
|
use std::char;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::f32;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -95,6 +96,30 @@ fn main() {
|
||||||
}
|
}
|
||||||
text = text.replace(&['\n', '\r', '\t'][..], " ");
|
text = text.replace(&['\n', '\r', '\t'][..], " ");
|
||||||
|
|
||||||
|
let font_index = match matches.value_of("index") {
|
||||||
|
Some(index) => index.parse().unwrap(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
|
||||||
|
let font = unsafe {
|
||||||
|
Font::from_collection_index(file.as_slice(), font_index).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
typesetter.add_text(&font, font.units_per_em() as f32, &text);
|
||||||
|
|
||||||
|
let renderer = Renderer::new();
|
||||||
|
let mut point_size = INITIAL_POINT_SIZE;
|
||||||
|
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();
|
||||||
|
|
||||||
// Make sure the characters include `[A-Za-z0-9 ./,]`, for the FPS display.
|
// Make sure the characters include `[A-Za-z0-9 ./,]`, for the FPS display.
|
||||||
let mut chars: Vec<char> = text.chars().collect();
|
let mut chars: Vec<char> = text.chars().collect();
|
||||||
chars.extend(" ./,:()".chars());
|
chars.extend(" ./,:()".chars());
|
||||||
|
@ -103,58 +128,15 @@ fn main() {
|
||||||
chars.extend(('0' as u32..('9' as u32 + 1)).flat_map(char::from_u32));
|
chars.extend(('0' as u32..('9' as u32 + 1)).flat_map(char::from_u32));
|
||||||
chars.sort();
|
chars.sort();
|
||||||
let codepoint_ranges = CodepointRanges::from_sorted_chars(&chars);
|
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));
|
||||||
|
|
||||||
let font_index = match matches.value_of("index") {
|
glyph_ids.sort();
|
||||||
Some(index) => index.parse().unwrap(),
|
glyph_ids.dedup();
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
|
|
||||||
let (font, glyph_mapping);
|
|
||||||
unsafe {
|
|
||||||
font = Font::from_collection_index(file.as_slice(), font_index).unwrap();
|
|
||||||
glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges.ranges).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do some basic line breaking.
|
|
||||||
let mut glyph_positions = vec![];
|
|
||||||
let paragraph_width = (device_pixel_size.width as f32 * font.units_per_em() as f32 /
|
|
||||||
INITIAL_POINT_SIZE) as u32;
|
|
||||||
let space_advance = font.metrics_for_glyph(glyph_mapping.glyph_for(' ' as u32).unwrap())
|
|
||||||
.unwrap()
|
|
||||||
.advance_width 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() {
|
|
||||||
let shaped_glyph_positions = shaper::shape_text(&font, &glyph_mapping, word);
|
|
||||||
let total_advance: u32 = shaped_glyph_positions.iter().map(|p| p.advance as u32).sum();
|
|
||||||
if current_x + total_advance > paragraph_width {
|
|
||||||
current_x = 0;
|
|
||||||
current_y += line_spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
for glyph_position in &shaped_glyph_positions {
|
|
||||||
glyph_positions.push(GlyphPos {
|
|
||||||
x: current_x,
|
|
||||||
y: current_y,
|
|
||||||
glyph_id: glyph_position.glyph_id,
|
|
||||||
});
|
|
||||||
current_x += glyph_position.advance as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
current_x += space_advance
|
|
||||||
}
|
|
||||||
|
|
||||||
let renderer = Renderer::new();
|
|
||||||
let mut point_size = INITIAL_POINT_SIZE;
|
|
||||||
let mut translation = Point2D::new(0, 0);
|
|
||||||
let mut dirty = true;
|
|
||||||
|
|
||||||
let mut outline_builder = OutlineBuilder::new();
|
let mut outline_builder = OutlineBuilder::new();
|
||||||
let mut glyph_indices = vec![];
|
let mut glyph_indices = vec![];
|
||||||
for (_, glyph_id) in glyph_mapping.iter() {
|
for glyph_id in glyph_ids {
|
||||||
let glyph_index = outline_builder.add_glyph(&font, glyph_id).unwrap();
|
let glyph_index = outline_builder.add_glyph(&font, glyph_id).unwrap();
|
||||||
|
|
||||||
while glyph_id as usize >= glyph_indices.len() {
|
while glyph_id as usize >= glyph_indices.len() {
|
||||||
|
@ -171,7 +153,7 @@ fn main() {
|
||||||
&font,
|
&font,
|
||||||
&outlines,
|
&outlines,
|
||||||
&glyph_indices,
|
&glyph_indices,
|
||||||
&glyph_positions,
|
&typesetter.glyph_positions,
|
||||||
&device_pixel_size,
|
&device_pixel_size,
|
||||||
&translation);
|
&translation);
|
||||||
|
|
||||||
|
@ -199,7 +181,6 @@ fn main() {
|
||||||
&outlines,
|
&outlines,
|
||||||
&device_pixel_size,
|
&device_pixel_size,
|
||||||
&glyph_indices,
|
&glyph_indices,
|
||||||
&glyph_mapping,
|
|
||||||
draw_time,
|
draw_time,
|
||||||
accum_time,
|
accum_time,
|
||||||
timing,
|
timing,
|
||||||
|
@ -445,7 +426,7 @@ impl Renderer {
|
||||||
font: &Font,
|
font: &Font,
|
||||||
outlines: &Outlines,
|
outlines: &Outlines,
|
||||||
glyph_indices: &[u16],
|
glyph_indices: &[u16],
|
||||||
glyph_positions: &[GlyphPos],
|
glyph_positions: &[GlyphPosition],
|
||||||
device_pixel_size: &Size2D<u32>,
|
device_pixel_size: &Size2D<u32>,
|
||||||
translation: &Point2D<i32>)
|
translation: &Point2D<i32>)
|
||||||
-> RedrawResult {
|
-> RedrawResult {
|
||||||
|
@ -516,7 +497,7 @@ impl Renderer {
|
||||||
font: &Font,
|
font: &Font,
|
||||||
outlines: &Outlines,
|
outlines: &Outlines,
|
||||||
glyph_indices: &[u16],
|
glyph_indices: &[u16],
|
||||||
glyph_positions: &[GlyphPos],
|
glyph_positions: &[GlyphPosition],
|
||||||
device_pixel_size: &Size2D<u32>,
|
device_pixel_size: &Size2D<u32>,
|
||||||
translation: &Point2D<i32>,
|
translation: &Point2D<i32>,
|
||||||
point_size: f32)
|
point_size: f32)
|
||||||
|
@ -563,7 +544,7 @@ impl Renderer {
|
||||||
|
|
||||||
fn subpixel_for_glyph_if_visible(&self,
|
fn subpixel_for_glyph_if_visible(&self,
|
||||||
font: &Font,
|
font: &Font,
|
||||||
glyph_position: &GlyphPos,
|
glyph_position: &GlyphPosition,
|
||||||
glyph_rect: &GlyphSubpixelBounds,
|
glyph_rect: &GlyphSubpixelBounds,
|
||||||
device_pixel_size: &Size2D<u32>,
|
device_pixel_size: &Size2D<u32>,
|
||||||
translation: &Point2D<i32>,
|
translation: &Point2D<i32>,
|
||||||
|
@ -598,7 +579,7 @@ impl Renderer {
|
||||||
outlines: &Outlines,
|
outlines: &Outlines,
|
||||||
vertex_array: &CompositeVertexArray,
|
vertex_array: &CompositeVertexArray,
|
||||||
glyph_indices: &[u16],
|
glyph_indices: &[u16],
|
||||||
glyph_positions: &[GlyphPos],
|
glyph_positions: &[GlyphPosition],
|
||||||
cached_glyphs: &[CachedGlyph],
|
cached_glyphs: &[CachedGlyph],
|
||||||
device_pixel_size: &Size2D<u32>,
|
device_pixel_size: &Size2D<u32>,
|
||||||
translation: &Point2D<i32>,
|
translation: &Point2D<i32>,
|
||||||
|
@ -657,7 +638,7 @@ impl Renderer {
|
||||||
font: &Font,
|
font: &Font,
|
||||||
outlines: &Outlines,
|
outlines: &Outlines,
|
||||||
glyph_indices: &[u16],
|
glyph_indices: &[u16],
|
||||||
glyph_positions: &[GlyphPos],
|
glyph_positions: &[GlyphPosition],
|
||||||
cached_glyphs: &[CachedGlyph],
|
cached_glyphs: &[CachedGlyph],
|
||||||
device_pixel_size: &Size2D<u32>,
|
device_pixel_size: &Size2D<u32>,
|
||||||
translation: &Point2D<i32>,
|
translation: &Point2D<i32>,
|
||||||
|
@ -668,7 +649,11 @@ impl Renderer {
|
||||||
|
|
||||||
let (mut vertices, mut indices) = (vec![], vec![]);
|
let (mut vertices, mut indices) = (vec![], vec![]);
|
||||||
for glyph_position in glyph_positions {
|
for glyph_position in glyph_positions {
|
||||||
let glyph_index = glyph_indices[glyph_position.glyph_id as usize];
|
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_rect = outlines.glyph_subpixel_bounds(glyph_index, point_size);
|
||||||
|
|
||||||
let subpixel = if use_subpixel_positioning {
|
let subpixel = if use_subpixel_positioning {
|
||||||
|
@ -735,7 +720,6 @@ impl Renderer {
|
||||||
outlines: &Outlines,
|
outlines: &Outlines,
|
||||||
device_pixel_size: &Size2D<u32>,
|
device_pixel_size: &Size2D<u32>,
|
||||||
glyph_indices: &[u16],
|
glyph_indices: &[u16],
|
||||||
glyph_mapping: &GlyphMapping,
|
|
||||||
draw_time: f64,
|
draw_time: f64,
|
||||||
accum_time: f64,
|
accum_time: f64,
|
||||||
composite_time: f64,
|
composite_time: f64,
|
||||||
|
@ -779,26 +763,23 @@ impl Renderer {
|
||||||
(composite_time * 1000.0) / (glyphs_drawn as f64));
|
(composite_time * 1000.0) / (glyphs_drawn as f64));
|
||||||
|
|
||||||
// TODO(pcwalton): Subpixel positioning for the FPS display.
|
// TODO(pcwalton): Subpixel positioning for the FPS display.
|
||||||
let (mut fps_glyph_positions, mut fps_glyph_indices) = (vec![], vec![]);
|
let mut fps_typesetter = Typesetter::new(f32::INFINITY, &font, font.units_per_em() as f32);
|
||||||
let mut current_x = 0;
|
fps_typesetter.add_text(&font, font.units_per_em() as f32, &fps_text);
|
||||||
for glyph_pos in &shaper::shape_text(&font, &glyph_mapping, &fps_text) {
|
|
||||||
fps_glyph_positions.push(GlyphPos {
|
|
||||||
x: current_x,
|
|
||||||
y: 0,
|
|
||||||
glyph_id: glyph_pos.glyph_id,
|
|
||||||
});
|
|
||||||
current_x += glyph_pos.advance as u32;
|
|
||||||
|
|
||||||
fps_glyph_indices.push(glyph_indices[glyph_pos.glyph_id as usize]);
|
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 shelf_height = font.shelf_height(FPS_DISPLAY_POINT_SIZE);
|
||||||
let mut fps_atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
|
let mut fps_atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
|
||||||
|
|
||||||
let mut fps_glyphs = vec![];
|
let mut fps_glyphs = vec![];
|
||||||
fps_glyph_indices.sort();
|
|
||||||
fps_glyph_indices.dedup();
|
|
||||||
|
|
||||||
for &fps_glyph_index in &fps_glyph_indices {
|
for &fps_glyph_index in &fps_glyph_indices {
|
||||||
let origin = fps_atlas_builder.pack_glyph(&outlines,
|
let origin = fps_atlas_builder.pack_glyph(&outlines,
|
||||||
fps_glyph_index,
|
fps_glyph_index,
|
||||||
|
@ -821,14 +802,21 @@ impl Renderer {
|
||||||
&self.fps_coverage_buffer).unwrap();
|
&self.fps_coverage_buffer).unwrap();
|
||||||
self.rasterizer.queue().flush().unwrap();
|
self.rasterizer.queue().flush().unwrap();
|
||||||
|
|
||||||
|
let fps_pixels_per_unit = FPS_DISPLAY_POINT_SIZE / font.units_per_em() as f32;
|
||||||
|
let fps_line_spacing = ((font.ascender() as f32 - font.descender() as f32 +
|
||||||
|
font.line_gap() as f32) * fps_pixels_per_unit).round() as i32;
|
||||||
|
let fps_origin =
|
||||||
|
Point2D::new(FPS_PADDING,
|
||||||
|
device_pixel_size.height as i32 - FPS_PADDING - fps_line_spacing);
|
||||||
|
|
||||||
self.draw_glyphs(font,
|
self.draw_glyphs(font,
|
||||||
outlines,
|
outlines,
|
||||||
&self.fps_composite_vertex_array,
|
&self.fps_composite_vertex_array,
|
||||||
glyph_indices,
|
glyph_indices,
|
||||||
&fps_glyph_positions,
|
&fps_typesetter.glyph_positions,
|
||||||
&fps_glyphs,
|
&fps_glyphs,
|
||||||
device_pixel_size,
|
device_pixel_size,
|
||||||
&Point2D::new(FPS_PADDING, device_pixel_size.height as i32 - FPS_PADDING),
|
&fps_origin,
|
||||||
self.fps_gl_texture,
|
self.fps_gl_texture,
|
||||||
FPS_DISPLAY_POINT_SIZE,
|
FPS_DISPLAY_POINT_SIZE,
|
||||||
&FPS_FOREGROUND_COLOR,
|
&FPS_FOREGROUND_COLOR,
|
||||||
|
@ -886,13 +874,6 @@ impl Vertex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
struct GlyphPos {
|
|
||||||
x: u32,
|
|
||||||
y: u32,
|
|
||||||
glyph_id: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct CachedGlyph {
|
struct CachedGlyph {
|
||||||
x: f32,
|
x: f32,
|
||||||
|
|
|
@ -95,6 +95,7 @@ pub mod otf;
|
||||||
pub mod outline;
|
pub mod outline;
|
||||||
pub mod rasterizer;
|
pub mod rasterizer;
|
||||||
pub mod shaper;
|
pub mod shaper;
|
||||||
|
pub mod typesetter;
|
||||||
|
|
||||||
mod rect_packer;
|
mod rect_packer;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Simple text layout.
|
||||||
|
//!
|
||||||
|
//! Do not use this for international or high-quality text. This layout has all of the limitations
|
||||||
|
//! of the shaper; additionally, it only does left-to-right text with a uniform page width and no
|
||||||
|
//! control over line spacing. Use Cocoa's `NSLayoutManager`, Pango, etc. for real use.
|
||||||
|
|
||||||
|
use charmap::CodepointRanges;
|
||||||
|
use euclid::Point2D;
|
||||||
|
use otf::Font;
|
||||||
|
use shaper;
|
||||||
|
|
||||||
|
pub struct Typesetter {
|
||||||
|
pub glyph_positions: Vec<GlyphPosition>,
|
||||||
|
page_width: f32,
|
||||||
|
cursor: Point2D<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Typesetter {
|
||||||
|
pub fn new(page_width: f32, initial_font: &Font, initial_point_size: f32) -> Typesetter {
|
||||||
|
let pixels_per_unit = initial_point_size / initial_font.units_per_em() as f32;
|
||||||
|
let initial_position = initial_font.ascender() as f32 * pixels_per_unit;
|
||||||
|
|
||||||
|
Typesetter {
|
||||||
|
glyph_positions: vec![],
|
||||||
|
page_width: page_width,
|
||||||
|
cursor: Point2D::new(0.0, initial_position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_text(&mut self, font: &Font, point_size: f32, string: &str) {
|
||||||
|
// TODO(pcwalton): Cache this mapping.
|
||||||
|
let mut chars: Vec<char> = string.chars().collect();
|
||||||
|
chars.push(' ');
|
||||||
|
chars.sort();
|
||||||
|
let codepoint_ranges = CodepointRanges::from_sorted_chars(&chars);
|
||||||
|
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges.ranges)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// All of these values are in pixels.
|
||||||
|
let pixels_per_unit = point_size / font.units_per_em() as f32;
|
||||||
|
let space_advance = font.metrics_for_glyph(glyph_mapping.glyph_for(' ' as u32).unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.advance_width as f32 * pixels_per_unit;
|
||||||
|
let line_spacing = (font.ascender() as f32 - font.descender() as f32 +
|
||||||
|
font.line_gap() as f32) * pixels_per_unit;
|
||||||
|
|
||||||
|
for word in string.split_whitespace() {
|
||||||
|
let shaped_glyph_positions = shaper::shape_text(&font, &glyph_mapping, word);
|
||||||
|
let total_advance = pixels_per_unit *
|
||||||
|
shaped_glyph_positions.iter().map(|p| p.advance as f32).sum::<f32>();
|
||||||
|
if self.cursor.x + total_advance > self.page_width {
|
||||||
|
self.cursor.x = 0.0;
|
||||||
|
self.cursor.y += line_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
for glyph_position in &shaped_glyph_positions {
|
||||||
|
self.glyph_positions.push(GlyphPosition {
|
||||||
|
x: self.cursor.x,
|
||||||
|
y: self.cursor.y,
|
||||||
|
glyph_id: glyph_position.glyph_id,
|
||||||
|
});
|
||||||
|
self.cursor.x += glyph_position.advance as f32 * pixels_per_unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cursor.x += space_advance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn glyph_positions(&self) -> &[GlyphPosition] {
|
||||||
|
&self.glyph_positions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct GlyphPosition {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub glyph_id: u16,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue