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

View File

@ -2,11 +2,12 @@
// //
// Copyright © 2017 Mozilla Foundation // Copyright © 2017 Mozilla Foundation
const base64js = require('base64-js'); import * as _ from 'lodash';
const glmatrix = require('gl-matrix'); import * as base64js from 'base64-js';
const opentype = require('opentype.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 FONT_SIZE: number = 16.0;
const SCALE_FACTOR: number = 1.0 / 100.0; const SCALE_FACTOR: number = 1.0 / 100.0;
@ -73,6 +74,8 @@ interface UnlinkedShaderProgram {
type Matrix4D = Float32Array; type Matrix4D = Float32Array;
type Rect = Float32Array;
interface Point2D { interface Point2D {
x: number; x: number;
y: number; y: number;
@ -316,7 +319,7 @@ class PathfinderMeshData implements Meshes<ArrayBuffer> {
throw new PathfinderError("Failed to partition the font!"); throw new PathfinderError("Failed to partition the font!");
const meshes = response.Ok; const meshes = response.Ok;
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>) 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.bQuadCount = this.bQuads.byteLength / B_QUAD_SIZE;
this.edgeUpperLineIndexCount = this.edgeUpperLineIndices.byteLength / 8; this.edgeUpperLineIndexCount = this.edgeUpperLineIndices.byteLength / 8;
@ -407,16 +410,40 @@ class AppController {
fontLoaded() { fontLoaded() {
this.font = opentype.parse(this.fontData); this.font = opentype.parse(this.fontData);
if (!this.font.supported) if (!(this.font as any).supported)
throw new PathfinderError("The font type is unsupported."); 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 = { const request = {
otf: base64js.fromByteArray(new Uint8Array(this.fontData)), otf: base64js.fromByteArray(new Uint8Array(this.fontData)),
fontIndex: 0, fontIndex: 0,
glyphIDs: glyphIDs, glyphs: this.glyphs.map(glyph => {
pointSize: FONT_SIZE, 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, { window.fetch(PARTITION_FONT_ENDPOINT_URL, {
@ -445,7 +472,8 @@ class AppController {
aaLevelSelect: HTMLSelectElement; aaLevelSelect: HTMLSelectElement;
fpsLabel: HTMLElement; fpsLabel: HTMLElement;
fontData: ArrayBuffer; fontData: ArrayBuffer;
font: any; font: opentype.Font;
glyphs: Array<PathfinderGlyph>;
meshes: PathfinderMeshData; meshes: PathfinderMeshData;
} }
@ -614,14 +642,14 @@ class PathfinderView {
if (event.ctrlKey) { if (event.ctrlKey) {
// Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel // Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel
const scaleFactor = 1.0 - event.deltaY * window.devicePixelRatio * SCALE_FACTOR; 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); glmatrix.mat4.scale(this.transform, this.transform, scaleFactors);
} else { } else {
const delta = new Float32Array([ const delta = new Float32Array([
-event.deltaX * window.devicePixelRatio, -event.deltaX * window.devicePixelRatio,
event.deltaY * window.devicePixelRatio, event.deltaY * window.devicePixelRatio,
0.0 0.0,
]); ]) as glmatrix.vec3;
glmatrix.mat4.translate(this.transform, this.transform, delta); glmatrix.mat4.translate(this.transform, this.transform, delta);
} }
@ -821,7 +849,7 @@ class PathfinderView {
quadTexCoordsBuffer: WebGLBuffer; quadTexCoordsBuffer: WebGLBuffer;
quadElementsBuffer: WebGLBuffer; quadElementsBuffer: WebGLBuffer;
transform: Matrix4D; transform: glmatrix.mat4;
appController: AppController; appController: AppController;
@ -1457,6 +1485,31 @@ interface AntialiasingStrategyTable {
ecaa: typeof ECAAStrategy; 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 = { const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy, none: NoAAStrategy,
ssaa: SSAAStrategy, ssaa: SSAAStrategy,

View File

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

View File

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

View File

@ -13,7 +13,7 @@ extern crate log;
extern crate env_logger; extern crate env_logger;
use app_units::Au; 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_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_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}; 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, pub fn push_glyph_outline(&self,
font_instance: &FontInstanceKey, font_instance: &FontInstanceKey,
glyph_key: &GlyphKey, glyph_key: &GlyphKey,
glyph_outline_buffer: &mut GlyphOutlineBuffer) glyph_outline_buffer: &mut GlyphOutlineBuffer,
transform: &Transform2D<f32>)
-> Result<(), ()> { -> Result<(), ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| { self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
self.push_glyph_outline_from_glyph_slot(font_instance, self.push_glyph_outline_from_glyph_slot(font_instance,
glyph_key, glyph_key,
glyph_slot, glyph_slot,
glyph_outline_buffer) glyph_outline_buffer,
transform)
}) })
} }
@ -111,7 +113,8 @@ impl FontContext {
}; };
unsafe { 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 { if FT_Load_Glyph(face.face, glyph_key.glyph_index as FT_UInt, GLYPH_LOAD_FLAGS) != 0 {
return None return None
@ -174,7 +177,8 @@ impl FontContext {
_: &FontInstanceKey, _: &FontInstanceKey,
_: &GlyphKey, _: &GlyphKey,
glyph_slot: FT_GlyphSlot, glyph_slot: FT_GlyphSlot,
glyph_outline_buffer: &mut GlyphOutlineBuffer) { glyph_outline_buffer: &mut GlyphOutlineBuffer,
transform: &Transform2D<f32>) {
unsafe { unsafe {
let outline = &(*glyph_slot).outline; let outline = &(*glyph_slot).outline;
let mut first_point_index = 0 as u32; let mut first_point_index = 0 as u32;
@ -189,7 +193,8 @@ impl FontContext {
// FIXME(pcwalton): Does FreeType produce multiple consecutive off-curve points // FIXME(pcwalton): Does FreeType produce multiple consecutive off-curve points
// in a row like raw TrueType does? // in a row like raw TrueType does?
let point = *outline.points.offset(point_index as isize); 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 { if (*outline.tags.offset(point_index as isize) & FREETYPE_POINT_ON_CURVE) != 0 {
glyph_outline_buffer.endpoints.push(Endpoint { glyph_outline_buffer.endpoints.push(Endpoint {
position: point_position, position: point_position,
@ -303,8 +308,14 @@ trait ToFtF26Dot6 {
fn to_ft_f26dot6(&self) -> FT_F26Dot6; 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 { impl ToFtF26Dot6 for Au {
fn to_ft_f26dot6(&self) -> FT_F26Dot6 { 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, 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)] #[repr(C)]
@ -145,6 +159,12 @@ impl LineIndices {
right_vertex_index: right_vertex_index, 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)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@ -167,4 +187,13 @@ impl CurveIndices {
pad: 0, 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;
}
}
} }