wip
This commit is contained in:
parent
3a5069b1e7
commit
a2522e3845
|
@ -14,6 +14,8 @@
|
||||||
/demo/server/Rocket.toml
|
/demo/server/Rocket.toml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
target
|
target
|
||||||
|
node_modules
|
||||||
|
.cache
|
||||||
|
|
||||||
# Editors
|
# Editors
|
||||||
*.swp
|
*.swp
|
||||||
|
|
|
@ -48,6 +48,7 @@ use lyon_path::PathEvent;
|
||||||
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
|
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
|
||||||
use lyon_path::iterator::PathIter;
|
use lyon_path::iterator::PathIter;
|
||||||
use pathfinder_partitioner::FillRule;
|
use pathfinder_partitioner::FillRule;
|
||||||
|
use pathfinder_partitioner::mesh::Mesh;
|
||||||
use pathfinder_partitioner::mesh_pack::MeshPack;
|
use pathfinder_partitioner::mesh_pack::MeshPack;
|
||||||
use pathfinder_partitioner::partitioner::Partitioner;
|
use pathfinder_partitioner::partitioner::Partitioner;
|
||||||
use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
|
use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
|
||||||
|
@ -273,31 +274,46 @@ impl PathPartitioningResult {
|
||||||
fn compute(pack: &mut MeshPack,
|
fn compute(pack: &mut MeshPack,
|
||||||
path_descriptors: &[PathDescriptor],
|
path_descriptors: &[PathDescriptor],
|
||||||
paths: &[Vec<PathEvent>],
|
paths: &[Vec<PathEvent>],
|
||||||
approx_tolerance: Option<f32>)
|
approx_tolerance: Option<f32>,
|
||||||
|
tile: bool)
|
||||||
-> PathPartitioningResult {
|
-> PathPartitioningResult {
|
||||||
let timestamp_before = Instant::now();
|
let timestamp_before = Instant::now();
|
||||||
|
|
||||||
for (path, path_descriptor) in paths.iter().zip(path_descriptors.iter()) {
|
for (path, path_descriptor) in paths.iter().zip(path_descriptors.iter()) {
|
||||||
let mut partitioner = Partitioner::new();
|
if !tile {
|
||||||
if let Some(tolerance) = approx_tolerance {
|
let mut partitioner = Partitioner::new();
|
||||||
partitioner.builder_mut().set_approx_tolerance(tolerance);
|
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();
|
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![]);
|
let mut data_buffer = Cursor::new(vec![]);
|
||||||
drop(pack.serialize_into(&mut data_buffer));
|
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,
|
let path_partitioning_result = PathPartitioningResult::compute(&mut pack,
|
||||||
&path_descriptors,
|
&path_descriptors,
|
||||||
&paths,
|
&paths,
|
||||||
None);
|
None,
|
||||||
|
false);
|
||||||
|
|
||||||
// Build the response.
|
// Build the response.
|
||||||
let elapsed_ms = path_partitioning_result.elapsed_ms();
|
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,
|
let path_partitioning_result = PathPartitioningResult::compute(&mut pack,
|
||||||
&path_descriptors,
|
&path_descriptors,
|
||||||
&paths,
|
&paths,
|
||||||
Some(tolerance));
|
Some(tolerance),
|
||||||
|
false);
|
||||||
|
|
||||||
// Return the response.
|
// Return the response.
|
||||||
let elapsed_ms = path_partitioning_result.elapsed_ms();
|
let elapsed_ms = path_partitioning_result.elapsed_ms();
|
||||||
|
@ -822,3 +840,113 @@ fn main() {
|
||||||
static_textures,
|
static_textures,
|
||||||
]).launch();
|
]).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 b_boxes: Vec<BBox>,
|
||||||
pub stencil_segments: Vec<StencilSegment>,
|
pub stencil_segments: Vec<StencilSegment>,
|
||||||
pub stencil_normals: Vec<StencilNormals>,
|
pub stencil_normals: Vec<StencilNormals>,
|
||||||
|
pub tile_metadata: Option<TileMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mesh {
|
impl Mesh {
|
||||||
|
@ -43,6 +44,7 @@ impl Mesh {
|
||||||
b_boxes: vec![],
|
b_boxes: vec![],
|
||||||
stencil_segments: vec![],
|
stencil_segments: vec![],
|
||||||
stencil_normals: vec![],
|
stencil_normals: vec![],
|
||||||
|
tile_metadata: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +57,7 @@ impl Mesh {
|
||||||
self.b_boxes.clear();
|
self.b_boxes.clear();
|
||||||
self.stencil_segments.clear();
|
self.stencil_segments.clear();
|
||||||
self.stencil_normals.clear();
|
self.stencil_normals.clear();
|
||||||
|
self.tile_metadata = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_b_vertex(&mut self,
|
pub(crate) fn add_b_vertex(&mut self,
|
||||||
|
@ -279,6 +282,12 @@ pub struct StencilNormals {
|
||||||
pub to: Vector2D<f32>,
|
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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct CornerValues {
|
struct CornerValues {
|
||||||
upper_left: Point2D<f32>,
|
upper_left: Point2D<f32>,
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
use bincode;
|
use bincode;
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use mesh::Mesh;
|
use mesh::{Mesh, TileMetadata};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::io::{self, ErrorKind, Seek, SeekFrom, Write};
|
use std::io::{self, ErrorKind, Seek, SeekFrom, Write};
|
||||||
use std::u32;
|
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"bbox", &mesh.b_boxes));
|
||||||
try!(write_simple_chunk(writer, b"sseg", &mesh.stencil_segments));
|
try!(write_simple_chunk(writer, b"sseg", &mesh.stencil_segments));
|
||||||
try!(write_simple_chunk(writer, b"snor", &mesh.stencil_normals));
|
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(())
|
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 as lyon_geom;
|
||||||
use lyon_path::geom::euclid;
|
use lyon_path::geom::euclid;
|
||||||
|
|
||||||
|
pub mod clip;
|
||||||
pub mod cubic_to_quadratic;
|
pub mod cubic_to_quadratic;
|
||||||
pub mod normals;
|
pub mod normals;
|
||||||
pub mod orientation;
|
pub mod orientation;
|
||||||
|
|
Loading…
Reference in New Issue