Add a new example and a toy shaper

This commit is contained in:
Patrick Walton 2017-01-26 18:53:50 -08:00
parent 1deaf9136e
commit 89a2bf54b0
15 changed files with 821 additions and 84 deletions

View File

@ -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);

393
examples/lorem-ipsum.rs Normal file
View File

@ -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<char> = 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<u32>,
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::<Vertex>() as GLsizei,
0 as *const GLvoid);
gl::VertexAttribPointer(tex_coord_attribute as GLuint,
2,
gl::UNSIGNED_INT,
gl::FALSE,
mem::size_of::<Vertex>() as GLsizei,
(mem::size_of::<f32>() * 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<u32>) {
// 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::<Vertex>()) as GLsizeiptr,
vertices.as_ptr() as *const GLvoid,
gl::STATIC_DRAW);
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER,
(indices.len() * mem::size_of::<u16>()) 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);
}
";

View File

@ -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<ImageDescriptor>,
pub glyph_indices: Vec<u32>,
pub image_descriptors: Vec<ImageDescriptor>,
pub image_metadata: Vec<ImageMetadata>,
}
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<Batch, ()> {
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::<ImageDescriptor>();
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::<ImageDescriptor>()) 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<u32> {
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<u32> {
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<u16> {
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<u32>,
glyph_index: u32,
glyph_id: u16,
}

View File

@ -15,6 +15,11 @@ pub struct CodepointRange {
pub end: u32,
}
#[derive(Clone, Debug)]
pub struct CodepointRanges {
pub ranges: Vec<CodepointRange>,
}
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<CodepointRange> = 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,

View File

@ -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 {

157
src/glyph_range.rs Normal file
View File

@ -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 <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.
#[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<MappedGlyphRange>,
}
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<u16> {
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<u16> {
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<u16> {
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
}
}

View File

@ -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)]

View File

@ -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<Vec<GlyphRange>, ()> {
-> Result<GlyphRanges, ()> {
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::<u16>()));
// 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 {
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 {
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 {
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::<BigEndian>().map_err(drop));
if glyph_id == 0 {
glyph_ranges.push(GlyphRange {
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 {
glyph_ranges.ranges.push(MappedGlyphRange {
codepoint_start: start_code as u32 + code_offset as u32,
glyphs: GlyphRange {
start: glyph_id,
end: glyph_id,
},
})
}
}

View File

@ -51,7 +51,7 @@ impl<'a> GlyfTable<'a> {
pub fn for_each_point<F>(&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<Rect<i16>, ()> {
let mut reader = self.table.bytes;
let offset = try!(loca_table.location_of(head_table, glyph_id));

View File

@ -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,

41
src/otf/hhea.rs Normal file
View File

@ -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 <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.
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<HheaTable, ()> {
let mut reader = table.bytes;
// Check the version.
let major_version = try!(reader.read_u16::<BigEndian>().map_err(drop));
let minor_version = try!(reader.read_u16::<BigEndian>().map_err(drop));
if (major_version, minor_version) != (1, 0) {
return Err(())
}
// Read the number of `hmtx` entries.
try!(reader.jump(mem::size_of::<u16>() * 15));
let number_of_h_metrics = try!(reader.read_u16::<BigEndian>().map_err(drop));
Ok(HheaTable {
number_of_h_metrics: number_of_h_metrics,
})
}
}

60
src/otf/hmtx.rs Normal file
View File

@ -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 <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.
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<HorizontalMetrics, ()> {
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::<u16>() * 2 * glyph_id as usize));
advance_width = try!(reader.read_u16::<BigEndian>().map_err(drop))
} else {
try!(reader.jump(mem::size_of::<u16>() * 2 *
(hhea_table.number_of_h_metrics - 1) as usize));
advance_width = try!(reader.read_u16::<BigEndian>().map_err(drop));
try!(reader.jump(mem::size_of::<i16>() * glyph_id as usize));
}
// Read the left-side bearing.
let lsb = try!(reader.read_i16::<BigEndian>().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,
}

View File

@ -24,7 +24,7 @@ impl<'a> LocaTable<'a> {
})
}
pub fn location_of(&self, head_table: &HeadTable, glyph_id: u32) -> Result<u32, ()> {
pub fn location_of(&self, head_table: &HeadTable, glyph_id: u16) -> Result<u32, ()> {
let mut reader = self.table.bytes;
match head_table.index_to_loc_format {
0 => {

View File

@ -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<GlyfTable<'a>>,
pub loca: Option<LocaTable<'a>>,
@ -72,7 +81,8 @@ impl<'a> Font<'a> {
let num_tables = try!(reader.read_u16::<BigEndian>().map_err(drop));
try!(reader.jump(mem::size_of::<u16>() * 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,

39
src/shaper.rs Normal file
View File

@ -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 <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.
//! 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<GlyphPos> {
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,
}