mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
ce9c94a77b
- Fifty more shapes: arches, paper cut-outs (leaves, coral, weed, organic circles, a wonky star and sun), stamps, a postmark, a rosette, tags and ticket stubs, sunbursts, ribbons and bands, speech bubbles, drawn arrows, hearts, stars, photo frames and ornaments (scroll, laurel, fleuron, corner). The broad ones are photo frames too, and each tile is drawn at its shape's own proportions, which a new shape is also placed at. - A cut edge on rectangles, circles, arches and the other broad shapes, painted or not, and on a photo's cut: zigzag, scallop, a postage stamp's perforation, wavy, torn and deckle, with a depth in millimetres. Cut in millimetres, so holes stay round on a wide frame. - A line element: two to 32 points dragged on the canvas (a handle pulled from the middle adds one, a double-click removes it), straight or curved, solid, dashed or dotted, a wave or a zigzag, a hand-drawn wobble, a head on either end and an ornament in the middle. A shelf of lines and dividers places them. - The assistant reaches all of it: add and style take the line (its points on the spread in mm) and the edges, describe_book_page says them, and two new findings say when a line is too thin to print and when an edge is set on a shape that takes none. A photo's mask is checked against the shape set rather than listed again, which keeps the page edit's schema smaller. - BOOK_SCHEMA_REV goes to 6.
103 lines
4.2 KiB
TypeScript
103 lines
4.2 KiB
TypeScript
import { BOOK_EDGES } from '@trek/shared';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import { SHAPE_PATHS, scalePath } from '../../../src/components/Studio/shapes';
|
|
import { edgedPath, flattenPath, outlineRuns } from '../../../src/components/Studio/shapes-edges';
|
|
|
|
/**
|
|
* Cut edges (shapes-edges.ts): the outline flattened, split at its corners,
|
|
* a whole number of teeth per side, cut inwards only.
|
|
*/
|
|
|
|
const RECT = 'M0 0L60 0L60 40L0 40Z';
|
|
const points = (d: string) => {
|
|
const nums = (d.match(/-?\d*\.?\d+/g) ?? []).map(Number);
|
|
const out: [number, number][] = [];
|
|
for (let i = 0; i < nums.length; i += 2) out.push([nums[i], nums[i + 1]]);
|
|
return out;
|
|
};
|
|
|
|
describe('the outline', () => {
|
|
it('FE-STUDIO-EDGE-001: flattens lines and curves, dropping the closing repeat', () => {
|
|
expect(flattenPath(RECT)).toEqual([
|
|
[0, 0],
|
|
[60, 0],
|
|
[60, 40],
|
|
[0, 40],
|
|
]);
|
|
const circle = flattenPath(scalePath(SHAPE_PATHS.ellipse, 50, 50));
|
|
expect(circle.length).toBeGreaterThan(30);
|
|
for (const [x, y] of circle) expect(Math.hypot(x - 25, y - 25)).toBeCloseTo(25, 0);
|
|
expect(flattenPath('M0 0Q10 10 20 0Z').length).toBeGreaterThan(3);
|
|
});
|
|
|
|
it('FE-STUDIO-EDGE-002: splits a rectangle into four runs at its corners and a circle into one', () => {
|
|
expect(outlineRuns(flattenPath(RECT))).toHaveLength(4);
|
|
expect(outlineRuns(flattenPath(scalePath(SHAPE_PATHS.ellipse, 50, 50)))).toHaveLength(1);
|
|
expect(outlineRuns(flattenPath(scalePath(SHAPE_PATHS.hexagon, 50, 50)))).toHaveLength(6);
|
|
});
|
|
});
|
|
|
|
describe('the cut', () => {
|
|
it('FE-STUDIO-EDGE-003: leaves a straight edge and a degenerate outline as they are', () => {
|
|
expect(edgedPath(RECT, 'straight', 3)).toBe(RECT);
|
|
expect(edgedPath(RECT, 'zigzag', 0)).toBe(RECT);
|
|
expect(edgedPath('M0 0L10 0Z', 'zigzag', 3)).toBe('M0 0L10 0Z');
|
|
});
|
|
|
|
it('FE-STUDIO-EDGE-004: cuts every edge inwards, inside the frame, with the corners left on the outline', () => {
|
|
for (const edge of BOOK_EDGES.filter((e) => e !== 'straight')) {
|
|
const d = edgedPath(RECT, edge, 3, 5);
|
|
expect(d, edge).toMatch(/^M[\d.]+ [\d.]+(L[\d.]+ [\d.]+)+Z$/);
|
|
const pts = points(d);
|
|
for (const [x, y] of pts) {
|
|
expect(x, edge).toBeGreaterThanOrEqual(-0.01);
|
|
expect(x, edge).toBeLessThanOrEqual(60.01);
|
|
expect(y, edge).toBeGreaterThanOrEqual(-0.01);
|
|
expect(y, edge).toBeLessThanOrEqual(40.01);
|
|
}
|
|
for (const corner of [
|
|
[0, 0],
|
|
[60, 0],
|
|
[60, 40],
|
|
[0, 40],
|
|
]) {
|
|
expect(
|
|
pts.some(([x, y]) => Math.hypot(x - corner[0], y - corner[1]) < 0.02),
|
|
`${edge} ${corner}`
|
|
).toBe(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('FE-STUDIO-EDGE-005: gives each side a whole number of teeth, so a stamp has round holes all round', () => {
|
|
const zig = points(edgedPath(RECT, 'zigzag', 3));
|
|
// 20 teeth along 60 mm and 13 along 40 mm, two points each.
|
|
expect(zig).toHaveLength((20 + 13) * 2 * 2);
|
|
// How deep the top side bites, its corners left out.
|
|
const top = (pts: [number, number][]) =>
|
|
Math.max(...pts.filter(([x, y]) => x > 3 && x < 57 && y < 10).map(([, y]) => y));
|
|
expect(top(zig)).toBeCloseTo(1.5, 1);
|
|
expect(top(points(edgedPath(RECT, 'perforated', 4)))).toBeCloseTo(1.2, 1);
|
|
});
|
|
|
|
it('FE-STUDIO-EDGE-006: tears the same way for the same seed and another way for another', () => {
|
|
expect(edgedPath(RECT, 'torn', 3, 7)).toBe(edgedPath(RECT, 'torn', 3, 7));
|
|
expect(edgedPath(RECT, 'torn', 3, 7)).not.toBe(edgedPath(RECT, 'torn', 3, 8));
|
|
expect(edgedPath(RECT, 'deckle', 3, 7)).not.toBe(edgedPath(RECT, 'deckle', 3, 8));
|
|
});
|
|
|
|
it('FE-STUDIO-EDGE-007: coarsens the teeth past the point cap rather than drawing a path nobody can render', () => {
|
|
const huge = 'M0 0L5000 0L5000 5000L0 5000Z';
|
|
expect(points(edgedPath(huge, 'scallop', 0.5)).length).toBeLessThanOrEqual(3100);
|
|
});
|
|
|
|
it('FE-STUDIO-EDGE-008: cuts inwards whichever way the outline is wound', () => {
|
|
const reversed = edgedPath('M0 0L0 40L60 40L60 0Z', 'zigzag', 3);
|
|
for (const [x, y] of points(reversed)) {
|
|
expect(x).toBeGreaterThanOrEqual(-0.01);
|
|
expect(y).toBeLessThanOrEqual(40.01);
|
|
}
|
|
});
|
|
});
|