Add support for subpixel offsets.

Closes #11.
This commit is contained in:
Patrick Walton 2017-02-20 16:01:15 -08:00
parent e2014cff13
commit 49408b95cb
15 changed files with 366 additions and 207 deletions

View File

@ -90,7 +90,8 @@ fn main() {
let mut atlas_builder = AtlasBuilder::new(device_pixel_width as GLuint,
shelf_height);
for glyph_index in 0..(glyph_count as u16) {
atlas_builder.pack_glyph(&outlines, glyph_index, point_size as f32).unwrap();
atlas_builder.pack_glyph(&outlines, glyph_index, point_size as f32, 0.0)
.unwrap();
}
atlas = atlas_builder.create_atlas().unwrap();
}

View File

@ -5,10 +5,9 @@ extern crate euclid;
extern crate memmap;
extern crate pathfinder;
use euclid::Point2D;
use memmap::{Mmap, Protection};
use pathfinder::charmap::CodepointRange;
use pathfinder::otf::Font;
use pathfinder::otf::{Font, PointKind};
use std::char;
use std::env;
@ -25,28 +24,26 @@ fn main() {
codepoint,
char::from_u32(codepoint).unwrap_or('?'));
let mut last_point: Option<Point2D<i16>> = None;
let mut last_point_was_off_curve = false;
let mut last_point_was_on_curve = false;
font.for_each_point(glyph_id, |point| {
if point.index_in_contour == 0 {
println!("M {},{}", point.position.x, point.position.y);
let prefix = if point.index_in_contour == 0 {
"M "
} else {
let last = last_point.unwrap();
if point.on_curve {
if last_point_was_off_curve {
println!("Q {},{} {},{}",
last.x,
last.y,
point.position.x,
point.position.y);
} else {
println!("L {},{}", point.position.x, point.position.y);
}
match point.kind {
PointKind::OnCurve if last_point_was_on_curve => "L ",
PointKind::OnCurve => " ",
PointKind::QuadControl => "Q ",
PointKind::FirstCubicControl => "C ",
PointKind::SecondCubicControl => " ",
}
}
};
last_point_was_off_curve = !point.on_curve;
last_point = Some(point.position);
print!("{}{},{}", prefix, point.position.x, point.position.y);
last_point_was_on_curve = point.kind == PointKind::OnCurve;
if last_point_was_on_curve {
println!("")
}
}).unwrap()
}
}

View File

@ -101,7 +101,7 @@ fn main() {
let mut atlas_builder = AtlasBuilder::new(device_pixel_width as GLuint, shelf_height);
for glyph_index in 0..glyph_count {
atlas_builder.pack_glyph(&outlines, glyph_index, point_size).unwrap();
atlas_builder.pack_glyph(&outlines, glyph_index, point_size, 0.0).unwrap();
}
atlas = atlas_builder.create_atlas().unwrap();
}

View File

@ -12,7 +12,7 @@ extern crate pathfinder;
use clap::{App, Arg};
use compute_shader::buffer;
use compute_shader::image::{ExternalImage, Format, Image};
use compute_shader::image::{Color, ExternalImage, Format, Image};
use compute_shader::instance::{Instance, ShadingLanguage};
use euclid::{Point2D, Rect, Size2D};
use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
@ -22,8 +22,9 @@ use memmap::{Mmap, Protection};
use pathfinder::atlas::AtlasBuilder;
use pathfinder::charmap::{CodepointRanges, GlyphMapping};
use pathfinder::coverage::CoverageBuffer;
use pathfinder::error::RasterError;
use pathfinder::otf::Font;
use pathfinder::outline::{OutlineBuilder, Outlines};
use pathfinder::outline::{GlyphSubpixelBounds, OutlineBuilder, Outlines};
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
use pathfinder::shaper;
use std::char;
@ -39,9 +40,11 @@ const WIDTH: u32 = 640;
const HEIGHT: u32 = 480;
const SCROLL_SPEED: f64 = 6.0;
const SUBPIXEL_GRANULARITY: f32 = 0.25;
const INITIAL_POINT_SIZE: f32 = 24.0;
const MIN_POINT_SIZE: f32 = 6.0;
const MAX_POINT_SIZE: f32 = 256.0;
const MAX_POINT_SIZE: f32 = 512.0;
const FPS_DISPLAY_POINT_SIZE: f32 = 24.0;
const FPS_PADDING: i32 = 6;
@ -151,52 +154,56 @@ fn main() {
let mut outline_builder = OutlineBuilder::new();
let mut glyph_indices = vec![];
let mut glyph_count = 0;
for (_, glyph_id) in glyph_mapping.iter() {
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;
glyph_count += 1
glyph_indices[glyph_id as usize] = glyph_index
}
let outlines = outline_builder.create_buffers().unwrap();
let fps_atlas_origins = renderer.create_fps_atlas(&font, &outlines, glyph_count);
while !window.should_close() {
if dirty {
let events = renderer.redraw(point_size,
&font,
&outlines,
&glyph_indices,
glyph_count,
&glyph_positions,
&device_pixel_size,
&translation);
let redraw_result = renderer.redraw(point_size,
&font,
&outlines,
&glyph_indices,
&glyph_positions,
&device_pixel_size,
&translation);
let mut draw_time = 0u64;
unsafe {
gl::Flush();
gl::GetQueryObjectui64v(events.draw, gl::QUERY_RESULT, &mut draw_time);
let (draw_time, accum_time);
match redraw_result.events {
Some(events) => {
let mut draw_nanos = 0u64;
unsafe {
gl::Flush();
gl::GetQueryObjectui64v(events.draw, gl::QUERY_RESULT, &mut draw_nanos);
}
draw_time = draw_nanos as f64;
accum_time = events.accum.time_elapsed().unwrap() as f64;
}
None => {
draw_time = 0.0;
accum_time = 0.0;
}
}
let draw_time = draw_time as f64;
let accum_time = events.accum.time_elapsed().unwrap() as f64;
let timing = renderer.get_timing_in_ms();
renderer.draw_fps(&font,
&outlines,
&device_pixel_size,
&fps_atlas_origins,
&glyph_indices,
&glyph_mapping,
draw_time,
accum_time,
timing,
glyph_count);
redraw_result.glyphs_drawn);
window.swap_buffers();
@ -344,7 +351,7 @@ impl Renderer {
gl::VertexAttribPointer(composite_position_attribute as GLuint,
2,
gl::INT,
gl::FLOAT,
gl::FALSE,
mem::size_of::<Vertex>() as GLsizei,
0 as *const GLvoid);
@ -438,26 +445,35 @@ impl Renderer {
font: &Font,
outlines: &Outlines,
glyph_indices: &[u16],
glyph_count: usize,
glyph_positions: &[GlyphPos],
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>)
-> DrawAtlasProfilingEvents {
-> RedrawResult {
let shelf_height = font.shelf_height(point_size);
let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
let atlas_origins: Vec<_> = (0..(glyph_count as u16)).map(|glyph_index| {
atlas_builder.pack_glyph(&outlines, glyph_index, point_size).unwrap()
}).collect();
let cached_glyphs = self.determine_visible_glyphs(&mut atlas_builder,
font,
outlines,
glyph_indices,
glyph_positions,
device_pixel_size,
translation,
point_size);
let atlas = atlas_builder.create_atlas().unwrap();
let rect = Rect::new(Point2D::new(0, 0), self.atlas_size);
let events = self.rasterizer.draw_atlas(&self.main_compute_image,
&rect,
&atlas,
outlines,
&self.main_coverage_buffer).unwrap();
let events = match self.rasterizer.draw_atlas(&self.main_compute_image,
&rect,
&atlas,
outlines,
&self.main_coverage_buffer) {
Ok(events) => Some(events),
Err(RasterError::NoGlyphsToDraw) => None,
Err(error) => panic!("Failed to rasterize atlas: {:?}", error),
};
self.rasterizer.queue().flush().unwrap();
unsafe {
@ -474,19 +490,67 @@ impl Renderer {
gl::Clear(gl::COLOR_BUFFER_BIT);
}
self.draw_glyphs(&font,
outlines,
&self.main_composite_vertex_array,
glyph_indices,
glyph_positions,
&atlas_origins,
device_pixel_size,
translation,
self.main_gl_texture,
point_size,
&TEXT_COLOR);
if events.is_some() {
self.draw_glyphs(&font,
outlines,
&self.main_composite_vertex_array,
glyph_indices,
glyph_positions,
&cached_glyphs,
device_pixel_size,
translation,
self.main_gl_texture,
point_size,
&TEXT_COLOR,
true)
}
events
RedrawResult {
events: events,
glyphs_drawn: cached_glyphs.len() as u32,
}
}
fn determine_visible_glyphs(&self,
atlas_builder: &mut AtlasBuilder,
font: &Font,
outlines: &Outlines,
glyph_indices: &[u16],
glyph_positions: &[GlyphPos],
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>,
point_size: f32)
-> 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);
if let Some(subpixel) = self.subpixel_for_glyph_if_visible(font,
glyph_position,
&glyph_rect,
device_pixel_size,
translation,
point_size) {
glyphs.push((glyph_index, subpixel))
}
}
glyphs.sort();
glyphs.dedup();
glyphs.iter().map(|&(glyph_index, subpixel)| {
let subpixel_offset = (subpixel as f32) / (SUBPIXEL_GRANULARITY as f32);
let origin = atlas_builder.pack_glyph(&outlines,
glyph_index,
point_size,
subpixel_offset).unwrap();
CachedGlyph {
x: origin.x,
y: origin.y,
glyph_index: glyph_index,
subpixel: subpixel,
}
}).collect()
}
fn get_timing_in_ms(&self) -> f64 {
@ -497,18 +561,51 @@ impl Renderer {
}
}
fn subpixel_for_glyph_if_visible(&self,
font: &Font,
glyph_position: &GlyphPos,
glyph_rect: &GlyphSubpixelBounds,
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>,
point_size: f32)
-> Option<u8> {
let pixels_per_unit = point_size / font.units_per_em() as f32;
let viewport: Rect<f32> = Rect::new(Point2D::zero(), device_pixel_size.cast().unwrap());
let glyph_size = glyph_rect.size();
let glyph_x_pos = glyph_position.x as f32 * pixels_per_unit;
let glyph_y_pos = glyph_position.y as f32 * pixels_per_unit - glyph_rect.top as f32;
let subpixel_rect = Rect::new(Point2D::new(glyph_x_pos, glyph_y_pos), glyph_size);
let subpixel_rect = subpixel_rect.translate(&translation.cast().unwrap());
let snapped_x_pos = (subpixel_rect.origin.x / SUBPIXEL_GRANULARITY).round() *
SUBPIXEL_GRANULARITY;
let snapped_y_pos = (subpixel_rect.origin.y / SUBPIXEL_GRANULARITY).round() *
SUBPIXEL_GRANULARITY;
let snapped_origin = Point2D::new(snapped_x_pos, snapped_y_pos);
if Rect::new(snapped_origin, subpixel_rect.size).intersects(&viewport) {
let mut subpixel_fract = snapped_x_pos.fract();
if subpixel_fract < 0.0 {
subpixel_fract += 1.0
}
Some((subpixel_fract / SUBPIXEL_GRANULARITY).round() as u8)
} else {
None
}
}
fn draw_glyphs(&self,
font: &Font,
outlines: &Outlines,
vertex_array: &CompositeVertexArray,
glyph_indices: &[u16],
glyph_positions: &[GlyphPos],
atlas_origins: &[Point2D<f32>],
cached_glyphs: &[CachedGlyph],
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>,
texture: GLuint,
point_size: f32,
color: &[f32]) {
color: &[f32],
use_subpixel_positioning: bool) {
unsafe {
gl::UseProgram(self.composite_program);
gl::BindVertexArray(vertex_array.vertex_array);
@ -519,8 +616,11 @@ impl Renderer {
outlines,
glyph_indices,
glyph_positions,
atlas_origins,
point_size);
cached_glyphs,
device_pixel_size,
translation,
point_size,
use_subpixel_positioning);
gl::ActiveTexture(gl::TEXTURE0);
gl::BindTexture(gl::TEXTURE_RECTANGLE, texture);
@ -558,26 +658,53 @@ impl Renderer {
outlines: &Outlines,
glyph_indices: &[u16],
glyph_positions: &[GlyphPos],
atlas_origins: &[Point2D<f32>],
point_size: f32)
cached_glyphs: &[CachedGlyph],
device_pixel_size: &Size2D<u32>,
translation: &Point2D<i32>,
point_size: f32,
use_subpixel_positioning: bool)
-> usize {
let pixels_per_unit = point_size as f32 / font.units_per_em() as f32;
let pixels_per_granule = point_size / font.units_per_em() as f32 / SUBPIXEL_GRANULARITY;
let (mut vertices, mut indices) = (vec![], vec![]);
for position in glyph_positions {
let glyph_index = glyph_indices[position.glyph_id as usize];
let glyph_rect_i = outlines.glyph_pixel_bounds(glyph_index, point_size);
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 uv_tl: Point2D<u32> = atlas_origins[glyph_index as usize].floor().cast().unwrap();
let subpixel = if use_subpixel_positioning {
match self.subpixel_for_glyph_if_visible(font,
glyph_position,
&glyph_rect,
device_pixel_size,
translation,
point_size) {
None => continue,
Some(subpixel) => subpixel,
}
} else {
0
};
let glyph_rect_i = glyph_rect.round_out();
let bearing_pos = (glyph_position.x as f32 * pixels_per_granule).round() *
SUBPIXEL_GRANULARITY;
let baseline_pos = (glyph_position.y as f32 * pixels_per_granule).round() *
SUBPIXEL_GRANULARITY;
let cached_glyph_index = cached_glyphs.binary_search_by(|cached_glyph| {
(cached_glyph.glyph_index, cached_glyph.subpixel).cmp(&(glyph_index, subpixel))
}).expect("Didn't cache the glyph properly!");
let cached_glyph = cached_glyphs[cached_glyph_index];
let uv_tl: Point2D<u32> = Point2D::new(cached_glyph.x,
cached_glyph.y).floor().cast().unwrap();
let uv_br = uv_tl + glyph_rect_i.size().cast().unwrap();
let bearing_pos = (position.x as f32 * pixels_per_unit).round() as i32;
let baseline_pos = (position.y as f32 * pixels_per_unit).round() as i32;
let left_pos = bearing_pos + glyph_rect_i.left;
let top_pos = baseline_pos - glyph_rect_i.top;
let right_pos = bearing_pos + glyph_rect_i.right;
let bottom_pos = baseline_pos - glyph_rect_i.bottom;
let left_pos = bearing_pos + glyph_rect_i.left as f32;
let top_pos = baseline_pos - glyph_rect_i.top as f32;
let right_pos = bearing_pos + glyph_rect_i.right as f32;
let bottom_pos = baseline_pos - glyph_rect_i.bottom as f32;
let first_index = vertices.len() as u16;
@ -603,36 +730,16 @@ impl Renderer {
indices.len()
}
fn create_fps_atlas(&self, font: &Font, outlines: &Outlines, glyph_count: usize)
-> Vec<Point2D<f32>> {
let shelf_height = font.shelf_height(FPS_DISPLAY_POINT_SIZE);
let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
let atlas_origins: Vec<_> = (0..(glyph_count as u16)).map(|glyph_index| {
atlas_builder.pack_glyph(&outlines, glyph_index, FPS_DISPLAY_POINT_SIZE).unwrap()
}).collect();
let atlas = atlas_builder.create_atlas().unwrap();
self.rasterizer.draw_atlas(&self.fps_compute_image,
&Rect::new(Point2D::new(0, 0), self.atlas_size),
&atlas,
outlines,
&self.fps_coverage_buffer).unwrap();
atlas_origins
}
fn draw_fps(&self,
font: &Font,
outlines: &Outlines,
device_pixel_size: &Size2D<u32>,
atlas_origins: &[Point2D<f32>],
glyph_indices: &[u16],
glyph_mapping: &GlyphMapping,
draw_time: f64,
accum_time: f64,
composite_time: f64,
glyph_count: usize) {
glyphs_drawn: u32) {
// Draw the background color.
unsafe {
gl::BindVertexArray(self.solid_color_vertex_array);
@ -665,34 +772,67 @@ impl Renderer {
accum: {:.3}ms ({:.3}us/glyph), \
composite: {:.3}ms ({:.3}us/glyph)",
draw_time / 1_000_000.0,
draw_time / (1000.0 * glyph_count as f64),
draw_time / (1000.0 * glyphs_drawn as f64),
accum_time / 1_000_000.0,
accum_time / (1000.0 * glyph_count as f64),
accum_time / (1000.0 * glyphs_drawn as f64),
composite_time,
(composite_time * 1000.0) / (glyph_count as f64));
(composite_time * 1000.0) / (glyphs_drawn as f64));
let mut fps_glyphs = vec![];
// TODO(pcwalton): Subpixel positioning for the FPS display.
let (mut fps_glyph_positions, mut fps_glyph_indices) = (vec![], vec![]);
let mut current_x = 0;
for glyph_pos in &shaper::shape_text(&font, &glyph_mapping, &fps_text) {
fps_glyphs.push(GlyphPos {
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 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![];
fps_glyph_indices.sort();
fps_glyph_indices.dedup();
for &fps_glyph_index in &fps_glyph_indices {
let origin = fps_atlas_builder.pack_glyph(&outlines,
fps_glyph_index,
FPS_DISPLAY_POINT_SIZE,
0.0).unwrap();
fps_glyphs.push(CachedGlyph {
x: origin.x,
y: origin.y,
glyph_index: fps_glyph_index,
subpixel: 0,
})
}
let fps_atlas = fps_atlas_builder.create_atlas().unwrap();
let rect = Rect::new(Point2D::new(0, 0), self.atlas_size);
self.rasterizer.draw_atlas(&self.fps_compute_image,
&rect,
&fps_atlas,
outlines,
&self.fps_coverage_buffer).unwrap();
self.rasterizer.queue().flush().unwrap();
self.draw_glyphs(font,
outlines,
&self.fps_composite_vertex_array,
glyph_indices,
&fps_glyph_positions,
&fps_glyphs,
atlas_origins,
device_pixel_size,
&Point2D::new(FPS_PADDING, device_pixel_size.height as i32 - FPS_PADDING),
self.fps_gl_texture,
FPS_DISPLAY_POINT_SIZE,
&FPS_FOREGROUND_COLOR);
&FPS_FOREGROUND_COLOR,
false);
}
fn take_screenshot(&self) {
@ -729,14 +869,14 @@ impl Renderer {
#[derive(Clone, Copy, Debug)]
#[repr(C)]
struct Vertex {
x: i32,
y: i32,
x: f32,
y: f32,
u: u32,
v: u32,
}
impl Vertex {
fn new(x: i32, y: i32, u: u32, v: u32) -> Vertex {
fn new(x: f32, y: f32, u: u32, v: u32) -> Vertex {
Vertex {
x: x,
y: y,
@ -753,6 +893,14 @@ struct GlyphPos {
glyph_id: u16,
}
#[derive(Clone, Copy, Debug)]
struct CachedGlyph {
x: f32,
y: f32,
glyph_index: u16,
subpixel: u8,
}
#[derive(Debug)]
struct CompositeVertexArray {
vertex_array: GLuint,
@ -806,6 +954,8 @@ fn create_image(rasterizer: &Rasterizer, atlas_size: &Size2D<u32>) -> (Image, GL
buffer::Protection::ReadWrite,
&atlas_size).unwrap();
rasterizer.queue().submit_clear(&compute_image, &Color::UInt(0, 0, 0, 0), &[]).unwrap();
let mut gl_texture = 0;
unsafe {
gl::GenTextures(1, &mut gl_texture);
@ -821,6 +971,11 @@ fn create_image(rasterizer: &Rasterizer, atlas_size: &Size2D<u32>) -> (Image, GL
(compute_image, gl_texture)
}
struct RedrawResult {
events: Option<DrawAtlasProfilingEvents>,
glyphs_drawn: u32,
}
static COMPOSITE_VERTEX_SHADER: &'static str = "\
#version 330

View File

@ -40,8 +40,6 @@ layout(std140) uniform ubImageDescriptors {
in ivec2 aPosition;
// Which glyph the vertex belongs to.
//
// TODO(pcwalton): See if this is faster as a binary search on the vertex ID.
in uint aGlyphIndex;
// The vertex ID, passed along onto the TCS.

View File

@ -11,7 +11,7 @@
//! Atlases, which hold rendered glyphs on the GPU.
use error::GlError;
use euclid::{Point2D, Rect, Size2D};
use euclid::Point2D;
use gl::types::{GLenum, GLsizei, GLsizeiptr, GLuint, GLvoid};
use gl;
use outline::Outlines;
@ -61,16 +61,25 @@ impl AtlasBuilder {
///
/// Returns the subpixel origin of the glyph in the atlas if successful or an error if there is
/// no space left for the glyph.
pub fn pack_glyph(&mut self, outlines: &Outlines, glyph_index: u16, point_size: f32)
pub fn pack_glyph(&mut self,
outlines: &Outlines,
glyph_index: u16,
point_size: f32,
horizontal_offset: f32)
-> Result<Point2D<f32>, ()> {
let pixel_bounds = outlines.glyph_pixel_bounds(glyph_index, point_size);
let mut subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, point_size);
subpixel_bounds.left += horizontal_offset;
subpixel_bounds.right += horizontal_offset;
let pixel_bounds = subpixel_bounds.round_out();
let atlas_origin = try!(self.rect_packer.pack(&pixel_bounds.size().cast().unwrap()));
for batch_builder in &mut self.batch_builders {
if let Ok(atlas_origin) = batch_builder.add_glyph(outlines,
&atlas_origin,
glyph_index,
point_size) {
point_size,
horizontal_offset) {
return Ok(atlas_origin)
}
}
@ -79,13 +88,14 @@ impl AtlasBuilder {
let atlas_origin = try!(batch_builder.add_glyph(outlines,
&atlas_origin,
glyph_index,
point_size));
point_size,
horizontal_offset));
self.batch_builders.push(batch_builder);
Ok(atlas_origin)
}
/// Creates an atlas by uploading the atlas info to the GPU.
pub fn create_atlas(mut self) -> Result<Atlas, GlError> {
pub fn create_atlas(self) -> Result<Atlas, GlError> {
let mut batches = vec![];
for batch_builder in self.batch_builders.into_iter() {
batches.push(try!(batch_builder.create_batch()))
@ -116,11 +126,14 @@ impl BatchBuilder {
outlines: &Outlines,
atlas_origin: &Point2D<u32>,
glyph_index: u16,
point_size: f32)
point_size: f32,
horizontal_offset: f32)
-> Result<Point2D<f32>, ()> {
// Check to see if we're already rendering this glyph.
if let Some(image_descriptor) = self.image_descriptors.get(glyph_index as usize) {
if image_descriptor.point_size == point_size {
let image_index = glyph_index as usize;
if let Some(image_descriptor) = self.image_descriptors.get(image_index) {
if image_descriptor.point_size == point_size &&
self.image_metadata[image_index].horizontal_offset == horizontal_offset {
// Glyph is already present.
return Ok(Point2D::new(image_descriptor.atlas_x, image_descriptor.atlas_y))
} else {
@ -131,28 +144,29 @@ impl BatchBuilder {
let subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, point_size);
let glyph_id = outlines.glyph_id(glyph_index);
let glyph_index = self.image_descriptors.len() as u16;
while self.image_descriptors.len() < glyph_index as usize + 1 {
self.image_descriptors.push(ImageDescriptor::default())
while self.image_descriptors.len() < image_index + 1 {
self.image_descriptors.push(ImageDescriptor::default());
self.image_metadata.push(ImageMetadata::default());
}
while self.image_metadata.len() < glyph_index as usize + 1 {
self.image_metadata.push(ImageMetadata::default())
}
let units_per_em = outlines.glyph_units_per_em(glyph_index) as f32;
let horizontal_px_offset = horizontal_offset / units_per_em * point_size;
let atlas_origin = Point2D::new(atlas_origin.x as f32 + subpixel_bounds.left.fract(),
atlas_origin.y as f32 + 1.0 - subpixel_bounds.top.fract());
self.image_descriptors[glyph_index as usize] = ImageDescriptor {
let atlas_origin = Point2D::new(
atlas_origin.x as f32 + subpixel_bounds.left.fract() + horizontal_px_offset,
atlas_origin.y as f32 + 1.0 - subpixel_bounds.top.fract());
self.image_descriptors[image_index] = ImageDescriptor {
atlas_x: atlas_origin.x,
atlas_y: atlas_origin.y,
point_size: point_size,
glyph_index: glyph_index as f32,
pad: 0.0,
};
self.image_metadata[glyph_index as usize] = ImageMetadata {
self.image_metadata[image_index] = ImageMetadata {
glyph_index: glyph_index as u32,
glyph_id: glyph_id,
horizontal_offset: horizontal_offset,
start_index: outlines.descriptor(glyph_index).unwrap().start_index(),
end_index: match outlines.descriptor(glyph_index + 1) {
Some(descriptor) => descriptor.start_index() as u32,
@ -164,14 +178,13 @@ impl BatchBuilder {
}
/// Uploads this batch data to the GPU.
fn create_batch(mut self) -> Result<Batch, GlError> {
self.image_metadata.sort_by(|a, b| a.glyph_index.cmp(&b.glyph_index));
fn create_batch(self) -> Result<Batch, GlError> {
let (mut current_range, mut counts, mut start_indices) = (None, vec![], vec![]);
for image_metadata in &self.image_metadata {
let glyph_index = image_metadata.glyph_index;
let start_index = image_metadata.start_index;
let end_index = image_metadata.end_index;
let (start_index, end_index) = (image_metadata.start_index, image_metadata.end_index);
if start_index == 0 {
continue
}
match current_range {
Some((current_first, current_last)) if start_index == current_last => {
@ -179,7 +192,7 @@ impl BatchBuilder {
}
Some((current_first, current_last)) => {
counts.push((current_last - current_first) as GLsizei);
start_indices.push(current_first as usize);
start_indices.push((current_first as usize) * mem::size_of::<u32>());
current_range = Some((start_index, end_index))
}
None => current_range = Some((start_index, end_index)),
@ -187,7 +200,7 @@ impl BatchBuilder {
}
if let Some((current_first, current_last)) = current_range {
counts.push((current_last - current_first) as GLsizei);
start_indices.push(current_first as usize);
start_indices.push((current_first as usize) * mem::size_of::<u32>());
}
// TODO(pcwalton): Try using `glMapBuffer` here.
@ -235,6 +248,12 @@ impl Atlas {
pub fn shelf_columns(&self) -> u32 {
self.shelf_columns
}
/// Returns true if this atlas has no glyphs of nonzero size in it.
#[inline]
pub fn is_empty(&self) -> bool {
self.shelf_columns == 0
}
}
struct Batch {
@ -274,7 +293,7 @@ pub struct ImageDescriptor {
atlas_x: f32,
atlas_y: f32,
point_size: f32,
glyph_index: f32,
pad: f32,
}
// Information about each image that we keep around ourselves.
@ -282,8 +301,9 @@ pub struct ImageDescriptor {
#[derive(Clone, Copy, Default, Debug)]
pub struct ImageMetadata {
glyph_index: u32,
glyph_id: u16,
start_index: u32,
end_index: u32,
horizontal_offset: f32,
glyph_id: u16,
}

View File

@ -116,17 +116,6 @@ pub struct GlyphMapping {
ranges: Vec<MappedGlyphRange>,
}
impl GlyphRange {
/// Returns an iterator over every glyph in this range.
#[inline]
fn iter(&self) -> GlyphRangeIter {
GlyphRangeIter {
start: self.start,
end: self.end,
}
}
}
impl GlyphMapping {
#[doc(hidden)]
#[inline]

View File

@ -51,6 +51,8 @@ pub enum InitError {
/// A rasterization error. This could be an OpenGL error or a compute error.
#[derive(Debug)]
pub enum RasterError {
/// No glyphs were supplied.
NoGlyphsToDraw,
/// An OpenGL error occurred.
GlError(GlError),
/// An error occurred during GPU compute.

View File

@ -8,10 +8,9 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
use byteorder::{BigEndian, ReadBytesExt};
use euclid::Point2D;
use otf::glyf::{Point, PointKind};
use otf::head::HeadTable;
use otf::{Error, FontTable};
use outline::GlyphBounds;
use std::cmp;
@ -107,7 +106,7 @@ impl<'a> CffTable<'a> {
4 => {
// |- dy1 vmoveto
close_path_if_necessary(&mut pos, &start, index_in_contour, &mut callback);
close_path_if_necessary(&start, index_in_contour, &mut callback);
pos.y += stack.array[0] as i16;
callback(&Point {
position: pos,
@ -295,9 +294,7 @@ impl<'a> CffTable<'a> {
start = 1
}
for (i, chunk) in stack.array[start..stack.size as usize]
.chunks(4)
.enumerate() {
for chunk in stack.array[start..stack.size as usize].chunks(4) {
add_curve(0, chunk[0] as i16,
chunk[1] as i16, chunk[2] as i16,
0, chunk[3] as i16,
@ -317,9 +314,7 @@ impl<'a> CffTable<'a> {
start = 1
}
for (i, chunk) in stack.array[start..stack.size as usize]
.chunks(4)
.enumerate() {
for chunk in stack.array[start..stack.size as usize].chunks(4) {
add_curve(chunk[0] as i16, 0,
chunk[1] as i16, chunk[2] as i16,
chunk[3] as i16, 0,
@ -364,7 +359,7 @@ impl<'a> CffTable<'a> {
}
21 => {
// |- dx1 dy1 rmoveto
close_path_if_necessary(&mut pos, &start, index_in_contour, &mut callback);
close_path_if_necessary(&start, index_in_contour, &mut callback);
pos = pos + Point2D::new(stack.array[0] as i16, stack.array[1] as i16);
callback(&Point {
position: pos,
@ -377,7 +372,7 @@ impl<'a> CffTable<'a> {
}
22 => {
// |- dx1 hmoveto
close_path_if_necessary(&mut pos, &start, index_in_contour, &mut callback);
close_path_if_necessary(&start, index_in_contour, &mut callback);
pos.x += stack.array[0] as i16;
callback(&Point {
position: pos,
@ -403,7 +398,7 @@ impl<'a> CffTable<'a> {
}
}
close_path_if_necessary(&mut pos, &start, index_in_contour, &mut callback);
close_path_if_necessary(&start, index_in_contour, &mut callback);
Ok(())
}
@ -411,12 +406,12 @@ impl<'a> CffTable<'a> {
// TODO(pcwalton): Compute this at the same time as `for_each_point`, perhaps?
pub fn glyph_bounds(&self, glyph_id: u16) -> Result<GlyphBounds, Error> {
let mut bounds = GlyphBounds::default();
self.for_each_point(glyph_id, |point| {
try!(self.for_each_point(glyph_id, |point| {
bounds.left = cmp::min(bounds.left, point.position.x as i32);
bounds.bottom = cmp::min(bounds.bottom, point.position.y as i32);
bounds.right = cmp::max(bounds.right, point.position.x as i32);
bounds.top = cmp::max(bounds.top, point.position.y as i32);
});
}));
Ok(bounds)
}
}
@ -547,10 +542,7 @@ impl EvaluationStack {
}
}
fn close_path_if_necessary<F>(pos: &mut Point2D<i16>,
start: &Point2D<i16>,
index_in_contour: u16,
mut callback: F)
fn close_path_if_necessary<F>(start: &Point2D<i16>, index_in_contour: u16, mut callback: F)
where F: FnMut(&Point) {
if index_in_contour == 0 {
// No path to close.

View File

@ -11,7 +11,6 @@
use byteorder::{BigEndian, ReadBytesExt};
use charmap::{CodepointRange, GlyphMapping, GlyphRange, MappedGlyphRange};
use otf::{Error, FontTable};
use std::char;
use std::cmp;
use std::mem;
use std::u16;

View File

@ -90,7 +90,7 @@ impl<'a> GlyfTable<'a> {
head_table: &HeadTable,
loca_table: &LocaTable,
glyph_id: u16,
mut callback: F)
callback: F)
-> Result<(), Error> where F: FnMut(&Point) {
let mut reader = self.table.bytes;
@ -154,7 +154,7 @@ impl<'a> GlyfTable<'a> {
let mut last_point_was_off_curve = false;
let mut point_index_in_contour = 0;
for contour_point_index in 0..contour_point_count {
for _ in 0..contour_point_count {
let flags = SimpleFlags::from_bits_truncate(*flag_parser.current);
try!(flag_parser.next());
@ -301,9 +301,9 @@ impl<'a> GlyfTable<'a> {
if let Some(offset) = try!(loca_table.location_of(head_table, glyph_index)) {
let mut reader = self.table.bytes;
try!(reader.jump(offset as usize).map_err(Error::eof));
self.for_each_point_in_simple_glyph(reader, |point| {
try!(self.for_each_point_in_simple_glyph(reader, |point| {
callback(&transform.transform(&point))
});
}));
}
if !flags.contains(MORE_COMPONENTS) {

View File

@ -24,7 +24,6 @@ bitflags! {
#[derive(Clone, Copy)]
pub struct KernTable<'a> {
table: FontTable<'a>,
horizontal_table: &'a [u8],
}
@ -38,7 +37,7 @@ impl<'a> KernTable<'a> {
let n_tables = try!(kern_reader.read_u16::<BigEndian>().map_err(Error::eof));
let mut horizontal_table = None;
for table_index in 0..n_tables {
for _ in 0..n_tables {
let mut table_reader = kern_reader;
let _version = try!(table_reader.read_u16::<BigEndian>().map_err(Error::eof));
let length = try!(table_reader.read_u16::<BigEndian>().map_err(Error::eof));
@ -58,7 +57,6 @@ impl<'a> KernTable<'a> {
match horizontal_table {
Some(horizontal_table) => {
Ok(KernTable {
table: table,
horizontal_table: horizontal_table,
})
}

View File

@ -138,9 +138,9 @@ impl<'a> Font<'a> {
///
/// Returns the font on success or an error on failure.
pub fn from_collection_index<'b>(bytes: &'b [u8], index: u32) -> Result<Font<'b>, Error> {
// Check magic number.
// Check the magic number.
let mut reader = bytes;
let mut magic_number = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
match magic_number {
TTCF => {
// This is a font collection. Read the first font.
@ -171,10 +171,8 @@ impl<'a> Font<'a> {
let mut reader = bytes;
try!(reader.jump(offset as usize).map_err(Error::eof));
let mut magic_number = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
// Check version.
if !SFNT_VERSIONS.contains(&magic_number) {
// Check the magic number.
if !SFNT_VERSIONS.contains(&try!(reader.read_u32::<BigEndian>().map_err(Error::eof))) {
return Err(Error::UnknownFormat)
}
@ -252,8 +250,8 @@ impl<'a> Font<'a> {
// Read the Mac resource file header.
let resource_data_offset = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
let resource_map_offset = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
let resource_data_size = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
let resource_map_size = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
let _resource_data_size = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
let _resource_map_size = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
// Move to the fields we care about in the resource map.
reader = bytes;
@ -262,7 +260,7 @@ impl<'a> Font<'a> {
// Read the type list and name list offsets.
let type_list_offset = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let name_list_offset = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let _name_list_offset = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
// Move to the type list.
reader = bytes;
@ -272,7 +270,7 @@ impl<'a> Font<'a> {
// Find the 'sfnt' type.
let type_count = (try!(reader.read_i16::<BigEndian>().map_err(Error::eof)) + 1) as usize;
let mut resource_count_and_list_offset = None;
for type_index in 0..type_count {
for _ in 0..type_count {
let type_id = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
let resource_count = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let resource_list_offset = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
@ -302,11 +300,11 @@ impl<'a> Font<'a> {
// Find the font we're interested in.
try!(reader.jump(index as usize * (mem::size_of::<u16>() * 2 + mem::size_of::<u32>() * 2))
.map_err(Error::eof));
let sfnt_id = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let sfnt_name_offset = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let _sfnt_id = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let _sfnt_name_offset = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let sfnt_data_offset = try!(reader.read_u32::<BigEndian>().map_err(Error::eof)) &
0x00ffffff;
let sfnt_ptr = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
let _sfnt_ptr = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
// Load the resource.
reader = bytes;

View File

@ -191,17 +191,17 @@ impl Outlines {
self.descriptors[glyph_index as usize].subpixel_bounds(point_size)
}
/// Returns the boundaries of the glyph, rounded out to the nearest pixel.
#[inline]
pub fn glyph_pixel_bounds(&self, glyph_index: u16, point_size: f32) -> GlyphPixelBounds {
self.descriptors[glyph_index as usize].subpixel_bounds(point_size).round_out()
}
/// Returns the ID of the glyph with the given index.
#[inline]
pub fn glyph_id(&self, glyph_index: u16) -> u16 {
self.descriptors[glyph_index as usize].glyph_id
}
/// Returns the units per em for the glyph with the given index.
#[inline]
pub fn glyph_units_per_em(&self, glyph_index: u16) -> u32 {
self.descriptors[glyph_index as usize].units_per_em
}
}
#[doc(hidden)]

View File

@ -237,8 +237,16 @@ impl Rasterizer {
outlines: &Outlines,
coverage_buffer: &CoverageBuffer)
-> Result<DrawAtlasProfilingEvents, RasterError> {
if atlas.is_empty() {
return Err(RasterError::NoGlyphsToDraw)
}
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, coverage_buffer.framebuffer());
// Save the old viewport so we can restore it later.
let mut old_viewport: [GLint; 4] = [0; 4];
gl::GetIntegerv(gl::VIEWPORT, old_viewport.as_mut_ptr());
gl::Viewport(0, 0, rect.size.width as GLint, rect.size.height as GLint);
// TODO(pcwalton): Scissor to the image rect to clear faster?
@ -302,7 +310,9 @@ impl Rasterizer {
gl::Disable(gl::CULL_FACE);
gl::Disable(gl::BLEND);
// Restore our old framebuffer and viewport.
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::Viewport(old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3]);
// FIXME(pcwalton): We should have some better synchronization here if we're using
// OpenCL, but I don't know how to do that portably (i.e. on Mac…) Just using