109 wiersze
2.8 KiB
TypeScript
109 wiersze
2.8 KiB
TypeScript
import { Bezier, Point, BBox, MinMax } from "../vendor/bezier-js";
|
|
import { SVGProps } from "react";
|
|
|
|
import type { SvgSymbolElement } from "./svg-symbol";
|
|
import { flatten, float } from "./util";
|
|
import { pathToShapes } from "./path";
|
|
|
|
export function getBoundingBoxSize(bbox: BBox): [number, number] {
|
|
const width = bbox.x.max - bbox.x.min;
|
|
const height = bbox.y.max - bbox.y.min;
|
|
|
|
return [width, height];
|
|
}
|
|
|
|
export function getBoundingBoxCenter(bbox: BBox): Point {
|
|
const [width, height] = getBoundingBoxSize(bbox);
|
|
|
|
return {
|
|
x: bbox.x.min + width / 2,
|
|
y: bbox.y.min + height / 2,
|
|
};
|
|
}
|
|
|
|
function dilateMinMax(minmax: MinMax, amount: number): MinMax {
|
|
return {
|
|
min: minmax.min - amount,
|
|
max: minmax.max + amount,
|
|
};
|
|
}
|
|
|
|
export function dilateBoundingBox(bbox: BBox, amount: number): BBox {
|
|
return {
|
|
x: dilateMinMax(bbox.x, amount),
|
|
y: dilateMinMax(bbox.y, amount),
|
|
};
|
|
}
|
|
|
|
export function coalesceBoundingBoxes(bboxes: BBox[]): BBox {
|
|
let minX = Infinity;
|
|
let minY = Infinity;
|
|
let maxX = -Infinity;
|
|
let maxY = -Infinity;
|
|
|
|
if (bboxes.length === 0) {
|
|
throw new Error(`Must have at least one bounding box!`);
|
|
}
|
|
|
|
for (let bbox of bboxes) {
|
|
if (bbox.x.min < minX) {
|
|
minX = bbox.x.min;
|
|
}
|
|
if (bbox.x.max > maxX) {
|
|
maxX = bbox.x.max;
|
|
}
|
|
if (bbox.y.min < minY) {
|
|
minY = bbox.y.min;
|
|
}
|
|
if (bbox.y.max > maxY) {
|
|
maxY = bbox.y.max;
|
|
}
|
|
}
|
|
|
|
return { x: { min: minX, max: maxX }, y: { min: minY, max: maxY } };
|
|
}
|
|
|
|
export function getBoundingBoxForBeziers(beziers: Bezier[]): BBox {
|
|
return coalesceBoundingBoxes(beziers.map((b) => b.bbox()));
|
|
}
|
|
|
|
function getPathBoundingBox(props: SVGProps<SVGPathElement>): BBox {
|
|
if (!props.d) {
|
|
throw new Error(`SVG path has no 'd' attribute value!`);
|
|
}
|
|
const beziers = flatten(pathToShapes(props.d));
|
|
const bbox = getBoundingBoxForBeziers(beziers);
|
|
return props.strokeWidth
|
|
? dilateBoundingBox(bbox, float(props.strokeWidth) / 2)
|
|
: bbox;
|
|
}
|
|
|
|
export function getSvgBoundingBox(
|
|
element: SvgSymbolElement | SvgSymbolElement[]
|
|
): BBox {
|
|
if (Array.isArray(element)) {
|
|
return coalesceBoundingBoxes(element.map(getSvgBoundingBox));
|
|
}
|
|
switch (element.tagName) {
|
|
case "g":
|
|
return getSvgBoundingBox(element.children);
|
|
case "path":
|
|
return getPathBoundingBox(element.props);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assuming the origins of the giving boxes are aligned and
|
|
* the transform origin is set to their center, return the maximum
|
|
* amount the child needs to be scaled to fit within the parent.
|
|
*/
|
|
export function uniformlyScaleToFit(parent: BBox, child: BBox): number {
|
|
const [pWidth, pHeight] = getBoundingBoxSize(parent);
|
|
const [cWidth, cHeight] = getBoundingBoxSize(child);
|
|
|
|
const widthScale = pWidth / cWidth;
|
|
const heightScale = pHeight / cHeight;
|
|
|
|
return Math.min(widthScale, heightScale);
|
|
}
|