Allow multiple glyphs to be rendered simultaneously

This commit is contained in:
Patrick Walton 2017-08-19 16:34:02 -07:00
parent 9b3f9d029c
commit 93277c7c11
6 changed files with 196 additions and 65 deletions

View File

@ -9,10 +9,15 @@
"author": "Patrick Walton <pcwalton@mimiga.net>",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@types/base64-js": "^1.2.5",
"@types/gl-matrix": "^2.2.34",
"@types/lodash": "^4.14.73",
"@types/node": "^8.0.19",
"@types/opentype.js": "0.0.0",
"base64-js": "^1.2.1",
"bootstrap": "^4.0.0-alpha.6",
"gl-matrix": "^2.4.0",
"lodash": "^4.17.4",
"opentype.js": "^0.7.3",
"ts-loader": "^2.3.2",
"typescript": "^2.4.2",

View File

@ -2,11 +2,12 @@
//
// Copyright © 2017 Mozilla Foundation
const base64js = require('base64-js');
const glmatrix = require('gl-matrix');
const opentype = require('opentype.js');
import * as _ from 'lodash';
import * as base64js from 'base64-js';
import * as glmatrix from 'gl-matrix';
import * as opentype from 'opentype.js';
const TEXT: string = "G";
const TEXT: string = "Lorem ipsum dolor sit amet";
const FONT_SIZE: number = 16.0;
const SCALE_FACTOR: number = 1.0 / 100.0;
@ -73,6 +74,8 @@ interface UnlinkedShaderProgram {
type Matrix4D = Float32Array;
type Rect = Float32Array;
interface Point2D {
x: number;
y: number;
@ -316,7 +319,7 @@ class PathfinderMeshData implements Meshes<ArrayBuffer> {
throw new PathfinderError("Failed to partition the font!");
const meshes = response.Ok;
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>)
this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer;
this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer as ArrayBuffer;
this.bQuadCount = this.bQuads.byteLength / B_QUAD_SIZE;
this.edgeUpperLineIndexCount = this.edgeUpperLineIndices.byteLength / 8;
@ -407,16 +410,40 @@ class AppController {
fontLoaded() {
this.font = opentype.parse(this.fontData);
if (!this.font.supported)
if (!(this.font as any).supported)
throw new PathfinderError("The font type is unsupported.");
const glyphIDs = this.font.stringToGlyphs(TEXT).map((glyph: any) => glyph.index);
this.glyphs = this.font.stringToGlyphs(TEXT).map(glyph => new PathfinderGlyph(glyph));
this.glyphs.sort((a, b) => a.index() - b.index());
this.glyphs = _.sortedUniqBy(this.glyphs, glyph => glyph.index());
// Lay out in the atlas.
let atlasWidth = 0, atlasHeight = 0;
for (const glyph of this.glyphs) {
const metrics = glyph.metrics();
const width = metrics.xMax - metrics.xMin;
const height = metrics.yMax - metrics.yMin;
atlasHeight = Math.max(atlasHeight, height);
const newAtlasWidth = atlasWidth + width;
glyph.setAtlasLocation(new Float32Array([atlasWidth, 0, newAtlasWidth, height]));
atlasWidth = newAtlasWidth;
}
// Build the partitioning request to the server.
const request = {
otf: base64js.fromByteArray(new Uint8Array(this.fontData)),
fontIndex: 0,
glyphIDs: glyphIDs,
pointSize: FONT_SIZE,
glyphs: this.glyphs.map(glyph => {
const atlasLocation = glyph.getAtlasLocation();
const metrics = glyph.metrics();
const tX = atlasLocation[0] - metrics.xMin;
const tY = atlasLocation[1] - metrics.yMin;
return {
id: glyph.index(),
transform: [1, 0, 0, 1, tX, tY],
};
}),
pointSize: this.font.unitsPerEm,
};
window.fetch(PARTITION_FONT_ENDPOINT_URL, {
@ -445,7 +472,8 @@ class AppController {
aaLevelSelect: HTMLSelectElement;
fpsLabel: HTMLElement;
fontData: ArrayBuffer;
font: any;
font: opentype.Font;
glyphs: Array<PathfinderGlyph>;
meshes: PathfinderMeshData;
}
@ -614,14 +642,14 @@ class PathfinderView {
if (event.ctrlKey) {
// Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel
const scaleFactor = 1.0 - event.deltaY * window.devicePixelRatio * SCALE_FACTOR;
const scaleFactors = new Float32Array([scaleFactor, scaleFactor, 1.0]);
const scaleFactors = new Float32Array([scaleFactor, scaleFactor, 1.0]) as glmatrix.vec3;
glmatrix.mat4.scale(this.transform, this.transform, scaleFactors);
} else {
const delta = new Float32Array([
-event.deltaX * window.devicePixelRatio,
event.deltaY * window.devicePixelRatio,
0.0
]);
0.0,
]) as glmatrix.vec3;
glmatrix.mat4.translate(this.transform, this.transform, delta);
}
@ -821,7 +849,7 @@ class PathfinderView {
quadTexCoordsBuffer: WebGLBuffer;
quadElementsBuffer: WebGLBuffer;
transform: Matrix4D;
transform: glmatrix.mat4;
appController: AppController;
@ -1457,6 +1485,31 @@ interface AntialiasingStrategyTable {
ecaa: typeof ECAAStrategy;
}
class PathfinderGlyph {
constructor(glyph: opentype.Glyph) {
this.glyph = glyph;
}
getAtlasLocation() {
return this.atlasLocation;
}
setAtlasLocation(rect: Rect) {
this.atlasLocation = rect;
}
index(): number {
return (this.glyph as any).index;
}
metrics(): opentype.Metrics {
return this.glyph.getMetrics();
}
glyph: opentype.Glyph;
private atlasLocation: Rect;
}
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy,
ssaa: SSAAStrategy,

View File

@ -3,7 +3,11 @@
"target": "ES2017",
"module": "commonjs",
"types": [
"node"
"base64-js",
"gl-matrix",
"lodash",
"node",
"opentype.js"
],
"typeRoots": [
"node_modules/@types"

View File

@ -22,7 +22,7 @@ extern crate serde_derive;
use app_units::Au;
use bincode::Infinite;
use euclid::{Point2D, Size2D};
use euclid::{Point2D, Size2D, Transform2D};
use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey};
use pathfinder_font_renderer::{GlyphKey, GlyphOutlineBuffer};
use pathfinder_partitioner::partitioner::Partitioner;
@ -43,7 +43,7 @@ static STATIC_JS_JQUERY_PATH: &'static str = "../client/node_modules/jquery/dist
static STATIC_JS_PATHFINDER_JS_PATH: &'static str = "../client/pathfinder.js";
static STATIC_GLSL_PATH: &'static str = "../../shaders";
#[derive(Clone, Copy, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
struct IndexRange {
start: usize,
end: usize,
@ -57,9 +57,7 @@ impl IndexRange {
}
}
fn from_vector_append_and_serialization<T>(dest: &mut Vec<u8>, src: &[T])
-> Result<IndexRange, ()>
where T: Serialize {
fn from_data<T>(dest: &mut Vec<u8>, src: &[T]) -> Result<IndexRange, ()> where T: Serialize {
let byte_len_before = dest.len();
for src_value in src {
try!(bincode::serialize_into(dest, src_value, Infinite).map_err(drop))
@ -78,10 +76,16 @@ struct PartitionFontRequest {
// Base64 encoded.
otf: String,
fontIndex: u32,
glyphIDs: Vec<u32>,
glyphs: Vec<PartitionGlyph>,
pointSize: f64,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
struct PartitionGlyph {
id: u32,
transform: Transform2D<f32>,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
struct PartitionGlyphDimensions {
origin: Point2D<i32>,
@ -89,7 +93,7 @@ struct PartitionGlyphDimensions {
advance: f32,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
struct DecodedOutlineIndices {
endpoint_indices: IndexRange,
control_point_indices: IndexRange,
@ -173,15 +177,18 @@ fn partition_font(request: Json<PartitionFontRequest>)
// Read glyph info.
let mut outline_buffer = GlyphOutlineBuffer::new();
let decoded_outline_indices: Vec<_> = request.glyphIDs.iter().map(|&glyph_id| {
let glyph_key = GlyphKey::new(glyph_id);
let decoded_outline_indices: Vec<_> = request.glyphs.iter().map(|glyph| {
let glyph_key = GlyphKey::new(glyph.id);
let first_endpoint_index = outline_buffer.endpoints.len();
let first_control_point_index = outline_buffer.control_points.len();
let first_subpath_index = outline_buffer.subpaths.len();
// This might fail; if so, just leave it blank.
drop(font_context.push_glyph_outline(&font_instance_key, &glyph_key, &mut outline_buffer));
drop(font_context.push_glyph_outline(&font_instance_key,
&glyph_key,
&mut outline_buffer,
&glyph.transform));
let last_endpoint_index = outline_buffer.endpoints.len();
let last_control_point_index = outline_buffer.control_points.len();
@ -202,13 +209,15 @@ fn partition_font(request: Json<PartitionFontRequest>)
let (mut cover_interior_indices, mut cover_curve_indices) = (vec![], vec![]);
let (mut edge_upper_line_indices, mut edge_upper_curve_indices) = (vec![], vec![]);
let (mut edge_lower_line_indices, mut edge_lower_curve_indices) = (vec![], vec![]);
partitioner.init(&outline_buffer.endpoints,
&outline_buffer.control_points,
&outline_buffer.subpaths);
let mut glyph_info = vec![];
for (path_index, (&glyph_id, decoded_outline_indices)) in
request.glyphIDs.iter().zip(decoded_outline_indices.iter()).enumerate() {
let glyph_key = GlyphKey::new(glyph_id);
for (path_index, (&glyph, decoded_outline_indices)) in
request.glyphs.iter().zip(decoded_outline_indices.iter()).enumerate() {
let glyph_key = GlyphKey::new(glyph.id);
let dimensions = match font_context.glyph_dimensions(&font_instance_key, &glyph_key) {
Some(dimensions) => {
@ -231,44 +240,64 @@ fn partition_font(request: Json<PartitionFontRequest>)
decoded_outline_indices.subpath_indices.start as u32,
decoded_outline_indices.subpath_indices.end as u32);
let path_b_quads = partitioner.b_quads();
let path_b_vertex_positions = partitioner.b_vertex_positions();
let path_b_vertex_path_ids = partitioner.b_vertex_path_ids();
let path_b_vertex_loop_blinn_data = partitioner.b_vertex_loop_blinn_data();
let cover_indices = partitioner.cover_indices();
let edge_indices = partitioner.edge_indices();
IndexRange::from_vector_append_and_serialization(&mut b_vertex_positions,
path_b_vertex_positions).unwrap();
IndexRange::from_vector_append_and_serialization(&mut b_vertex_path_ids,
path_b_vertex_path_ids).unwrap();
let positions_start = IndexRange::from_data(&mut b_vertex_positions,
path_b_vertex_positions).unwrap().start as u32;
IndexRange::from_data(&mut b_vertex_path_ids, path_b_vertex_path_ids).unwrap();
let mut path_b_quads = partitioner.b_quads().to_vec();
let mut path_cover_interior_indices = cover_indices.interior_indices.to_vec();
let mut path_cover_curve_indices = cover_indices.curve_indices.to_vec();
let mut path_edge_upper_line_indices = edge_indices.upper_line_indices.to_vec();
let mut path_edge_upper_curve_indices = edge_indices.upper_curve_indices.to_vec();
let mut path_edge_lower_line_indices = edge_indices.lower_line_indices.to_vec();
let mut path_edge_lower_curve_indices = edge_indices.lower_curve_indices.to_vec();
for path_b_quad in &mut path_b_quads {
path_b_quad.offset(positions_start);
}
for path_cover_interior_index in &mut path_cover_interior_indices {
*path_cover_interior_index += positions_start
}
for path_cover_curve_index in &mut path_cover_curve_indices {
*path_cover_curve_index += positions_start
}
for path_edge_upper_line_indices in &mut path_edge_upper_line_indices {
path_edge_upper_line_indices.offset(positions_start);
}
for path_edge_upper_curve_indices in &mut path_edge_upper_curve_indices {
path_edge_upper_curve_indices.offset(positions_start);
}
for path_edge_lower_line_indices in &mut path_edge_lower_line_indices {
path_edge_lower_line_indices.offset(positions_start);
}
for path_edge_lower_curve_indices in &mut path_edge_lower_curve_indices {
path_edge_lower_curve_indices.offset(positions_start);
}
glyph_info.push(PartitionGlyphInfo {
id: glyph_id,
id: glyph.id,
dimensions: dimensions,
bQuadIndices: IndexRange::from_vector_append_and_serialization(&mut b_quads,
path_b_quads).unwrap(),
bVertexIndices: IndexRange::from_vector_append_and_serialization(
&mut b_vertex_loop_blinn_data,
bQuadIndices: IndexRange::from_data(&mut b_quads, &path_b_quads).unwrap(),
bVertexIndices: IndexRange::from_data(&mut b_vertex_loop_blinn_data,
path_b_vertex_loop_blinn_data).unwrap(),
coverInteriorIndices: IndexRange::from_vector_append_and_serialization(
&mut cover_interior_indices,
cover_indices.interior_indices).unwrap(),
coverCurveIndices: IndexRange::from_vector_append_and_serialization(
&mut cover_curve_indices,
cover_indices.curve_indices).unwrap(),
edgeUpperLineIndices: IndexRange::from_vector_append_and_serialization(
&mut edge_upper_line_indices,
edge_indices.upper_line_indices).unwrap(),
edgeUpperCurveIndices: IndexRange::from_vector_append_and_serialization(
&mut edge_upper_curve_indices,
edge_indices.upper_curve_indices).unwrap(),
edgeLowerLineIndices: IndexRange::from_vector_append_and_serialization(
&mut edge_lower_line_indices,
edge_indices.lower_line_indices).unwrap(),
edgeLowerCurveIndices: IndexRange::from_vector_append_and_serialization(
&mut edge_lower_curve_indices,
edge_indices.lower_curve_indices).unwrap(),
coverInteriorIndices: IndexRange::from_data(&mut cover_interior_indices,
&path_cover_interior_indices).unwrap(),
coverCurveIndices: IndexRange::from_data(&mut cover_curve_indices,
&path_cover_curve_indices).unwrap(),
edgeUpperLineIndices: IndexRange::from_data(&mut edge_upper_line_indices,
&path_edge_upper_line_indices).unwrap(),
edgeUpperCurveIndices: IndexRange::from_data(&mut edge_upper_curve_indices,
&path_edge_upper_curve_indices).unwrap(),
edgeLowerLineIndices: IndexRange::from_data(&mut edge_lower_line_indices,
&path_edge_lower_line_indices).unwrap(),
edgeLowerCurveIndices: IndexRange::from_data(&mut edge_lower_curve_indices,
&path_edge_lower_curve_indices).unwrap(),
})
}

View File

@ -13,7 +13,7 @@ extern crate log;
extern crate env_logger;
use app_units::Au;
use euclid::{Point2D, Size2D};
use euclid::{Point2D, Size2D, Transform2D};
use freetype_sys::{FT_BBox, FT_Done_Face, FT_F26Dot6, FT_Face, FT_GLYPH_FORMAT_OUTLINE};
use freetype_sys::{FT_GlyphSlot, FT_Init_FreeType, FT_Int32, FT_LOAD_TARGET_LIGHT, FT_Library};
use freetype_sys::{FT_Load_Glyph, FT_Long, FT_New_Memory_Face, FT_Outline_Get_CBox};
@ -93,13 +93,15 @@ impl FontContext {
pub fn push_glyph_outline(&self,
font_instance: &FontInstanceKey,
glyph_key: &GlyphKey,
glyph_outline_buffer: &mut GlyphOutlineBuffer)
glyph_outline_buffer: &mut GlyphOutlineBuffer,
transform: &Transform2D<f32>)
-> Result<(), ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
self.push_glyph_outline_from_glyph_slot(font_instance,
glyph_key,
glyph_slot,
glyph_outline_buffer)
glyph_outline_buffer,
transform)
})
}
@ -111,7 +113,8 @@ impl FontContext {
};
unsafe {
FT_Set_Char_Size(face.face, font_instance.size.to_ft_f26dot6(), 0, 0, 0);
let point_size = (font_instance.size.to_f64_px() / 72.0).to_ft_f26dot6();
FT_Set_Char_Size(face.face, point_size, 0, 72, 0);
if FT_Load_Glyph(face.face, glyph_key.glyph_index as FT_UInt, GLYPH_LOAD_FLAGS) != 0 {
return None
@ -174,7 +177,8 @@ impl FontContext {
_: &FontInstanceKey,
_: &GlyphKey,
glyph_slot: FT_GlyphSlot,
glyph_outline_buffer: &mut GlyphOutlineBuffer) {
glyph_outline_buffer: &mut GlyphOutlineBuffer,
transform: &Transform2D<f32>) {
unsafe {
let outline = &(*glyph_slot).outline;
let mut first_point_index = 0 as u32;
@ -189,7 +193,8 @@ impl FontContext {
// FIXME(pcwalton): Does FreeType produce multiple consecutive off-curve points
// in a row like raw TrueType does?
let point = *outline.points.offset(point_index as isize);
let point_position = Point2D::new(point.x as f32, point.y as f32);
let point_position = transform.transform_point(&Point2D::new(point.x as f32,
point.y as f32));
if (*outline.tags.offset(point_index as isize) & FREETYPE_POINT_ON_CURVE) != 0 {
glyph_outline_buffer.endpoints.push(Endpoint {
position: point_position,
@ -303,8 +308,14 @@ trait ToFtF26Dot6 {
fn to_ft_f26dot6(&self) -> FT_F26Dot6;
}
impl ToFtF26Dot6 for f64 {
fn to_ft_f26dot6(&self) -> FT_F26Dot6 {
(*self * 64.0 + 0.5) as FT_F26Dot6
}
}
impl ToFtF26Dot6 for Au {
fn to_ft_f26dot6(&self) -> FT_F26Dot6 {
(self.to_f64_px() * 64.0 + 0.5) as FT_F26Dot6
self.to_f64_px().to_ft_f26dot6()
}
}

View File

@ -55,6 +55,20 @@ impl BQuad {
pad1: 0,
}
}
#[inline]
pub fn offset(&mut self, delta: u32) {
self.upper_left_vertex_index += delta;
self.upper_right_vertex_index += delta;
self.lower_left_vertex_index += delta;
self.lower_right_vertex_index += delta;
if self.upper_control_point_vertex_index < u32::MAX {
self.upper_control_point_vertex_index += delta;
}
if self.lower_control_point_vertex_index < u32::MAX {
self.lower_control_point_vertex_index += delta;
}
}
}
#[repr(C)]
@ -145,6 +159,12 @@ impl LineIndices {
right_vertex_index: right_vertex_index,
}
}
#[inline]
pub fn offset(&mut self, delta: u32) {
self.left_vertex_index += delta;
self.right_vertex_index += delta;
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@ -167,4 +187,13 @@ impl CurveIndices {
pad: 0,
}
}
#[inline]
pub fn offset(&mut self, delta: u32) {
self.left_vertex_index += delta;
self.right_vertex_index += delta;
if self.control_point_vertex_index < u32::MAX {
self.control_point_vertex_index += delta;
}
}
}