From 89a2bf54b0f0f4aebba3877559be27fbce3aa3eb Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 26 Jan 2017 18:53:50 -0800 Subject: [PATCH] Add a new example and a toy shaper --- examples/generate-atlas.rs | 6 +- examples/lorem-ipsum.rs | 393 +++++++++++++++++++++++++++++++++++++ src/batch.rs | 95 +++++---- src/charmap.rs | 26 +++ src/glyph_buffer.rs | 6 +- src/glyph_range.rs | 157 +++++++++++++++ src/lib.rs | 2 + src/otf/cmap.rs | 55 ++++-- src/otf/glyf.rs | 4 +- src/otf/head.rs | 1 + src/otf/hhea.rs | 41 ++++ src/otf/hmtx.rs | 60 ++++++ src/otf/loca.rs | 2 +- src/otf/mod.rs | 18 +- src/shaper.rs | 39 ++++ 15 files changed, 821 insertions(+), 84 deletions(-) create mode 100644 examples/lorem-ipsum.rs create mode 100644 src/glyph_range.rs create mode 100644 src/otf/hhea.rs create mode 100644 src/otf/hmtx.rs create mode 100644 src/shaper.rs diff --git a/examples/generate-atlas.rs b/examples/generate-atlas.rs index f878970c..1e6dda43 100644 --- a/examples/generate-atlas.rs +++ b/examples/generate-atlas.rs @@ -16,7 +16,7 @@ use euclid::{Point2D, Rect, Size2D}; use gl::types::{GLint, GLuint}; use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode}; use memmap::{Mmap, Protection}; -use pathfinder::batch::{BatchBuilder, GlyphRange}; +use pathfinder::batch::BatchBuilder; use pathfinder::charmap::CodepointRange; use pathfinder::coverage::CoverageBuffer; use pathfinder::glyph_buffer::GlyphBufferBuilder; @@ -58,7 +58,7 @@ fn main() { let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)]; let glyph_ranges = font.cmap.glyph_ranges_for_codepoint_ranges(&codepoint_ranges).unwrap(); - for (glyph_index, glyph_id) in glyph_ranges.iter().flat_map(GlyphRange::iter).enumerate() { + for (glyph_index, glyph_id) in glyph_ranges.iter().enumerate() { glyph_buffer_builder.add_glyph(&font, glyph_id as u32).unwrap(); batch_builder.add_glyph(&glyph_buffer_builder, glyph_index as u32, POINT_SIZE).unwrap() } @@ -93,9 +93,7 @@ fn main() { gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint); gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint); - } - unsafe { gl::Viewport(0, 0, device_pixel_width, device_pixel_height); gl::ClearColor(1.0, 1.0, 1.0, 1.0); gl::Clear(gl::COLOR_BUFFER_BIT); diff --git a/examples/lorem-ipsum.rs b/examples/lorem-ipsum.rs new file mode 100644 index 00000000..9de02c1d --- /dev/null +++ b/examples/lorem-ipsum.rs @@ -0,0 +1,393 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +extern crate compute_shader; +extern crate euclid; +extern crate gl; +extern crate glfw; +extern crate memmap; +extern crate pathfinder; + +use compute_shader::buffer; +use compute_shader::instance::Instance; +use compute_shader::texture::{ExternalTexture, Format, Texture}; +use euclid::{Point2D, Rect, Size2D}; +use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid}; +use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode}; +use memmap::{Mmap, Protection}; +use pathfinder::batch::BatchBuilder; +use pathfinder::charmap::CodepointRanges; +use pathfinder::coverage::CoverageBuffer; +use pathfinder::glyph_buffer::GlyphBufferBuilder; +use pathfinder::glyph_range::GlyphRanges; +use pathfinder::otf::Font; +use pathfinder::rasterizer::{Rasterizer, RasterizerOptions}; +use pathfinder::shaper::{self, GlyphPos}; +use std::env; +use std::mem; +use std::os::raw::c_void; + +const ATLAS_SIZE: u32 = 1024; +const WIDTH: u32 = 512; +const HEIGHT: u32 = 384; +const UNITS_PER_EM: u32 = 2048; + +const INITIAL_POINT_SIZE: f32 = 24.0; +const MIN_POINT_SIZE: f32 = 6.0; +const MAX_POINT_SIZE: f32 = 400.0; + +static TEXT: &'static str = "Loremipsumdolorsitamet"; + +fn main() { + let mut glfw = glfw::init(glfw::LOG_ERRORS).unwrap(); + glfw.window_hint(WindowHint::ContextVersion(3, 3)); + glfw.window_hint(WindowHint::OpenGlForwardCompat(true)); + glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core)); + let context = glfw.create_window(WIDTH, HEIGHT, "lorem-ipsum", WindowMode::Windowed); + + let (mut window, events) = context.expect("Couldn't create a window!"); + window.make_current(); + window.set_scroll_polling(true); + window.set_size_polling(true); + window.set_framebuffer_size_polling(true); + + gl::load_with(|symbol| window.get_proc_address(symbol) as *const c_void); + + let (width, height) = window.get_framebuffer_size(); + let mut device_pixel_size = Size2D::new(width as u32, height as u32); + + let mut chars: Vec = TEXT.chars().collect(); + chars.sort(); + let codepoint_ranges = CodepointRanges::from_sorted_chars(&chars); + + let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap(); + let (font, glyph_positions, glyph_ranges); + unsafe { + font = Font::new(file.as_slice()).unwrap(); + glyph_ranges = font.cmap + .glyph_ranges_for_codepoint_ranges(&codepoint_ranges.ranges) + .unwrap(); + glyph_positions = shaper::shape_text(&font, &glyph_ranges, TEXT) + } + + let renderer = Renderer::new(); + let mut point_size = INITIAL_POINT_SIZE; + let mut dirty = true; + + while !window.should_close() { + if dirty { + renderer.redraw(&font, + point_size, + &glyph_ranges, + &glyph_positions, + &device_pixel_size); + window.swap_buffers(); + dirty = false + } + + glfw.wait_events(); + for (_, event) in glfw::flush_messages(&events) { + match event { + WindowEvent::Key(Key::Escape, _, Action::Press, _) => { + window.set_should_close(true) + } + WindowEvent::Scroll(_, y) => { + point_size += y as f32; + + if point_size < MIN_POINT_SIZE { + point_size = MIN_POINT_SIZE + } else if point_size > MAX_POINT_SIZE { + point_size = MAX_POINT_SIZE + } + + dirty = true + } + WindowEvent::Size(_, _) | WindowEvent::FramebufferSize(_, _) => { + let (width, height) = window.get_framebuffer_size(); + device_pixel_size = Size2D::new(width as u32, height as u32); + dirty = true + } + _ => {} + } + } + } +} + +struct Renderer { + rasterizer: Rasterizer, + + program: GLuint, + atlas_uniform: GLint, + transform_uniform: GLint, + translation_uniform: GLint, + + vertex_array: GLuint, + vertex_buffer: GLuint, + index_buffer: GLuint, + + atlas_size: Size2D, + + coverage_buffer: CoverageBuffer, + compute_texture: Texture, + gl_texture: GLuint, +} + +impl Renderer { + fn new() -> Renderer { + let instance = Instance::new().unwrap(); + let device = instance.create_device().unwrap(); + let queue = device.create_queue().unwrap(); + + let rasterizer_options = RasterizerOptions::from_env().unwrap(); + let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap(); + + let (program, position_attribute, tex_coord_attribute, atlas_uniform); + let (transform_uniform, translation_uniform); + let (mut vertex_array, mut vertex_buffer, mut index_buffer) = (0, 0, 0); + unsafe { + let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER); + let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER); + gl::ShaderSource(vertex_shader, + 1, + &(VERTEX_SHADER.as_ptr() as *const u8 as *const GLchar), + &(VERTEX_SHADER.len() as GLint)); + gl::ShaderSource(fragment_shader, + 1, + &(FRAGMENT_SHADER.as_ptr() as *const u8 as *const GLchar), + &(FRAGMENT_SHADER.len() as GLint)); + gl::CompileShader(vertex_shader); + gl::CompileShader(fragment_shader); + + program = gl::CreateProgram(); + gl::AttachShader(program, vertex_shader); + gl::AttachShader(program, fragment_shader); + gl::LinkProgram(program); + gl::UseProgram(program); + + position_attribute = gl::GetAttribLocation(program, + "aPosition\0".as_ptr() as *const GLchar); + tex_coord_attribute = gl::GetAttribLocation(program, + "aTexCoord\0".as_ptr() as *const GLchar); + atlas_uniform = gl::GetUniformLocation(program, "uAtlas\0".as_ptr() as *const GLchar); + transform_uniform = gl::GetUniformLocation(program, + "uTransform\0".as_ptr() as *const GLchar); + translation_uniform = + gl::GetUniformLocation(program, "uTranslation\0".as_ptr() as *const GLchar); + + gl::GenVertexArrays(1, &mut vertex_array); + gl::BindVertexArray(vertex_array); + + gl::GenBuffers(1, &mut vertex_buffer); + gl::GenBuffers(1, &mut index_buffer); + + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, index_buffer); + gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer); + + gl::VertexAttribPointer(position_attribute as GLuint, + 2, + gl::UNSIGNED_INT, + gl::FALSE, + mem::size_of::() as GLsizei, + 0 as *const GLvoid); + gl::VertexAttribPointer(tex_coord_attribute as GLuint, + 2, + gl::UNSIGNED_INT, + gl::FALSE, + mem::size_of::() as GLsizei, + (mem::size_of::() * 2) as *const GLvoid); + gl::EnableVertexAttribArray(position_attribute as GLuint); + gl::EnableVertexAttribArray(tex_coord_attribute as GLuint); + } + + // FIXME(pcwalton) + let atlas_size = Size2D::new(ATLAS_SIZE, ATLAS_SIZE); + + let coverage_buffer = CoverageBuffer::new(&rasterizer.device, &atlas_size).unwrap(); + + let compute_texture = rasterizer.device.create_texture(Format::R8, + buffer::Protection::WriteOnly, + &atlas_size).unwrap(); + + let mut gl_texture = 0; + unsafe { + gl::GenTextures(1, &mut gl_texture); + compute_texture.bind_to(&ExternalTexture::Gl(gl_texture)).unwrap(); + + gl::BindTexture(gl::TEXTURE_RECTANGLE, gl_texture); + gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); + gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); + gl::TexParameteri(gl::TEXTURE_RECTANGLE, + gl::TEXTURE_WRAP_S, + gl::CLAMP_TO_EDGE as GLint); + gl::TexParameteri(gl::TEXTURE_RECTANGLE, + gl::TEXTURE_WRAP_T, + gl::CLAMP_TO_EDGE as GLint); + } + + Renderer { + rasterizer: rasterizer, + + program: program, + atlas_uniform: atlas_uniform, + transform_uniform: transform_uniform, + translation_uniform: translation_uniform, + + vertex_array: vertex_array, + vertex_buffer: vertex_buffer, + index_buffer: index_buffer, + + atlas_size: atlas_size, + + coverage_buffer: coverage_buffer, + compute_texture: compute_texture, + gl_texture: gl_texture, + } + } + + fn redraw(&self, + font: &Font, + point_size: f32, + glyph_ranges: &GlyphRanges, + glyph_positions: &[GlyphPos], + device_pixel_size: &Size2D) { + // FIXME(pcwalton) + let shelf_height = (point_size * 2.0).ceil() as u32; + + let mut glyph_buffer_builder = GlyphBufferBuilder::new(); + let mut batch_builder = BatchBuilder::new(device_pixel_size.width, shelf_height); + + for (glyph_index, glyph_id) in glyph_ranges.iter().enumerate() { + glyph_buffer_builder.add_glyph(&font, glyph_id).unwrap(); + batch_builder.add_glyph(&glyph_buffer_builder, glyph_index as u32, point_size).unwrap() + } + + let glyph_buffer = glyph_buffer_builder.finish().unwrap(); + let batch = batch_builder.finish(&glyph_buffer_builder).unwrap(); + + self.rasterizer.draw_atlas(&Rect::new(Point2D::new(0, 0), self.atlas_size), + shelf_height, + &glyph_buffer, + &batch, + &self.coverage_buffer, + &self.compute_texture).unwrap(); + + self.rasterizer.queue.flush().unwrap(); + + unsafe { + gl::UseProgram(self.program); + gl::BindVertexArray(self.vertex_array); + gl::BindBuffer(gl::ARRAY_BUFFER, self.vertex_buffer); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.index_buffer); + + let (mut vertices, mut indices) = (vec![], vec![]); + let mut left_pos = 0; + for position in glyph_positions { + let glyph_index = batch_builder.glyph_index_for(position.glyph_id).unwrap(); + let uv_rect = batch_builder.atlas_rect(glyph_index); + let (uv_bl, uv_tr) = (uv_rect.origin, uv_rect.bottom_right()); + let right_pos = left_pos + uv_rect.size.width; + let bottom_pos = uv_rect.size.height; + + let first_index = vertices.len() as u16; + + vertices.push(Vertex::new(left_pos, 0, uv_bl.x, uv_tr.y)); + vertices.push(Vertex::new(right_pos, 0, uv_tr.x, uv_tr.y)); + vertices.push(Vertex::new(right_pos, bottom_pos, uv_tr.x, uv_bl.y)); + vertices.push(Vertex::new(left_pos, bottom_pos, uv_bl.x, uv_bl.y)); + + indices.extend([0, 1, 3, 1, 2, 3].iter().map(|index| first_index + index)); + + left_pos += ((position.advance as f32 * point_size) / + (UNITS_PER_EM as f32)).ceil() as u32 + } + + gl::BufferData(gl::ARRAY_BUFFER, + (vertices.len() * mem::size_of::()) as GLsizeiptr, + vertices.as_ptr() as *const GLvoid, + gl::STATIC_DRAW); + gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, + (indices.len() * mem::size_of::()) as GLsizeiptr, + indices.as_ptr() as *const GLvoid, + gl::STATIC_DRAW); + + gl::ActiveTexture(gl::TEXTURE0); + gl::BindTexture(gl::TEXTURE_RECTANGLE, self.gl_texture); + gl::Uniform1i(self.atlas_uniform, 0); + + let matrix = [ + 2.0 / device_pixel_size.width as f32, 0.0, + 0.0, 2.0 / device_pixel_size.height as f32, + ]; + gl::UniformMatrix2fv(self.transform_uniform, 1, gl::FALSE, matrix.as_ptr()); + + gl::Uniform2f(self.translation_uniform, + -1.0, + 1.0 - point_size * 2.0 / (device_pixel_size.height as f32)); + + gl::Viewport(0, + 0, + device_pixel_size.width as GLint, + device_pixel_size.height as GLint); + gl::ClearColor(1.0, 1.0, 1.0, 1.0); + gl::Clear(gl::COLOR_BUFFER_BIT); + + gl::DrawElements(gl::TRIANGLES, + indices.len() as GLsizei, + gl::UNSIGNED_SHORT, + 0 as *const GLvoid); + } + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct Vertex { + x: u32, + y: u32, + u: u32, + v: u32, +} + +impl Vertex { + fn new(x: u32, y: u32, u: u32, v: u32) -> Vertex { + Vertex { + x: x, + y: y, + u: u, + v: v, + } + } +} + +static VERTEX_SHADER: &'static str = "\ +#version 330 + +uniform mat2 uTransform; +uniform vec2 uTranslation; + +in vec2 aPosition; +in vec2 aTexCoord; + +out vec2 vTexCoord; + +void main() { + vTexCoord = aTexCoord; + gl_Position = vec4(uTransform * aPosition + uTranslation, 0.0f, 1.0f); +} +"; + +static FRAGMENT_SHADER: &'static str = "\ +#version 330 + +uniform sampler2DRect uAtlas; + +in vec2 vTexCoord; + +out vec4 oFragColor; + +void main() { + float value = 1.0f - texture(uAtlas, vTexCoord).r; + oFragColor = vec4(value, value, value, 1.0f); +} +"; + diff --git a/src/batch.rs b/src/batch.rs index 22774eb4..d7a90d6f 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -9,6 +9,7 @@ // except according to those terms. use atlas::Atlas; +use euclid::{Point2D, Rect, Size2D}; use gl::types::{GLsizei, GLsizeiptr, GLuint}; use gl; use glyph_buffer::GlyphBufferBuilder; @@ -18,8 +19,8 @@ use std::u16; pub struct BatchBuilder { pub atlas: Atlas, - pub images: Vec, - pub glyph_indices: Vec, + pub image_descriptors: Vec, + pub image_metadata: Vec, } impl BatchBuilder { @@ -28,8 +29,8 @@ impl BatchBuilder { pub fn new(available_width: u32, shelf_height: u32) -> BatchBuilder { BatchBuilder { atlas: Atlas::new(available_width, shelf_height), - images: vec![], - glyph_indices: vec![], + image_descriptors: vec![], + image_metadata: vec![], } } @@ -46,27 +47,33 @@ impl BatchBuilder { let pixel_size = descriptor.pixel_rect(point_size).size.ceil().cast().unwrap(); let atlas_origin = try!(self.atlas.place(&pixel_size)); - while self.images.len() < glyph_index as usize + 1 { - self.images.push(ImageDescriptor::default()) + while self.image_descriptors.len() < glyph_index as usize + 1 { + self.image_descriptors.push(ImageDescriptor::default()) } - self.images[glyph_index as usize] = ImageDescriptor { + self.image_descriptors[glyph_index as usize] = ImageDescriptor { atlas_x: atlas_origin.x, atlas_y: atlas_origin.y, point_size: (point_size * 65536.0) as u32, glyph_index: glyph_index, }; - self.glyph_indices.push(glyph_index); + self.image_metadata.push(ImageMetadata { + atlas_size: pixel_size, + glyph_index: glyph_index, + glyph_id: descriptor.glyph_id, + }); Ok(()) } pub fn finish(&mut self, glyph_buffer_builder: &GlyphBufferBuilder) -> Result { - self.glyph_indices.sort(); + self.image_metadata.sort_by(|a, b| a.glyph_index.cmp(&b.glyph_index)); let (mut current_range, mut counts, mut start_indices) = (None, vec![], vec![]); - for &glyph_index in &self.glyph_indices { + for image_metadata in &self.image_metadata { + let glyph_index = image_metadata.glyph_index; + let first_index = glyph_buffer_builder.descriptors[glyph_index as usize].start_index as usize; let last_index = match glyph_buffer_builder.descriptors.get(glyph_index as usize + 1) { @@ -96,11 +103,10 @@ impl BatchBuilder { let mut images = 0; gl::GenBuffers(1, &mut images); + let length = self.image_descriptors.len() * mem::size_of::(); + let ptr = self.image_descriptors.as_ptr() as *const ImageDescriptor as *const c_void; gl::BindBuffer(gl::UNIFORM_BUFFER, images); - gl::BufferData(gl::UNIFORM_BUFFER, - (self.images.len() * mem::size_of::()) as GLsizeiptr, - self.images.as_ptr() as *const ImageDescriptor as *const c_void, - gl::DYNAMIC_DRAW); + gl::BufferData(gl::UNIFORM_BUFFER, length as GLsizeiptr, ptr, gl::DYNAMIC_DRAW); Ok(Batch { start_indices: start_indices, @@ -109,6 +115,21 @@ impl BatchBuilder { }) } } + + #[inline] + pub fn glyph_index_for(&self, glyph_id: u16) -> Option { + match self.image_metadata.binary_search_by(|metadata| metadata.glyph_id.cmp(&glyph_id)) { + Ok(glyph_index) => Some(self.image_metadata[glyph_index].glyph_index), + Err(_) => None, + } + } + + #[inline] + pub fn atlas_rect(&self, glyph_index: u32) -> Rect { + let descriptor = &self.image_descriptors[glyph_index as usize]; + let metadata = &self.image_metadata[glyph_index as usize]; + Rect::new(Point2D::new(descriptor.atlas_x, descriptor.atlas_y), metadata.atlas_size) + } } pub struct Batch { @@ -125,43 +146,7 @@ impl Drop for Batch { } } -#[derive(Clone, Copy, Debug)] -pub struct GlyphRange { - pub start: u16, - pub end: u16, -} - -impl GlyphRange { - #[inline] - pub fn iter(&self) -> GlyphRangeIter { - GlyphRangeIter { - start: self.start, - end: self.end, - } - } -} - -#[derive(Clone)] -pub struct GlyphRangeIter { - start: u16, - end: u16, -} - -impl Iterator for GlyphRangeIter { - type Item = u16; - - #[inline] - fn next(&mut self) -> Option { - if self.start > self.end { - None - } else { - let item = self.start; - self.start += 1; - Some(item) - } - } -} - +/// Information about each image that we send to the GPU. #[repr(C)] #[derive(Clone, Copy, Default, Debug)] pub struct ImageDescriptor { @@ -171,3 +156,11 @@ pub struct ImageDescriptor { glyph_index: u32, } +/// Information about each image that we keep around ourselves. +#[derive(Clone, Copy, Debug)] +pub struct ImageMetadata { + atlas_size: Size2D, + glyph_index: u32, + glyph_id: u16, +} + diff --git a/src/charmap.rs b/src/charmap.rs index d1a04c00..6c34b42f 100644 --- a/src/charmap.rs +++ b/src/charmap.rs @@ -15,6 +15,11 @@ pub struct CodepointRange { pub end: u32, } +#[derive(Clone, Debug)] +pub struct CodepointRanges { + pub ranges: Vec, +} + impl CodepointRange { #[inline] pub fn new(start: u32, end: u32) -> CodepointRange { @@ -33,6 +38,27 @@ impl CodepointRange { } } +impl CodepointRanges { + pub fn from_sorted_chars(chars: &[char]) -> CodepointRanges { + let mut ranges: Vec = vec![]; + for &ch in chars { + match ranges.last_mut() { + Some(ref mut range) if range.end == ch as u32 => continue, + Some(ref mut range) if range.end == ch as u32 + 1 => { + range.end += 1; + continue + } + _ => {} + } + ranges.push(CodepointRange::new(ch as u32, ch as u32)) + } + + CodepointRanges { + ranges: ranges, + } + } +} + pub struct CodepointRangeIter { start: u32, end: u32, diff --git a/src/glyph_buffer.rs b/src/glyph_buffer.rs index ca388b27..1d49e449 100644 --- a/src/glyph_buffer.rs +++ b/src/glyph_buffer.rs @@ -37,7 +37,7 @@ impl GlyphBufferBuilder { } } - pub fn add_glyph(&mut self, font: &Font, glyph_id: u32) -> Result<(), ()> { + pub fn add_glyph(&mut self, font: &Font, glyph_id: u16) -> Result<(), ()> { let glyph_index = self.descriptors.len() as u16; let mut point_index = self.vertices.len() as u32; @@ -78,7 +78,7 @@ impl GlyphBufferBuilder { units_per_em: font.head.units_per_em as u32, start_point: start_point as u32, start_index: start_index, - pad: 0, + glyph_id: glyph_id, }); Ok(()) @@ -137,7 +137,7 @@ pub struct GlyphDescriptor { pub units_per_em: u32, pub start_point: u32, pub start_index: u32, - pub pad: u32, + pub glyph_id: u16, } impl GlyphDescriptor { diff --git a/src/glyph_range.rs b/src/glyph_range.rs new file mode 100644 index 00000000..9a208bcf --- /dev/null +++ b/src/glyph_range.rs @@ -0,0 +1,157 @@ +// 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. + +#[derive(Clone, Copy, Debug)] +pub struct GlyphRange { + pub start: u16, + pub end: u16, +} + +#[derive(Clone, Copy, Debug)] +pub struct MappedGlyphRange { + pub codepoint_start: u32, + pub glyphs: GlyphRange, +} + +#[derive(Clone, Debug)] +pub struct GlyphRanges { + pub ranges: Vec, +} + +impl GlyphRange { + #[inline] + pub fn iter(&self) -> GlyphRangeIter { + GlyphRangeIter { + start: self.start, + end: self.end, + } + } +} + +impl GlyphRanges { + #[inline] + pub fn new() -> GlyphRanges { + GlyphRanges { + ranges: vec![], + } + } + + #[inline] + pub fn iter(&self) -> GlyphRangesIter { + if self.ranges.is_empty() { + return GlyphRangesIter { + start: GlyphRangesIndex { + range_index: 0, + glyph_index: 0, + }, + end: GlyphRangesIndex { + range_index: 0, + glyph_index: 0, + }, + ranges: &self.ranges, + } + } + + GlyphRangesIter { + start: GlyphRangesIndex { + range_index: 0, + glyph_index: self.ranges[0].glyphs.start, + }, + end: GlyphRangesIndex { + range_index: (self.ranges.len() - 1) as u16, + glyph_index: self.ranges.last().unwrap().glyphs.end, + }, + ranges: &self.ranges, + } + } + + pub fn glyph_for(&self, codepoint: u32) -> Option { + let (mut lo, mut hi) = (0, self.ranges.len()); + while lo < hi { + let mid = (lo + hi) / 2; + if codepoint < self.ranges[mid].codepoint_start { + hi = mid + } else if codepoint > self.ranges[mid].codepoint_end() { + lo = mid + 1 + } else { + return Some((codepoint - self.ranges[mid].codepoint_start) as u16 + + self.ranges[mid].glyphs.start) + } + } + None + } +} + +#[derive(Clone)] +pub struct GlyphRangeIter { + start: u16, + end: u16, +} + +impl Iterator for GlyphRangeIter { + type Item = u16; + + #[inline] + fn next(&mut self) -> Option { + if self.start > self.end { + None + } else { + let item = self.start; + self.start += 1; + Some(item) + } + } +} + +#[derive(Clone)] +pub struct GlyphRangesIter<'a> { + start: GlyphRangesIndex, + end: GlyphRangesIndex, + ranges: &'a [MappedGlyphRange], +} + +impl<'a> Iterator for GlyphRangesIter<'a> { + type Item = u16; + + #[inline] + fn next(&mut self) -> Option { + if self.start.range_index > self.end.range_index { + return None + } + + let item = self.start.glyph_index; + + self.start.glyph_index += 1; + while self.start.glyph_index > self.ranges[self.start.range_index as usize].glyphs.end { + 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 + } + + Some(item) + } +} + +#[derive(Clone, Copy, Debug)] +struct GlyphRangesIndex { + range_index: u16, + glyph_index: u16, +} + +impl MappedGlyphRange { + /// Inclusive. + #[inline] + pub fn codepoint_end(&self) -> u32 { + self.codepoint_start + self.glyphs.end as u32 - self.glyphs.start as u32 + } +} + diff --git a/src/lib.rs b/src/lib.rs index 224ded2f..f5845f53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,8 +29,10 @@ pub mod batch; pub mod charmap; pub mod coverage; pub mod glyph_buffer; +pub mod glyph_range; pub mod otf; pub mod rasterizer; +pub mod shaper; mod util; #[cfg(test)] diff --git a/src/otf/cmap.rs b/src/otf/cmap.rs index ed926bd9..9ed484c2 100644 --- a/src/otf/cmap.rs +++ b/src/otf/cmap.rs @@ -8,9 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use batch::GlyphRange; use byteorder::{BigEndian, ReadBytesExt}; use charmap::CodepointRange; +use glyph_range::{GlyphRange, GlyphRanges, MappedGlyphRange}; use otf::FontTable; use std::cmp; use std::mem; @@ -40,7 +40,7 @@ impl<'a> CmapTable<'a> { } pub fn glyph_ranges_for_codepoint_ranges(&self, codepoint_ranges: &[CodepointRange]) - -> Result, ()> { + -> Result { let mut cmap_reader = self.table.bytes; // Check version. @@ -96,16 +96,19 @@ impl<'a> CmapTable<'a> { try!(glyph_ids.jump(seg_count as usize * mem::size_of::())); // Now perform the lookups. - let mut glyph_ranges = vec![]; + let mut glyph_ranges = GlyphRanges::new(); for codepoint_range in codepoint_ranges { let mut codepoint_range = *codepoint_range; while codepoint_range.end >= codepoint_range.start { if codepoint_range.start > u16::MAX as u32 { - codepoint_range.start += 1; - glyph_ranges.push(GlyphRange { - start: MISSING_GLYPH, - end: MISSING_GLYPH, + glyph_ranges.ranges.push(MappedGlyphRange { + codepoint_start: codepoint_range.start, + glyphs: GlyphRange { + start: MISSING_GLYPH, + end: MISSING_GLYPH, + }, }); + codepoint_range.start += 1; continue } @@ -141,11 +144,14 @@ impl<'a> CmapTable<'a> { let segment_index = match segment_index { Some(segment_index) => segment_index, None => { - codepoint_range.start += 1; - glyph_ranges.push(GlyphRange { - start: MISSING_GLYPH, - end: MISSING_GLYPH, + glyph_ranges.ranges.push(MappedGlyphRange { + codepoint_start: codepoint_range.start, + glyphs: GlyphRange { + start: MISSING_GLYPH, + end: MISSING_GLYPH, + }, }); + codepoint_range.start += 1; continue } }; @@ -176,9 +182,12 @@ impl<'a> CmapTable<'a> { // Microsoft's documentation is contradictory as to whether the code offset or // the actual code is added to the ID delta here. In reality it seems to be the // latter. - glyph_ranges.push(GlyphRange { - start: (start_codepoint_range as i16).wrapping_add(id_delta) as u16, - end: (end_codepoint_range as i16).wrapping_add(id_delta) as u16, + glyph_ranges.ranges.push(MappedGlyphRange { + codepoint_start: start_codepoint_range as u32, + glyphs: GlyphRange { + start: (start_codepoint_range as i16).wrapping_add(id_delta) as u16, + end: (end_codepoint_range as i16).wrapping_add(id_delta) as u16, + }, }); continue } @@ -189,15 +198,21 @@ impl<'a> CmapTable<'a> { try!(glyph_id.jump((id_range_offset as usize + code_offset as usize) * 2)); let mut glyph_id = try!(glyph_id.read_u16::().map_err(drop)); if glyph_id == 0 { - glyph_ranges.push(GlyphRange { - start: MISSING_GLYPH, - end: MISSING_GLYPH, + glyph_ranges.ranges.push(MappedGlyphRange { + codepoint_start: start_code as u32 + code_offset as u32, + glyphs: GlyphRange { + start: MISSING_GLYPH, + end: MISSING_GLYPH, + }, }) } else { glyph_id = (glyph_id as i16).wrapping_add(id_delta) as u16; - glyph_ranges.push(GlyphRange { - start: glyph_id, - end: glyph_id, + glyph_ranges.ranges.push(MappedGlyphRange { + codepoint_start: start_code as u32 + code_offset as u32, + glyphs: GlyphRange { + start: glyph_id, + end: glyph_id, + }, }) } } diff --git a/src/otf/glyf.rs b/src/otf/glyf.rs index 1917d5ba..6fd89d43 100644 --- a/src/otf/glyf.rs +++ b/src/otf/glyf.rs @@ -51,7 +51,7 @@ impl<'a> GlyfTable<'a> { pub fn for_each_point(&self, head_table: &HeadTable, loca_table: &LocaTable, - glyph_id: u32, + glyph_id: u16, mut callback: F) -> Result<(), ()> where F: FnMut(&Point) { let mut reader = self.table.bytes; @@ -149,7 +149,7 @@ impl<'a> GlyfTable<'a> { Ok(()) } - pub fn bounding_rect(&self, head_table: &HeadTable, loca_table: &LocaTable, glyph_id: u32) + pub fn bounding_rect(&self, head_table: &HeadTable, loca_table: &LocaTable, glyph_id: u16) -> Result, ()> { let mut reader = self.table.bytes; let offset = try!(loca_table.location_of(head_table, glyph_id)); diff --git a/src/otf/head.rs b/src/otf/head.rs index 0bbe4452..1ca71eb2 100644 --- a/src/otf/head.rs +++ b/src/otf/head.rs @@ -15,6 +15,7 @@ use util::Jump; const MAGIC_NUMBER: u32 = 0x5f0f3cf5; +#[derive(Clone, Debug)] pub struct HeadTable { pub units_per_em: u16, pub index_to_loc_format: i16, diff --git a/src/otf/hhea.rs b/src/otf/hhea.rs new file mode 100644 index 00000000..052582aa --- /dev/null +++ b/src/otf/hhea.rs @@ -0,0 +1,41 @@ +// 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::FontTable; +use std::mem; +use util::Jump; + +#[derive(Clone, Debug)] +pub struct HheaTable { + pub number_of_h_metrics: u16, +} + +impl HheaTable { + pub fn new(table: FontTable) -> Result { + let mut reader = table.bytes; + + // Check the version. + let major_version = try!(reader.read_u16::().map_err(drop)); + let minor_version = try!(reader.read_u16::().map_err(drop)); + if (major_version, minor_version) != (1, 0) { + return Err(()) + } + + // Read the number of `hmtx` entries. + try!(reader.jump(mem::size_of::() * 15)); + let number_of_h_metrics = try!(reader.read_u16::().map_err(drop)); + + Ok(HheaTable { + number_of_h_metrics: number_of_h_metrics, + }) + } +} + diff --git a/src/otf/hmtx.rs b/src/otf/hmtx.rs new file mode 100644 index 00000000..b51f4bc9 --- /dev/null +++ b/src/otf/hmtx.rs @@ -0,0 +1,60 @@ +// 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::FontTable; +use otf::hhea::HheaTable; +use std::mem; +use util::Jump; + +#[derive(Clone, Copy)] +pub struct HmtxTable<'a> { + table: FontTable<'a>, +} + +impl<'a> HmtxTable<'a> { + pub fn new(table: FontTable) -> HmtxTable { + HmtxTable { + table: table, + } + } + + pub fn metrics_for_glyph(&self, hhea_table: &HheaTable, glyph_id: u16) + -> Result { + let mut reader = self.table.bytes; + + // Read the advance width. + let advance_width; + if glyph_id < hhea_table.number_of_h_metrics { + try!(reader.jump(mem::size_of::() * 2 * glyph_id as usize)); + advance_width = try!(reader.read_u16::().map_err(drop)) + } else { + try!(reader.jump(mem::size_of::() * 2 * + (hhea_table.number_of_h_metrics - 1) as usize)); + advance_width = try!(reader.read_u16::().map_err(drop)); + try!(reader.jump(mem::size_of::() * glyph_id as usize)); + } + + // Read the left-side bearing. + let lsb = try!(reader.read_i16::().map_err(drop)); + + Ok(HorizontalMetrics { + advance_width: advance_width, + lsb: lsb, + }) + } +} + +#[derive(Clone, Copy, Default, Debug)] +pub struct HorizontalMetrics { + pub advance_width: u16, + pub lsb: i16, +} + diff --git a/src/otf/loca.rs b/src/otf/loca.rs index 1da46500..31e8051f 100644 --- a/src/otf/loca.rs +++ b/src/otf/loca.rs @@ -24,7 +24,7 @@ impl<'a> LocaTable<'a> { }) } - pub fn location_of(&self, head_table: &HeadTable, glyph_id: u32) -> Result { + pub fn location_of(&self, head_table: &HeadTable, glyph_id: u16) -> Result { let mut reader = self.table.bytes; match head_table.index_to_loc_format { 0 => { diff --git a/src/otf/mod.rs b/src/otf/mod.rs index 229b47ce..73a7834c 100644 --- a/src/otf/mod.rs +++ b/src/otf/mod.rs @@ -12,6 +12,8 @@ use byteorder::{BigEndian, ReadBytesExt}; use otf::cmap::CmapTable; use otf::glyf::GlyfTable; use otf::head::HeadTable; +use otf::hhea::HheaTable; +use otf::hmtx::HmtxTable; use otf::loca::LocaTable; use std::mem; use std::u16; @@ -20,6 +22,8 @@ use util::Jump; pub mod cmap; pub mod glyf; pub mod head; +pub mod hhea; +pub mod hmtx; pub mod loca; const CMAP: u32 = ((b'c' as u32) << 24) | @@ -34,6 +38,10 @@ const HEAD: u32 = ((b'h' as u32) << 24) | ((b'e' as u32) << 16) | ((b'a' as u32) << 8) | (b'd' as u32); +const HHEA: u32 = ((b'h' as u32) << 24) | + ((b'h' as u32) << 16) | + ((b'e' as u32) << 8) | + (b'a' as u32); const HMTX: u32 = ((b'h' as u32) << 24) | ((b'm' as u32) << 16) | ((b't' as u32) << 8) | @@ -48,7 +56,8 @@ pub struct Font<'a> { pub cmap: CmapTable<'a>, pub head: HeadTable, - pub hmtx: FontTable<'a>, + pub hhea: HheaTable, + pub hmtx: HmtxTable<'a>, pub glyf: Option>, pub loca: Option>, @@ -72,7 +81,8 @@ impl<'a> Font<'a> { let num_tables = try!(reader.read_u16::().map_err(drop)); try!(reader.jump(mem::size_of::() * 3)); - let (mut cmap_table, mut head_table, mut hmtx_table) = (None, None, None); + 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); for _ in 0..num_tables { @@ -87,6 +97,7 @@ impl<'a> Font<'a> { let mut slot = match table_id { CMAP => &mut cmap_table, HEAD => &mut head_table, + HHEA => &mut hhea_table, HMTX => &mut hmtx_table, GLYF => &mut glyf_table, LOCA => &mut loca_table, @@ -113,7 +124,8 @@ impl<'a> Font<'a> { cmap: CmapTable::new(try!(cmap_table.ok_or(()))), head: try!(HeadTable::new(try!(head_table.ok_or(())))), - hmtx: try!(hmtx_table.ok_or(())), + hhea: try!(HheaTable::new(try!(hhea_table.ok_or(())))), + hmtx: HmtxTable::new(try!(hmtx_table.ok_or(()))), glyf: glyf_table.map(GlyfTable::new), loca: loca_table, diff --git a/src/shaper.rs b/src/shaper.rs new file mode 100644 index 00000000..42b26a63 --- /dev/null +++ b/src/shaper.rs @@ -0,0 +1,39 @@ +// 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. + +//! A very basic text shaper for simple needs. +//! +//! Do not use this for international or high-quality text. This shaper does not do kerning, +//! ligation, or advanced typography features (`GSUB`, `GPOS`, text morphing). Consider HarfBuzz or +//! the system shaper instead. + +use glyph_range::GlyphRanges; +use otf::Font; + +pub fn shape_text(font: &Font, glyph_ranges: &GlyphRanges, string: &str) -> Vec { + string.chars().map(|ch| { + let glyph_id = glyph_ranges.glyph_for(ch as u32).unwrap_or(0); + let advance = match font.hmtx.metrics_for_glyph(&font.hhea, glyph_id) { + Ok(metrics) => metrics.advance_width, + Err(_) => 0, + }; + GlyphPos { + glyph_id: glyph_id, + advance: advance, + } + }).collect() +} + +#[derive(Clone, Copy, Debug)] +pub struct GlyphPos { + pub glyph_id: u16, + pub advance: u16, +} +