wip
This commit is contained in:
parent
3a5069b1e7
commit
a2522e3845
|
@ -14,6 +14,8 @@
|
|||
/demo/server/Rocket.toml
|
||||
.DS_Store
|
||||
target
|
||||
node_modules
|
||||
.cache
|
||||
|
||||
# Editors
|
||||
*.swp
|
||||
|
|
|
@ -48,6 +48,7 @@ use lyon_path::PathEvent;
|
|||
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
|
||||
use lyon_path::iterator::PathIter;
|
||||
use pathfinder_partitioner::FillRule;
|
||||
use pathfinder_partitioner::mesh::Mesh;
|
||||
use pathfinder_partitioner::mesh_pack::MeshPack;
|
||||
use pathfinder_partitioner::partitioner::Partitioner;
|
||||
use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
|
||||
|
@ -273,31 +274,46 @@ impl PathPartitioningResult {
|
|||
fn compute(pack: &mut MeshPack,
|
||||
path_descriptors: &[PathDescriptor],
|
||||
paths: &[Vec<PathEvent>],
|
||||
approx_tolerance: Option<f32>)
|
||||
approx_tolerance: Option<f32>,
|
||||
tile: bool)
|
||||
-> PathPartitioningResult {
|
||||
let timestamp_before = Instant::now();
|
||||
|
||||
for (path, path_descriptor) in paths.iter().zip(path_descriptors.iter()) {
|
||||
let mut partitioner = Partitioner::new();
|
||||
if let Some(tolerance) = approx_tolerance {
|
||||
partitioner.builder_mut().set_approx_tolerance(tolerance);
|
||||
if !tile {
|
||||
let mut partitioner = Partitioner::new();
|
||||
if let Some(tolerance) = approx_tolerance {
|
||||
partitioner.builder_mut().set_approx_tolerance(tolerance);
|
||||
}
|
||||
|
||||
path.iter().for_each(|event| partitioner.builder_mut().path_event(*event));
|
||||
partitioner.partition(path_descriptor.fill_rule);
|
||||
partitioner.builder_mut().build_and_reset();
|
||||
|
||||
partitioner.mesh_mut().push_stencil_segments(
|
||||
CubicToQuadraticTransformer::new(path.iter().cloned(),
|
||||
CUBIC_TO_QUADRATIC_APPROX_TOLERANCE));
|
||||
partitioner.mesh_mut().push_stencil_normals(
|
||||
CubicToQuadraticTransformer::new(path.iter().cloned(),
|
||||
CUBIC_TO_QUADRATIC_APPROX_TOLERANCE));
|
||||
|
||||
pack.push(partitioner.into_mesh());
|
||||
} else {
|
||||
let tiles = tiling::tile_path(path);
|
||||
for tile_info in tiles {
|
||||
let mut mesh = Mesh::new();
|
||||
mesh.push_stencil_segments(tile_info.events.into_iter());
|
||||
pack.push(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
path.iter().for_each(|event| partitioner.builder_mut().path_event(*event));
|
||||
partitioner.partition(path_descriptor.fill_rule);
|
||||
partitioner.builder_mut().build_and_reset();
|
||||
|
||||
partitioner.mesh_mut().push_stencil_segments(
|
||||
CubicToQuadraticTransformer::new(path.iter().cloned(),
|
||||
CUBIC_TO_QUADRATIC_APPROX_TOLERANCE));
|
||||
partitioner.mesh_mut().push_stencil_normals(
|
||||
CubicToQuadraticTransformer::new(path.iter().cloned(),
|
||||
CUBIC_TO_QUADRATIC_APPROX_TOLERANCE));
|
||||
pack.push(partitioner.into_mesh());
|
||||
}
|
||||
|
||||
let time_elapsed = timestamp_before.elapsed();
|
||||
|
||||
eprintln!("path partitioning time: {}ms",
|
||||
time_elapsed.as_secs() as f64 * 1000.0 +
|
||||
time_elapsed.subsec_nanos() as f64 * 1e-6);
|
||||
|
||||
let mut data_buffer = Cursor::new(vec![]);
|
||||
drop(pack.serialize_into(&mut data_buffer));
|
||||
|
||||
|
@ -452,7 +468,8 @@ fn partition_font(request: Json<PartitionFontRequest>) -> Result<PartitionRespon
|
|||
let path_partitioning_result = PathPartitioningResult::compute(&mut pack,
|
||||
&path_descriptors,
|
||||
&paths,
|
||||
None);
|
||||
None,
|
||||
false);
|
||||
|
||||
// Build the response.
|
||||
let elapsed_ms = path_partitioning_result.elapsed_ms();
|
||||
|
@ -538,7 +555,8 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
|
|||
let path_partitioning_result = PathPartitioningResult::compute(&mut pack,
|
||||
&path_descriptors,
|
||||
&paths,
|
||||
Some(tolerance));
|
||||
Some(tolerance),
|
||||
false);
|
||||
|
||||
// Return the response.
|
||||
let elapsed_ms = path_partitioning_result.elapsed_ms();
|
||||
|
@ -822,3 +840,113 @@ fn main() {
|
|||
static_textures,
|
||||
]).launch();
|
||||
}
|
||||
|
||||
mod tiling {
|
||||
use euclid::{Point2D, Rect, Size2D, Vector2D};
|
||||
use lyon_path::PathEvent;
|
||||
use pathfinder_path_utils::clip::RectClipper;
|
||||
use std::f32;
|
||||
use std::mem;
|
||||
|
||||
const TILE_SIZE: f32 = 8.0;
|
||||
|
||||
pub fn tile_path(path: &[PathEvent]) -> Vec<TileInfo> {
|
||||
let mut tiles = vec![];
|
||||
|
||||
let mut all_points = vec![];
|
||||
for event in path {
|
||||
match *event {
|
||||
PathEvent::MoveTo(point) | PathEvent::LineTo(point) => all_points.push(point),
|
||||
PathEvent::QuadraticTo(point0, point1) => {
|
||||
all_points.extend_from_slice(&[point0, point1])
|
||||
}
|
||||
PathEvent::CubicTo(point0, point1, point2) => {
|
||||
all_points.extend_from_slice(&[point0, point1, point2])
|
||||
}
|
||||
PathEvent::Arc(..) | PathEvent::Close => {}
|
||||
}
|
||||
}
|
||||
|
||||
let bounding_rect = Rect::from_points(all_points);
|
||||
//eprintln!("path {}: bounding rect = {:?}", path_index, bounding_rect);
|
||||
|
||||
let tile_size = Size2D::new(TILE_SIZE, TILE_SIZE);
|
||||
let (mut full_tile_count, mut tile_count) = (0, 0);
|
||||
let mut y = bounding_rect.origin.y - bounding_rect.origin.y % TILE_SIZE;
|
||||
|
||||
loop {
|
||||
let mut x = bounding_rect.origin.x - bounding_rect.origin.x % TILE_SIZE;
|
||||
loop {
|
||||
let origin = Point2D::new(x, y);
|
||||
let clipper = RectClipper::new(&Rect::new(origin, tile_size), path);
|
||||
let mut tile_path = clipper.clip();
|
||||
simplify_path(&mut tile_path);
|
||||
translate_path(&mut tile_path, &Vector2D::new(-x, -y));
|
||||
//eprintln!("({},{}): {:?}", x, y, tile_path);
|
||||
|
||||
if !tile_path.is_empty() {
|
||||
tiles.push(TileInfo {
|
||||
events: tile_path,
|
||||
origin,
|
||||
});
|
||||
|
||||
full_tile_count += 1;
|
||||
} else {
|
||||
tile_count += 1;
|
||||
}
|
||||
|
||||
if x < bounding_rect.max_x() {
|
||||
x += TILE_SIZE;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if y < bounding_rect.max_y() {
|
||||
y += TILE_SIZE;
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tiles
|
||||
}
|
||||
|
||||
fn simplify_path(output: &mut Vec<PathEvent>) {
|
||||
let mut subpath = vec![];
|
||||
for event in mem::replace(output, vec![]) {
|
||||
subpath.push(event);
|
||||
if let PathEvent::Close = event {
|
||||
if subpath.len() > 2 {
|
||||
output.extend_from_slice(&subpath);
|
||||
}
|
||||
subpath.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_path(output: &mut [PathEvent], vector: &Vector2D<f32>) {
|
||||
for event in output {
|
||||
match *event {
|
||||
PathEvent::Close => {}
|
||||
PathEvent::MoveTo(ref mut to) | PathEvent::LineTo(ref mut to) => *to += *vector,
|
||||
PathEvent::QuadraticTo(ref mut cp, ref mut to) => {
|
||||
*cp += *vector;
|
||||
*to += *vector;
|
||||
}
|
||||
PathEvent::CubicTo(ref mut cp0, ref mut cp1, ref mut to) => {
|
||||
*cp0 += *vector;
|
||||
*cp1 += *vector;
|
||||
*to += *vector;
|
||||
}
|
||||
PathEvent::Arc(ref mut center, _, _, _) => *center += *vector,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TileInfo {
|
||||
pub events: Vec<PathEvent>,
|
||||
pub origin: Point2D<f32>,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Pathfinder Demo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="pathfinder.css" />
|
||||
</head>
|
||||
<body>
|
||||
<canvas id=canvas width=640 height=480></canvas>
|
||||
<script src="pathfinder.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,5 @@
|
|||
.tile {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
// pathfinder/demo2/pathfinder.ts
|
||||
//
|
||||
// Copyright © 2018 The Pathfinder Project Developers.
|
||||
//
|
||||
// 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.
|
||||
|
||||
import SVG from "../resources/svg/Ghostscript_Tiger.svg";
|
||||
|
||||
const SVGPath = require('svgpath');
|
||||
|
||||
const TILE_SIZE: number = 16.0;
|
||||
const GLOBAL_OFFSET: Point2D = {x: 400.0, y: 200.0};
|
||||
|
||||
const SVG_NS: string = "http://www.w3.org/2000/svg";
|
||||
|
||||
type Point2D = {x: number, y: number};
|
||||
type Size2D = {width: number, height: number};
|
||||
type Rect = {origin: Point2D, size: Size2D};
|
||||
type Vector3D = {x: number, y: number, z: number};
|
||||
|
||||
type Edge = 'left' | 'top' | 'right' | 'bottom';
|
||||
|
||||
type SVGPath = any;
|
||||
|
||||
class App {
|
||||
private svg: XMLDocument;
|
||||
|
||||
constructor(svg: XMLDocument) {
|
||||
this.svg = svg;
|
||||
}
|
||||
|
||||
run(): void {
|
||||
const svgElement = unwrapNull(this.svg.documentElement).cloneNode(true);
|
||||
document.body.appendChild(svgElement);
|
||||
|
||||
const pathElements = Array.from(document.getElementsByTagName('path'));
|
||||
const tiles: Tile[] = [];
|
||||
|
||||
for (let pathElementIndex = 0;
|
||||
pathElementIndex < 15;
|
||||
pathElementIndex++) {
|
||||
const pathElement = pathElements[pathElementIndex];
|
||||
|
||||
const path = canonicalizePath(SVGPath(unwrapNull(pathElement.getAttribute('d'))));
|
||||
const boundingRect = this.boundingRectOfPath(path);
|
||||
|
||||
//console.log("path " + pathElementIndex, path.toString(), ":", boundingRect);
|
||||
|
||||
let y = boundingRect.origin.y;
|
||||
while (true) {
|
||||
let x = boundingRect.origin.x;
|
||||
while (true) {
|
||||
const tileBounds = {
|
||||
origin: {x, y},
|
||||
size: {width: TILE_SIZE, height: TILE_SIZE},
|
||||
};
|
||||
const tilePath = this.clipPathToRect(path, tileBounds);
|
||||
|
||||
tiles.push(new Tile(pathElementIndex, tilePath, tileBounds.origin));
|
||||
|
||||
if (x >= boundingRect.origin.x + boundingRect.size.width)
|
||||
break;
|
||||
x += TILE_SIZE;
|
||||
}
|
||||
|
||||
if (y >= boundingRect.origin.y + boundingRect.size.height)
|
||||
break;
|
||||
y += TILE_SIZE;
|
||||
}
|
||||
|
||||
for (const tile of tiles) {
|
||||
const newSVG = staticCast(document.createElementNS(SVG_NS, 'svg'), SVGElement);
|
||||
newSVG.setAttribute('class', "tile");
|
||||
newSVG.style.left = (GLOBAL_OFFSET.x + tile.origin.x) + "px";
|
||||
newSVG.style.top = (GLOBAL_OFFSET.y + tile.origin.y) + "px";
|
||||
newSVG.style.width = TILE_SIZE + "px";
|
||||
newSVG.style.height = TILE_SIZE + "px";
|
||||
|
||||
const newPath = document.createElementNS(SVG_NS, 'path');
|
||||
newPath.setAttribute('d',
|
||||
tile.path
|
||||
.translate(-tile.origin.x, -tile.origin.y)
|
||||
.toString());
|
||||
|
||||
let color = "#";
|
||||
for (let i = 0; i < 6; i++)
|
||||
color += Math.floor(Math.random() * 16).toString(16);
|
||||
newPath.setAttribute('fill', color);
|
||||
|
||||
newSVG.appendChild(newPath);
|
||||
document.body.appendChild(newSVG);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.removeChild(svgElement);
|
||||
}
|
||||
|
||||
private clipPathToRect(path: SVGPath, tileBounds: Rect): SVGPath {
|
||||
path = this.clipPathToEdge('left', tileBounds.origin.x, path);
|
||||
path = this.clipPathToEdge('top', tileBounds.origin.y, path);
|
||||
path = this.clipPathToEdge('right', tileBounds.origin.x + tileBounds.size.width, path);
|
||||
path = this.clipPathToEdge('bottom', tileBounds.origin.y + tileBounds.size.height, path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private clipPathToEdge(edge: Edge, edgePos: number, input: SVGPath): SVGPath {
|
||||
let pathStart: Point2D | null = null, from = {x: 0, y: 0}, firstPoint = false;
|
||||
let output: string[][] = [];
|
||||
input.iterate((segment: string[], index: number, x: number, y: number) => {
|
||||
const event = segment[0];
|
||||
let to;
|
||||
switch (event) {
|
||||
case 'M':
|
||||
from = {
|
||||
x: parseFloat(segment[segment.length - 2]),
|
||||
y: parseFloat(segment[segment.length - 1]),
|
||||
};
|
||||
pathStart = from;
|
||||
firstPoint = true;
|
||||
return;
|
||||
case 'Z':
|
||||
if (pathStart == null)
|
||||
return;
|
||||
to = pathStart;
|
||||
break;
|
||||
default:
|
||||
to = {
|
||||
x: parseFloat(segment[segment.length - 2]),
|
||||
y: parseFloat(segment[segment.length - 1]),
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.pointIsInside(edge, edgePos, to)) {
|
||||
if (!this.pointIsInside(edge, edgePos, from)) {
|
||||
this.addLine(this.computeLineIntersection(edge, edgePos, from, to),
|
||||
output,
|
||||
firstPoint);
|
||||
firstPoint = false;
|
||||
}
|
||||
this.addLine(to, output, firstPoint);
|
||||
firstPoint = false;
|
||||
} else if (this.pointIsInside(edge, edgePos, from)) {
|
||||
this.addLine(this.computeLineIntersection(edge, edgePos, from, to),
|
||||
output,
|
||||
firstPoint);
|
||||
firstPoint = false;
|
||||
}
|
||||
|
||||
from = to;
|
||||
|
||||
if (event === 'Z') {
|
||||
output.push(['Z']);
|
||||
pathStart = null;
|
||||
}
|
||||
});
|
||||
|
||||
return SVGPath(output.map(segment => segment.join(" ")).join(" "));
|
||||
}
|
||||
|
||||
private addLine(to: Point2D, output: string[][], firstPoint: boolean) {
|
||||
if (firstPoint)
|
||||
output.push(['M', "" + to.x, "" + to.y]);
|
||||
else
|
||||
output.push(['L', "" + to.x, "" + to.y]);
|
||||
}
|
||||
|
||||
private pointIsInside(edge: Edge, edgePos: number, point: Point2D): boolean {
|
||||
switch (edge) {
|
||||
case 'left': return point.x >= edgePos;
|
||||
case 'top': return point.y >= edgePos;
|
||||
case 'right': return point.x <= edgePos;
|
||||
case 'bottom': return point.y <= edgePos;
|
||||
}
|
||||
}
|
||||
|
||||
private computeLineIntersection(edge: Edge,
|
||||
edgePos: number,
|
||||
startPoint: Point2D,
|
||||
endpoint: Point2D):
|
||||
Point2D {
|
||||
const start = {x: startPoint.x, y: startPoint.y, z: 1.0};
|
||||
const end = {x: endpoint.x, y: endpoint.y, z: 1.0};
|
||||
|
||||
let edgeVector: Vector3D;
|
||||
switch (edge) {
|
||||
case 'left':
|
||||
case 'right':
|
||||
edgeVector = {x: 1.0, y: 0.0, z: -edgePos};
|
||||
break;
|
||||
default:
|
||||
edgeVector = {x: 0.0, y: 1.0, z: -edgePos};
|
||||
break;
|
||||
}
|
||||
|
||||
const intersection = this.cross(this.cross(start, end), edgeVector);
|
||||
return {x: intersection.x / intersection.z, y: intersection.y / intersection.z};
|
||||
}
|
||||
|
||||
private boundingRectOfPath(path: SVGPath): Rect {
|
||||
let minX: number | null = null, minY: number | null = null;
|
||||
let maxX: number | null = null, maxY: number | null = null;
|
||||
path.iterate((segment: string[], index: number, x: number, y: number) => {
|
||||
for (let i = 1; i < segment.length; i += 2) {
|
||||
const x = parseFloat(segment[i]), y = parseFloat(segment[i + 1]);
|
||||
minX = minX == null ? x : Math.min(minX, x);
|
||||
minY = minY == null ? y : Math.min(minY, y);
|
||||
maxX = maxX == null ? x : Math.max(maxX, x);
|
||||
maxY = maxY == null ? y : Math.max(maxY, y);
|
||||
//console.log("x", x, "y", y, "maxX", maxX, "maxY", maxY, "segment", segment);
|
||||
}
|
||||
});
|
||||
if (minX == null || minY == null || maxX == null || maxY == null)
|
||||
return {origin: {x: 0, y: 0}, size: {width: 0, height: 0}};
|
||||
return {origin: {x: minX, y: minY}, size: {width: maxX - minX, height: maxY - minY}};
|
||||
}
|
||||
|
||||
private cross(a: Vector3D, b: Vector3D): Vector3D {
|
||||
return {
|
||||
x: a.y*b.z - a.z*b.y,
|
||||
y: a.z*b.x - a.x*b.z,
|
||||
z: a.x*b.y - a.y*b.x,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Tile {
|
||||
pathIndex: number;
|
||||
path: SVGPath;
|
||||
origin: Point2D;
|
||||
|
||||
constructor(pathIndex: number, path: SVGPath, origin: Point2D) {
|
||||
this.pathIndex = pathIndex;
|
||||
this.path = path;
|
||||
this.origin = origin;
|
||||
}
|
||||
}
|
||||
|
||||
function canonicalizePath(path: SVGPath): SVGPath {
|
||||
return path.abs().iterate((segment: string[], index: number, x: number, y: number) => {
|
||||
if (segment[0] === 'H')
|
||||
return [['L', segment[1], '0']];
|
||||
if (segment[0] === 'V')
|
||||
return [['L', '0', segment[1]]];
|
||||
return [segment];
|
||||
});
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
window.fetch(SVG).then(svg => {
|
||||
svg.text().then(svgText => {
|
||||
const svg = staticCast((new DOMParser).parseFromString(svgText, 'image/svg+xml'),
|
||||
XMLDocument);
|
||||
new App(svg).run();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => main(), false);
|
||||
|
||||
function staticCast<T>(value: any, constructor: { new(...args: any[]): T }): T {
|
||||
if (!(value instanceof constructor))
|
||||
throw new Error("Invalid dynamic cast");
|
||||
return value;
|
||||
}
|
||||
|
||||
function unwrapNull<T>(value: T | null): T {
|
||||
if (value == null)
|
||||
throw new Error("Unexpected null");
|
||||
return value;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "es6"
|
||||
},
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -29,6 +29,7 @@ pub struct Mesh {
|
|||
pub b_boxes: Vec<BBox>,
|
||||
pub stencil_segments: Vec<StencilSegment>,
|
||||
pub stencil_normals: Vec<StencilNormals>,
|
||||
pub tile_metadata: Option<TileMetadata>,
|
||||
}
|
||||
|
||||
impl Mesh {
|
||||
|
@ -43,6 +44,7 @@ impl Mesh {
|
|||
b_boxes: vec![],
|
||||
stencil_segments: vec![],
|
||||
stencil_normals: vec![],
|
||||
tile_metadata: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +57,7 @@ impl Mesh {
|
|||
self.b_boxes.clear();
|
||||
self.stencil_segments.clear();
|
||||
self.stencil_normals.clear();
|
||||
self.tile_metadata = None;
|
||||
}
|
||||
|
||||
pub(crate) fn add_b_vertex(&mut self,
|
||||
|
@ -279,6 +282,12 @@ pub struct StencilNormals {
|
|||
pub to: Vector2D<f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct TileMetadata {
|
||||
pub origin: Point2D<f32>,
|
||||
pub path_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct CornerValues {
|
||||
upper_left: Point2D<f32>,
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
use bincode;
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use mesh::Mesh;
|
||||
use mesh::{Mesh, TileMetadata};
|
||||
use serde::Serialize;
|
||||
use std::io::{self, ErrorKind, Seek, SeekFrom, Write};
|
||||
use std::u32;
|
||||
|
@ -53,6 +53,10 @@ impl MeshPack {
|
|||
try!(write_simple_chunk(writer, b"bbox", &mesh.b_boxes));
|
||||
try!(write_simple_chunk(writer, b"sseg", &mesh.stencil_segments));
|
||||
try!(write_simple_chunk(writer, b"snor", &mesh.stencil_normals));
|
||||
match mesh.tile_metadata {
|
||||
None => try!(write_simple_chunk::<_, TileMetadata>(writer, b"tile", &[])),
|
||||
Some(metadata) => try!(write_simple_chunk(writer, b"tile", &[metadata])),
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
// pathfinder/partitioner/src/clip.rs
|
||||
//
|
||||
// Copyright © 2018 The Pathfinder Project Developers.
|
||||
//
|
||||
// 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 euclid::{Point2D, Rect, Vector3D};
|
||||
use lyon_path::PathEvent;
|
||||
use std::mem;
|
||||
use std::ops::Range;
|
||||
|
||||
pub struct RectClipper<'a> {
|
||||
clip_rect: Rect<f32>,
|
||||
subject: &'a [PathEvent],
|
||||
}
|
||||
|
||||
impl<'a> RectClipper<'a> {
|
||||
pub fn new<'aa>(clip_rect: &Rect<f32>, subject: &'aa [PathEvent]) -> RectClipper<'aa> {
|
||||
RectClipper {
|
||||
clip_rect: *clip_rect,
|
||||
subject,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clip(&self) -> Vec<PathEvent> {
|
||||
let mut output = self.subject.to_vec();
|
||||
self.clip_against(Edge::Left(self.clip_rect.origin.x), &mut output);
|
||||
self.clip_against(Edge::Top(self.clip_rect.origin.y), &mut output);
|
||||
self.clip_against(Edge::Right(self.clip_rect.max_x()), &mut output);
|
||||
self.clip_against(Edge::Bottom(self.clip_rect.max_y()), &mut output);
|
||||
output
|
||||
}
|
||||
|
||||
fn clip_against(&self, edge: Edge, output: &mut Vec<PathEvent>) {
|
||||
let (mut from, mut path_start, mut first_point) = (Point2D::zero(), None, false);
|
||||
let input = mem::replace(output, vec![]);
|
||||
for event in input {
|
||||
let to = match event {
|
||||
PathEvent::MoveTo(to) => {
|
||||
path_start = Some(to);
|
||||
from = to;
|
||||
first_point = true;
|
||||
continue
|
||||
}
|
||||
PathEvent::Close => {
|
||||
match path_start {
|
||||
None => continue,
|
||||
Some(path_start) => path_start,
|
||||
}
|
||||
}
|
||||
PathEvent::LineTo(to) |
|
||||
PathEvent::QuadraticTo(_, to) |
|
||||
PathEvent::CubicTo(_, _, to) => to,
|
||||
PathEvent::Arc(..) => panic!("Arcs unsupported!"),
|
||||
};
|
||||
|
||||
if edge.point_is_inside(&to) {
|
||||
if !edge.point_is_inside(&from) {
|
||||
add_line(&edge.line_intersection(&from, &to), output, &mut first_point);
|
||||
}
|
||||
add_line(&to, output, &mut first_point);
|
||||
} else if edge.point_is_inside(&from) {
|
||||
add_line(&edge.line_intersection(&from, &to), output, &mut first_point);
|
||||
}
|
||||
|
||||
from = to;
|
||||
|
||||
if let PathEvent::Close = event {
|
||||
output.push(PathEvent::Close);
|
||||
path_start = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn add_line(to: &Point2D<f32>, output: &mut Vec<PathEvent>, first_point: &mut bool) {
|
||||
if *first_point {
|
||||
output.push(PathEvent::MoveTo(*to));
|
||||
*first_point = false;
|
||||
} else {
|
||||
output.push(PathEvent::LineTo(*to));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Edge {
|
||||
Left(f32),
|
||||
Top(f32),
|
||||
Right(f32),
|
||||
Bottom(f32),
|
||||
}
|
||||
|
||||
impl Edge {
|
||||
fn point_is_inside(&self, point: &Point2D<f32>) -> bool {
|
||||
match *self {
|
||||
Edge::Left(x_edge) => point.x >= x_edge,
|
||||
Edge::Top(y_edge) => point.y >= y_edge,
|
||||
Edge::Right(x_edge) => point.x <= x_edge,
|
||||
Edge::Bottom(y_edge) => point.y <= y_edge,
|
||||
}
|
||||
}
|
||||
|
||||
fn line_intersection(&self, start_point: &Point2D<f32>, endpoint: &Point2D<f32>)
|
||||
-> Point2D<f32> {
|
||||
let start_point = Vector3D::new(start_point.x, start_point.y, 1.0);
|
||||
let endpoint = Vector3D::new(endpoint.x, endpoint.y, 1.0);
|
||||
let edge = match *self {
|
||||
Edge::Left(x_edge) | Edge::Right(x_edge) => Vector3D::new(1.0, 0.0, -x_edge),
|
||||
Edge::Top(y_edge) | Edge::Bottom(y_edge) => Vector3D::new(0.0, 1.0, -y_edge),
|
||||
};
|
||||
let intersection = start_point.cross(endpoint).cross(edge);
|
||||
Point2D::new(intersection.x / intersection.z, intersection.y / intersection.z)
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ extern crate lyon_path;
|
|||
use lyon_path::geom as lyon_geom;
|
||||
use lyon_path::geom::euclid;
|
||||
|
||||
pub mod clip;
|
||||
pub mod cubic_to_quadratic;
|
||||
pub mod normals;
|
||||
pub mod orientation;
|
||||
|
|
Loading…
Reference in New Issue