Extract attachment points and nesting boxes.

pull/1/head
Atul Varma 2021-02-13 20:47:28 -05:00
rodzic 26d4c721cf
commit f70cdbad8d
4 zmienionych plików z 122 dodań i 93 usunięć

Wyświetl plik

@ -2,6 +2,8 @@ import { Bezier, Point } from "../vendor/bezier-js";
import { SVGProps } from "react";
import type { SvgSymbolElement } from "./vocabulary";
import { flatten, float } from "./util";
import { pathToShapes } from "./path";
export type Bbox = {
minX: number;
@ -17,6 +19,15 @@ export function getBoundingBoxSize(bbox: Bbox): [number, number] {
return [width, height];
}
export function getBoundingBoxCenter(bbox: Bbox): Point {
const [width, height] = getBoundingBoxSize(bbox);
return {
x: bbox.minX + width / 2,
y: bbox.minY + height / 2,
};
}
function dilateBoundingBox(bbox: Bbox, amount: number): Bbox {
return {
minX: bbox.minX - amount,
@ -26,7 +37,7 @@ function dilateBoundingBox(bbox: Bbox, amount: number): Bbox {
};
}
function coalesceBoundingBoxes(bboxes: Bbox[]): Bbox {
export function coalesceBoundingBoxes(bboxes: Bbox[]): Bbox {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
@ -82,85 +93,6 @@ function getBoundingBoxForPoints(points: Point[]): Bbox {
return { minX, minY, maxX, maxY };
}
function float(value: string | number | undefined): number {
if (typeof value === "number") return value;
if (value === undefined) value = "";
const float = parseFloat(value);
if (isNaN(float)) {
throw new Error(`Expected '${value}' to be a float!`);
}
return float;
}
function flatten<T>(arr: T[][]): T[] {
const result: T[] = [];
for (let subarr of arr) {
result.push(...subarr);
}
return result;
}
function pathToBeziers(path: string): Bezier[][] {
const parts = path.trim().split(" ");
let x = 0;
let y = 0;
let i = 0;
const shapes: Bezier[][] = [];
let currShape: Bezier[] = [];
const chomp = () => {
if (i >= parts.length) {
throw new Error(`Ran out of path parts!`);
}
const val = parts[i];
i++;
return val;
};
const finishCurrShape = () => {
if (currShape.length) {
shapes.push(currShape);
currShape = [];
}
};
while (i < parts.length) {
const command = chomp();
switch (command) {
case "M":
finishCurrShape();
x = float(chomp());
y = float(chomp());
break;
case "C":
const x1 = float(chomp());
const y1 = float(chomp());
const x2 = float(chomp());
const y2 = float(chomp());
const endX = float(chomp());
const endY = float(chomp());
currShape.push(new Bezier(x, y, x1, y1, x2, y2, endX, endY));
x = endX;
y = endY;
break;
case "Z":
finishCurrShape();
break;
default:
throw new Error(`Unknown SVG path command: '${command}'`);
}
}
finishCurrShape();
return shapes;
}
function getBezierBoundingBox(bezier: Bezier): Bbox {
const start = bezier.get(0.0);
const end = bezier.get(1.0);
@ -169,13 +101,16 @@ function getBezierBoundingBox(bezier: Bezier): Bbox {
return getBoundingBoxForPoints([start, end, ...extrema]);
}
export function getBoundingBoxForBeziers(beziers: Bezier[]): Bbox {
return coalesceBoundingBoxes(beziers.map(getBezierBoundingBox));
}
function getPathBoundingBox(props: SVGProps<SVGPathElement>): Bbox {
if (!props.d) {
throw new Error(`SVG path has no 'd' attribute value!`);
}
const beziers = flatten(pathToBeziers(props.d));
const bezierBboxes = beziers.map(getBezierBoundingBox);
const bbox = coalesceBoundingBoxes(bezierBboxes);
const beziers = flatten(pathToShapes(props.d));
const bbox = getBoundingBoxForBeziers(beziers);
return props.strokeWidth
? dilateBoundingBox(bbox, float(props.strokeWidth) / 2)
: bbox;

58
lib/path.ts 100644
Wyświetl plik

@ -0,0 +1,58 @@
import { Bezier } from "../vendor/bezier-js";
import { float } from "./util";
export function pathToShapes(path: string): Bezier[][] {
const parts = path.trim().split(" ");
let x = 0;
let y = 0;
let i = 0;
const shapes: Bezier[][] = [];
let currShape: Bezier[] = [];
const chomp = () => {
if (i >= parts.length) {
throw new Error(`Ran out of path parts!`);
}
const val = parts[i];
i++;
return val;
};
const finishCurrShape = () => {
if (currShape.length) {
shapes.push(currShape);
currShape = [];
}
};
while (i < parts.length) {
const command = chomp();
switch (command) {
case "M":
finishCurrShape();
x = float(chomp());
y = float(chomp());
break;
case "C":
const x1 = float(chomp());
const y1 = float(chomp());
const x2 = float(chomp());
const y2 = float(chomp());
const endX = float(chomp());
const endY = float(chomp());
currShape.push(new Bezier(x, y, x1, y1, x2, y2, endX, endY));
x = endX;
y = endY;
break;
case "Z":
finishCurrShape();
break;
default:
throw new Error(`Unknown SVG path command: '${command}'`);
}
}
finishCurrShape();
return shapes;
}

Wyświetl plik

@ -1,14 +1,15 @@
import { Bbox } from "./bounding-box";
import { Point } from "../vendor/bezier-js";
import {
Bbox,
getBoundingBoxCenter,
getBoundingBoxForBeziers,
} from "./bounding-box";
import * as colors from "./colors";
import { pathToShapes } from "./path";
import type { SvgSymbolElement } from "./vocabulary";
const SPEC_LAYER_ID_RE = /^specs.*/i;
export type Point = {
x: number;
y: number;
};
export type Specs = {
tail?: Point[];
leg?: Point[];
@ -19,13 +20,26 @@ export type Specs = {
};
function getPoints(path: string): Point[] {
// TODO: Implement this.
return [];
const shapes = pathToShapes(path);
const points: Point[] = [];
for (let shape of shapes) {
const bbox = getBoundingBoxForBeziers(shape);
points.push(getBoundingBoxCenter(bbox));
}
return points;
}
function getBoundingBoxes(path: string): Bbox[] {
// TODO: Implement this.
return [];
const shapes = pathToShapes(path);
const bboxes: Bbox[] = [];
for (let shape of shapes) {
bboxes.push(getBoundingBoxForBeziers(shape));
}
return bboxes;
}
function concat<T>(first: T[] | undefined, second: T[]): T[] {

22
lib/util.ts 100644
Wyświetl plik

@ -0,0 +1,22 @@
export function float(value: string | number | undefined): number {
if (typeof value === "number") return value;
if (value === undefined) value = "";
const float = parseFloat(value);
if (isNaN(float)) {
throw new Error(`Expected '${value}' to be a float!`);
}
return float;
}
export function flatten<T>(arr: T[][]): T[] {
const result: T[] = [];
for (let subarr of arr) {
result.push(...subarr);
}
return result;
}