Factor out svg-symbol.tsx, visible-specs.tsx.

pull/4/head
Atul Varma 2021-02-15 09:56:02 -05:00
rodzic 455fb4ee45
commit 291c9d60fa
8 zmienionych plików z 202 dodań i 144 usunięć

Wyświetl plik

@ -1,7 +1,7 @@
import { Bezier, Point, BBox, MinMax } from "../vendor/bezier-js";
import { SVGProps } from "react";
import type { SvgSymbolElement } from "./vocabulary";
import type { SvgSymbolElement } from "./svg-symbol";
import { flatten, float } from "./util";
import { pathToShapes } from "./path";

Wyświetl plik

@ -1,10 +1,30 @@
import React from "react";
import { Random } from "../random";
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<{}> = () => {
const rand = new Random(1);
const parts: string[] = [];
const ctx = createSvgSymbolContext();
const eye = getSymbol("eye");
const hand = getSymbol("hand");
for (let i = 0; i < 5; i++) {
parts.push(rand.choice(SvgVocabulary).name);
@ -13,6 +33,12 @@ export const CreaturePage: React.FC<{}> = () => {
return (
<>
<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>
<ul>
{parts.map((name, i) => (

Wyświetl plik

@ -1,18 +1,12 @@
import React, { useState } from "react";
import { BBox } from "../../vendor/bezier-js";
import { dilateBoundingBox, getBoundingBoxSize } from "../bounding-box";
import { FILL_REPLACEMENT_COLOR, STROKE_REPLACEMENT_COLOR } from "../colors";
import * as colors from "../colors";
import { iterAttachmentPoints, AttachmentPoint, Specs } from "../specs";
import type { SvgSymbolData, SvgSymbolElement } from "../vocabulary";
import {
createSvgSymbolContext,
SvgSymbolContent,
SvgSymbolData,
} from "../svg-symbol";
import { SvgVocabulary } from "../svg-vocabulary";
type SvgSymbolContext = {
stroke: string;
fill: string;
showSpecs: boolean;
};
import { SvgSymbolContext } from "../svg-symbol";
type SvgSymbolProps = {
data: SvgSymbolData;
@ -21,103 +15,6 @@ type SvgSymbolProps = {
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 SvgSymbol: React.FC<SvgSymbolProps> = (props) => {
@ -132,8 +29,7 @@ const SvgSymbol: React.FC<SvgSymbolProps> = (props) => {
width={px(width * scale)}
height={px(height * scale)}
>
{props.data.layers.map(reactifySvgSymbolElement.bind(null, props))}
{props.showSpecs && d.specs && <SvgSymbolSpecs specs={d.specs} />}
<SvgSymbolContent {...props} />
</svg>
);
};
@ -142,6 +38,7 @@ export const VocabularyPage: React.FC<{}> = () => {
const [stroke, setStroke] = useState("#000000");
const [fill, setFill] = useState("#ffffff");
const [showSpecs, setShowSpecs] = useState(false);
const ctx = createSvgSymbolContext({ stroke, fill, showSpecs });
return (
<>
@ -189,13 +86,7 @@ export const VocabularyPage: React.FC<{}> = () => {
{symbolData.name}
</div>
<div className="checkerboard-bg" style={{ lineHeight: 0 }}>
<SvgSymbol
data={symbolData}
scale={0.25}
stroke={stroke}
fill={fill}
showSpecs={showSpecs}
/>
<SvgSymbol data={symbolData} scale={0.25} {...ctx} />
</div>
</div>
))}

Wyświetl plik

@ -2,7 +2,7 @@ import { Point, BBox } from "../vendor/bezier-js";
import { getBoundingBoxForBeziers } from "./bounding-box";
import * as colors from "./colors";
import { pathToShapes } from "./path";
import type { SvgSymbolElement } from "./vocabulary";
import type { SvgSymbolElement } from "./svg-symbol";
const SPEC_LAYER_ID_RE = /^specs.*/i;

94
lib/svg-symbol.tsx 100644
Wyświetl plik

@ -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} />}
</>
);
};

Wyświetl plik

@ -1,4 +1,4 @@
import type { SvgSymbolData } from "./vocabulary";
import type { SvgSymbolData } from "./svg-symbol";
import _SvgVocabulary from "./_svg-vocabulary.json";
export const SvgVocabulary: SvgSymbolData[] = _SvgVocabulary as any;

Wyświetl plik

@ -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}
/>
)}
</>
);
};

Wyświetl plik

@ -1,30 +1,9 @@
import fs from "fs";
import path from "path";
import cheerio from "cheerio";
import { SVGProps } from "react";
import { BBox } from "../vendor/bezier-js";
import { getSvgBoundingBox } from "./bounding-box";
import { Specs, extractSpecs } from "./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[];
};
import { extractSpecs } from "./specs";
import { SvgSymbolData, SvgSymbolElement } from "./svg-symbol";
const SUPPORTED_SVG_TAG_ARRAY: SvgSymbolElement["tagName"][] = ["g", "path"];
const SUPPORTED_SVG_TAGS = new Set(SUPPORTED_SVG_TAG_ARRAY);