Factor out svg-symbol.tsx, visible-specs.tsx.
rodzic
455fb4ee45
commit
291c9d60fa
|
@ -1,7 +1,7 @@
|
||||||
import { Bezier, Point, BBox, MinMax } from "../vendor/bezier-js";
|
import { Bezier, Point, BBox, MinMax } from "../vendor/bezier-js";
|
||||||
import { SVGProps } from "react";
|
import { SVGProps } from "react";
|
||||||
|
|
||||||
import type { SvgSymbolElement } from "./vocabulary";
|
import type { SvgSymbolElement } from "./svg-symbol";
|
||||||
import { flatten, float } from "./util";
|
import { flatten, float } from "./util";
|
||||||
import { pathToShapes } from "./path";
|
import { pathToShapes } from "./path";
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,30 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Random } from "../random";
|
import { Random } from "../random";
|
||||||
import { SvgVocabulary } from "../svg-vocabulary";
|
import { SvgVocabulary } from "../svg-vocabulary";
|
||||||
|
import {
|
||||||
|
createSvgSymbolContext,
|
||||||
|
SvgSymbolContent,
|
||||||
|
SvgSymbolData,
|
||||||
|
} from "../svg-symbol";
|
||||||
|
|
||||||
|
const SYMBOL_MAP = new Map(
|
||||||
|
SvgVocabulary.map((symbol) => [symbol.name, symbol])
|
||||||
|
);
|
||||||
|
|
||||||
|
function getSymbol(name: string): SvgSymbolData {
|
||||||
|
const symbol = SYMBOL_MAP.get(name);
|
||||||
|
if (!symbol) {
|
||||||
|
throw new Error(`Unable to find the symbol "${name}"!`);
|
||||||
|
}
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
|
||||||
export const CreaturePage: React.FC<{}> = () => {
|
export const CreaturePage: React.FC<{}> = () => {
|
||||||
const rand = new Random(1);
|
const rand = new Random(1);
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
|
const ctx = createSvgSymbolContext();
|
||||||
|
const eye = getSymbol("eye");
|
||||||
|
const hand = getSymbol("hand");
|
||||||
|
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
parts.push(rand.choice(SvgVocabulary).name);
|
parts.push(rand.choice(SvgVocabulary).name);
|
||||||
|
@ -13,6 +33,12 @@ export const CreaturePage: React.FC<{}> = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Creature!</h1>
|
<h1>Creature!</h1>
|
||||||
|
<svg width="1280px" height="720px">
|
||||||
|
<SvgSymbolContent data={eye} {...ctx} />
|
||||||
|
<g transform="scale(0.25 0.25) translate(1075 1075)">
|
||||||
|
<SvgSymbolContent data={hand} {...ctx} />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
<p>TODO: Make a creature with maybe the following parts:</p>
|
<p>TODO: Make a creature with maybe the following parts:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{parts.map((name, i) => (
|
{parts.map((name, i) => (
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { BBox } from "../../vendor/bezier-js";
|
|
||||||
import { dilateBoundingBox, getBoundingBoxSize } from "../bounding-box";
|
import { dilateBoundingBox, getBoundingBoxSize } from "../bounding-box";
|
||||||
import { FILL_REPLACEMENT_COLOR, STROKE_REPLACEMENT_COLOR } from "../colors";
|
import {
|
||||||
import * as colors from "../colors";
|
createSvgSymbolContext,
|
||||||
import { iterAttachmentPoints, AttachmentPoint, Specs } from "../specs";
|
SvgSymbolContent,
|
||||||
|
SvgSymbolData,
|
||||||
import type { SvgSymbolData, SvgSymbolElement } from "../vocabulary";
|
} from "../svg-symbol";
|
||||||
import { SvgVocabulary } from "../svg-vocabulary";
|
import { SvgVocabulary } from "../svg-vocabulary";
|
||||||
|
import { SvgSymbolContext } from "../svg-symbol";
|
||||||
type SvgSymbolContext = {
|
|
||||||
stroke: string;
|
|
||||||
fill: string;
|
|
||||||
showSpecs: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type SvgSymbolProps = {
|
type SvgSymbolProps = {
|
||||||
data: SvgSymbolData;
|
data: SvgSymbolData;
|
||||||
|
@ -21,103 +15,6 @@ type SvgSymbolProps = {
|
||||||
|
|
||||||
const px = (value: number) => `${value}px`;
|
const px = (value: number) => `${value}px`;
|
||||||
|
|
||||||
function getColor(
|
|
||||||
ctx: SvgSymbolContext,
|
|
||||||
color: string | undefined
|
|
||||||
): string | undefined {
|
|
||||||
switch (color) {
|
|
||||||
case STROKE_REPLACEMENT_COLOR:
|
|
||||||
return ctx.stroke;
|
|
||||||
case FILL_REPLACEMENT_COLOR:
|
|
||||||
return ctx.fill;
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reactifySvgSymbolElement(
|
|
||||||
ctx: SvgSymbolContext,
|
|
||||||
el: SvgSymbolElement,
|
|
||||||
key: number
|
|
||||||
): JSX.Element {
|
|
||||||
let { fill, stroke } = el.props;
|
|
||||||
fill = getColor(ctx, fill);
|
|
||||||
stroke = getColor(ctx, stroke);
|
|
||||||
return React.createElement(
|
|
||||||
el.tagName,
|
|
||||||
{
|
|
||||||
...el.props,
|
|
||||||
id: undefined,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
key,
|
|
||||||
},
|
|
||||||
el.children.map(reactifySvgSymbolElement.bind(null, ctx))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ATTACHMENT_POINT_RADIUS = 20;
|
|
||||||
|
|
||||||
const ATTACHMENT_POINT_NORMAL_LENGTH = 50;
|
|
||||||
|
|
||||||
const ATTACHMENT_POINT_NORMAL_STROKE = 4;
|
|
||||||
|
|
||||||
const VisibleAttachmentPoint: React.FC<{
|
|
||||||
point: AttachmentPoint;
|
|
||||||
}> = ({ point: ap }) => {
|
|
||||||
const { x, y } = ap.point;
|
|
||||||
const x2 = x + ap.normal.x * ATTACHMENT_POINT_NORMAL_LENGTH;
|
|
||||||
const y2 = y + ap.normal.y * ATTACHMENT_POINT_NORMAL_LENGTH;
|
|
||||||
const color = colors.ATTACHMENT_POINT_COLORS[ap.type];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<circle fill={color} r={ATTACHMENT_POINT_RADIUS} cx={x} cy={y} />
|
|
||||||
<line
|
|
||||||
x1={x}
|
|
||||||
y1={y}
|
|
||||||
x2={x2}
|
|
||||||
y2={y2}
|
|
||||||
stroke={color}
|
|
||||||
strokeWidth={ATTACHMENT_POINT_NORMAL_STROKE}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BoundingBoxes: React.FC<{ fill: string; bboxes: BBox[] }> = (props) => (
|
|
||||||
<>
|
|
||||||
{props.bboxes.map((b, i) => {
|
|
||||||
const [width, height] = getBoundingBoxSize(b);
|
|
||||||
return (
|
|
||||||
<rect
|
|
||||||
key={i}
|
|
||||||
x={b.x.min}
|
|
||||||
y={b.y.min}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
fill={props.fill}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const SvgSymbolSpecs: React.FC<{ specs: Specs }> = ({ specs }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{Array.from(iterAttachmentPoints(specs)).map((point, i) => (
|
|
||||||
<VisibleAttachmentPoint key={i} point={point} />
|
|
||||||
))}
|
|
||||||
{specs.nesting && (
|
|
||||||
<BoundingBoxes
|
|
||||||
fill={colors.NESTING_BOUNDING_BOX_COLOR}
|
|
||||||
bboxes={specs.nesting}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BBOX_DILATION = 100;
|
const BBOX_DILATION = 100;
|
||||||
|
|
||||||
const SvgSymbol: React.FC<SvgSymbolProps> = (props) => {
|
const SvgSymbol: React.FC<SvgSymbolProps> = (props) => {
|
||||||
|
@ -132,8 +29,7 @@ const SvgSymbol: React.FC<SvgSymbolProps> = (props) => {
|
||||||
width={px(width * scale)}
|
width={px(width * scale)}
|
||||||
height={px(height * scale)}
|
height={px(height * scale)}
|
||||||
>
|
>
|
||||||
{props.data.layers.map(reactifySvgSymbolElement.bind(null, props))}
|
<SvgSymbolContent {...props} />
|
||||||
{props.showSpecs && d.specs && <SvgSymbolSpecs specs={d.specs} />}
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -142,6 +38,7 @@ export const VocabularyPage: React.FC<{}> = () => {
|
||||||
const [stroke, setStroke] = useState("#000000");
|
const [stroke, setStroke] = useState("#000000");
|
||||||
const [fill, setFill] = useState("#ffffff");
|
const [fill, setFill] = useState("#ffffff");
|
||||||
const [showSpecs, setShowSpecs] = useState(false);
|
const [showSpecs, setShowSpecs] = useState(false);
|
||||||
|
const ctx = createSvgSymbolContext({ stroke, fill, showSpecs });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -189,13 +86,7 @@ export const VocabularyPage: React.FC<{}> = () => {
|
||||||
{symbolData.name}
|
{symbolData.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="checkerboard-bg" style={{ lineHeight: 0 }}>
|
<div className="checkerboard-bg" style={{ lineHeight: 0 }}>
|
||||||
<SvgSymbol
|
<SvgSymbol data={symbolData} scale={0.25} {...ctx} />
|
||||||
data={symbolData}
|
|
||||||
scale={0.25}
|
|
||||||
stroke={stroke}
|
|
||||||
fill={fill}
|
|
||||||
showSpecs={showSpecs}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Point, BBox } from "../vendor/bezier-js";
|
||||||
import { getBoundingBoxForBeziers } from "./bounding-box";
|
import { getBoundingBoxForBeziers } from "./bounding-box";
|
||||||
import * as colors from "./colors";
|
import * as colors from "./colors";
|
||||||
import { pathToShapes } from "./path";
|
import { pathToShapes } from "./path";
|
||||||
import type { SvgSymbolElement } from "./vocabulary";
|
import type { SvgSymbolElement } from "./svg-symbol";
|
||||||
|
|
||||||
const SPEC_LAYER_ID_RE = /^specs.*/i;
|
const SPEC_LAYER_ID_RE = /^specs.*/i;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SVGProps } from "react";
|
||||||
|
import { BBox } from "../vendor/bezier-js";
|
||||||
|
import { FILL_REPLACEMENT_COLOR, STROKE_REPLACEMENT_COLOR } from "./colors";
|
||||||
|
import { Specs } from "./specs";
|
||||||
|
import { VisibleSpecs } from "./visible-specs";
|
||||||
|
|
||||||
|
export type SvgSymbolData = {
|
||||||
|
name: string;
|
||||||
|
bbox: BBox;
|
||||||
|
layers: SvgSymbolElement[];
|
||||||
|
specs?: Specs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SvgSymbolElement = (
|
||||||
|
| {
|
||||||
|
tagName: "g";
|
||||||
|
props: SVGProps<SVGGElement>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
tagName: "path";
|
||||||
|
props: SVGProps<SVGPathElement>;
|
||||||
|
}
|
||||||
|
) & {
|
||||||
|
children: SvgSymbolElement[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SvgSymbolContext = {
|
||||||
|
stroke: string;
|
||||||
|
fill: string;
|
||||||
|
showSpecs: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_CONTEXT: SvgSymbolContext = {
|
||||||
|
stroke: "#000000",
|
||||||
|
fill: "#ffffff",
|
||||||
|
showSpecs: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createSvgSymbolContext(
|
||||||
|
ctx: Partial<SvgSymbolContext> = {}
|
||||||
|
): SvgSymbolContext {
|
||||||
|
return {
|
||||||
|
...DEFAULT_CONTEXT,
|
||||||
|
...ctx,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColor(
|
||||||
|
ctx: SvgSymbolContext,
|
||||||
|
color: string | undefined
|
||||||
|
): string | undefined {
|
||||||
|
switch (color) {
|
||||||
|
case STROKE_REPLACEMENT_COLOR:
|
||||||
|
return ctx.stroke;
|
||||||
|
case FILL_REPLACEMENT_COLOR:
|
||||||
|
return ctx.fill;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reactifySvgSymbolElement(
|
||||||
|
ctx: SvgSymbolContext,
|
||||||
|
el: SvgSymbolElement,
|
||||||
|
key: number
|
||||||
|
): JSX.Element {
|
||||||
|
let { fill, stroke } = el.props;
|
||||||
|
fill = getColor(ctx, fill);
|
||||||
|
stroke = getColor(ctx, stroke);
|
||||||
|
return React.createElement(
|
||||||
|
el.tagName,
|
||||||
|
{
|
||||||
|
...el.props,
|
||||||
|
id: undefined,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
el.children.map(reactifySvgSymbolElement.bind(null, ctx))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SvgSymbolContent: React.FC<
|
||||||
|
{ data: SvgSymbolData } & SvgSymbolContext
|
||||||
|
> = (props) => {
|
||||||
|
const d = props.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.data.layers.map(reactifySvgSymbolElement.bind(null, props))}
|
||||||
|
{props.showSpecs && d.specs && <VisibleSpecs specs={d.specs} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import type { SvgSymbolData } from "./vocabulary";
|
import type { SvgSymbolData } from "./svg-symbol";
|
||||||
import _SvgVocabulary from "./_svg-vocabulary.json";
|
import _SvgVocabulary from "./_svg-vocabulary.json";
|
||||||
|
|
||||||
export const SvgVocabulary: SvgSymbolData[] = _SvgVocabulary as any;
|
export const SvgVocabulary: SvgSymbolData[] = _SvgVocabulary as any;
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
import React from "react";
|
||||||
|
import { BBox } from "../vendor/bezier-js";
|
||||||
|
import { getBoundingBoxSize } from "./bounding-box";
|
||||||
|
import * as colors from "./colors";
|
||||||
|
import { AttachmentPoint, iterAttachmentPoints, Specs } from "./specs";
|
||||||
|
|
||||||
|
const ATTACHMENT_POINT_RADIUS = 20;
|
||||||
|
|
||||||
|
const ATTACHMENT_POINT_NORMAL_LENGTH = 50;
|
||||||
|
|
||||||
|
const ATTACHMENT_POINT_NORMAL_STROKE = 4;
|
||||||
|
|
||||||
|
const VisibleAttachmentPoint: React.FC<{
|
||||||
|
point: AttachmentPoint;
|
||||||
|
}> = ({ point: ap }) => {
|
||||||
|
const { x, y } = ap.point;
|
||||||
|
const x2 = x + ap.normal.x * ATTACHMENT_POINT_NORMAL_LENGTH;
|
||||||
|
const y2 = y + ap.normal.y * ATTACHMENT_POINT_NORMAL_LENGTH;
|
||||||
|
const color = colors.ATTACHMENT_POINT_COLORS[ap.type];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<circle fill={color} r={ATTACHMENT_POINT_RADIUS} cx={x} cy={y} />
|
||||||
|
<line
|
||||||
|
x1={x}
|
||||||
|
y1={y}
|
||||||
|
x2={x2}
|
||||||
|
y2={y2}
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={ATTACHMENT_POINT_NORMAL_STROKE}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BoundingBoxes: React.FC<{ fill: string; bboxes: BBox[] }> = (props) => (
|
||||||
|
<>
|
||||||
|
{props.bboxes.map((b, i) => {
|
||||||
|
const [width, height] = getBoundingBoxSize(b);
|
||||||
|
return (
|
||||||
|
<rect
|
||||||
|
key={i}
|
||||||
|
x={b.x.min}
|
||||||
|
y={b.y.min}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
fill={props.fill}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const VisibleSpecs: React.FC<{ specs: Specs }> = ({ specs }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Array.from(iterAttachmentPoints(specs)).map((point, i) => (
|
||||||
|
<VisibleAttachmentPoint key={i} point={point} />
|
||||||
|
))}
|
||||||
|
{specs.nesting && (
|
||||||
|
<BoundingBoxes
|
||||||
|
fill={colors.NESTING_BOUNDING_BOX_COLOR}
|
||||||
|
bboxes={specs.nesting}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,30 +1,9 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
import { SVGProps } from "react";
|
|
||||||
import { BBox } from "../vendor/bezier-js";
|
|
||||||
import { getSvgBoundingBox } from "./bounding-box";
|
import { getSvgBoundingBox } from "./bounding-box";
|
||||||
import { Specs, extractSpecs } from "./specs";
|
import { extractSpecs } from "./specs";
|
||||||
|
import { SvgSymbolData, SvgSymbolElement } from "./svg-symbol";
|
||||||
export type SvgSymbolData = {
|
|
||||||
name: string;
|
|
||||||
bbox: BBox;
|
|
||||||
layers: SvgSymbolElement[];
|
|
||||||
specs?: Specs;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SvgSymbolElement = (
|
|
||||||
| {
|
|
||||||
tagName: "g";
|
|
||||||
props: SVGProps<SVGGElement>;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
tagName: "path";
|
|
||||||
props: SVGProps<SVGPathElement>;
|
|
||||||
}
|
|
||||||
) & {
|
|
||||||
children: SvgSymbolElement[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const SUPPORTED_SVG_TAG_ARRAY: SvgSymbolElement["tagName"][] = ["g", "path"];
|
const SUPPORTED_SVG_TAG_ARRAY: SvgSymbolElement["tagName"][] = ["g", "path"];
|
||||||
const SUPPORTED_SVG_TAGS = new Set(SUPPORTED_SVG_TAG_ARRAY);
|
const SUPPORTED_SVG_TAGS = new Set(SUPPORTED_SVG_TAG_ARRAY);
|
||||||
|
|
Ładowanie…
Reference in New Issue