diff --git a/demo/client/package.json b/demo/client/package.json index f3ac72b4..6f9bfbb0 100644 --- a/demo/client/package.json +++ b/demo/client/package.json @@ -9,10 +9,15 @@ "author": "Patrick Walton ", "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", diff --git a/demo/client/src/index.ts b/demo/client/src/index.ts index 0269ec91..9de97bbf 100644 --- a/demo/client/src/index.ts +++ b/demo/client/src/index.ts @@ -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 { throw new PathfinderError("Failed to partition the font!"); const meshes = response.Ok; for (const bufferName of Object.keys(BUFFER_TYPES) as Array>) - 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; 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, diff --git a/demo/client/tsconfig.json b/demo/client/tsconfig.json index 2248ddd1..8f754cb7 100644 --- a/demo/client/tsconfig.json +++ b/demo/client/tsconfig.json @@ -3,7 +3,11 @@ "target": "ES2017", "module": "commonjs", "types": [ - "node" + "base64-js", + "gl-matrix", + "lodash", + "node", + "opentype.js" ], "typeRoots": [ "node_modules/@types" diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index d1a8d903..d3abec69 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -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(dest: &mut Vec, src: &[T]) - -> Result - where T: Serialize { + fn from_data(dest: &mut Vec, src: &[T]) -> Result 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, + glyphs: Vec, pointSize: f64, } +#[derive(Clone, Copy, Serialize, Deserialize)] +struct PartitionGlyph { + id: u32, + transform: Transform2D, +} + #[derive(Clone, Copy, Serialize, Deserialize)] struct PartitionGlyphDimensions { origin: Point2D, @@ -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) // 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) 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) 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, - 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(), + 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_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(), }) } diff --git a/font-renderer/src/lib.rs b/font-renderer/src/lib.rs index b40f884a..c7516191 100644 --- a/font-renderer/src/lib.rs +++ b/font-renderer/src/lib.rs @@ -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) -> 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) { 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 Au { +impl ToFtF26Dot6 for f64 { fn to_ft_f26dot6(&self) -> FT_F26Dot6 { - (self.to_f64_px() * 64.0 + 0.5) as 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().to_ft_f26dot6() } } diff --git a/partitioner/src/lib.rs b/partitioner/src/lib.rs index a91c30cc..699deedd 100644 --- a/partitioner/src/lib.rs +++ b/partitioner/src/lib.rs @@ -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; + } + } }