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 { SVGProps } from "react";
import type { SvgSymbolElement } from "./vocabulary"; import type { SvgSymbolElement } from "./vocabulary";
import { flatten, float } from "./util";
import { pathToShapes } from "./path";
export type Bbox = { export type Bbox = {
minX: number; minX: number;
@ -17,6 +19,15 @@ export function getBoundingBoxSize(bbox: Bbox): [number, number] {
return [width, height]; 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 { function dilateBoundingBox(bbox: Bbox, amount: number): Bbox {
return { return {
minX: bbox.minX - amount, 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 minX = Infinity;
let minY = Infinity; let minY = Infinity;
let maxX = -Infinity; let maxX = -Infinity;
@ -82,85 +93,6 @@ function getBoundingBoxForPoints(points: Point[]): Bbox {
return { minX, minY, maxX, maxY }; 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 { function getBezierBoundingBox(bezier: Bezier): Bbox {
const start = bezier.get(0.0); const start = bezier.get(0.0);
const end = bezier.get(1.0); const end = bezier.get(1.0);
@ -169,13 +101,16 @@ function getBezierBoundingBox(bezier: Bezier): Bbox {
return getBoundingBoxForPoints([start, end, ...extrema]); return getBoundingBoxForPoints([start, end, ...extrema]);
} }
export function getBoundingBoxForBeziers(beziers: Bezier[]): Bbox {
return coalesceBoundingBoxes(beziers.map(getBezierBoundingBox));
}
function getPathBoundingBox(props: SVGProps<SVGPathElement>): Bbox { function getPathBoundingBox(props: SVGProps<SVGPathElement>): Bbox {
if (!props.d) { if (!props.d) {
throw new Error(`SVG path has no 'd' attribute value!`); throw new Error(`SVG path has no 'd' attribute value!`);
} }
const beziers = flatten(pathToBeziers(props.d)); const beziers = flatten(pathToShapes(props.d));
const bezierBboxes = beziers.map(getBezierBoundingBox); const bbox = getBoundingBoxForBeziers(beziers);
const bbox = coalesceBoundingBoxes(bezierBboxes);
return props.strokeWidth return props.strokeWidth
? dilateBoundingBox(bbox, float(props.strokeWidth) / 2) ? dilateBoundingBox(bbox, float(props.strokeWidth) / 2)
: bbox; : 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 * as colors from "./colors";
import { pathToShapes } from "./path";
import type { SvgSymbolElement } from "./vocabulary"; import type { SvgSymbolElement } from "./vocabulary";
const SPEC_LAYER_ID_RE = /^specs.*/i; const SPEC_LAYER_ID_RE = /^specs.*/i;
export type Point = {
x: number;
y: number;
};
export type Specs = { export type Specs = {
tail?: Point[]; tail?: Point[];
leg?: Point[]; leg?: Point[];
@ -19,13 +20,26 @@ export type Specs = {
}; };
function getPoints(path: string): Point[] { function getPoints(path: string): Point[] {
// TODO: Implement this. const shapes = pathToShapes(path);
return []; const points: Point[] = [];
for (let shape of shapes) {
const bbox = getBoundingBoxForBeziers(shape);
points.push(getBoundingBoxCenter(bbox));
}
return points;
} }
function getBoundingBoxes(path: string): Bbox[] { function getBoundingBoxes(path: string): Bbox[] {
// TODO: Implement this. const shapes = pathToShapes(path);
return []; const bboxes: Bbox[] = [];
for (let shape of shapes) {
bboxes.push(getBoundingBoxForBeziers(shape));
}
return bboxes;
} }
function concat<T>(first: T[] | undefined, second: T[]): T[] { 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;
}