feat(proxy): add offset param to inset or outset traced shapes

Trace results can now be inset (negative) or outset (positive) by a
pixel amount. Offset the outline and holes in opposite directions with
clipper-lib's miter joins, then recombine with a boolean difference so
holes grow on inset and shrink on outset. Collapsed shapes return an
empty outline; a split outline keeps the largest ring. Document the
parameter and the new dependency.
This commit is contained in:
2026-08-08 15:37:27 +08:00
parent c7f4bd03fd
commit 9094ad58e0
12 changed files with 284 additions and 17 deletions
+9 -3
View File
@@ -3,7 +3,7 @@ import { serialize } from 'bson';
import sharp from 'sharp';
import { createRequire } from 'module';
import { traceRequestSchema, type TraceResult } from '@tts/shared';
import { parseSvgShape } from './svgShape.js';
import { offsetShape, parseSvgShape } from './svgShape.js';
// vtracer is a CommonJS package; load it via require so the wasm initializes
// with the correct `__dirname`.
@@ -36,7 +36,8 @@ app.get('/', async (c) => {
if (!parsed.success) {
return c.json({ error: parsed.error.issues[0]?.message }, 400);
}
const { url, mode, threshold, format, simplify, maxColors } = parsed.data;
const { url, mode, threshold, format, simplify, maxColors, offset } =
parsed.data;
// Only allow http(s) to avoid SSRF via file://, etc.
const parsedUrl = new URL(url);
@@ -95,7 +96,12 @@ app.get('/', async (c) => {
svg,
};
if (format === 'shape') {
result.shape = parseSvgShape(svg);
let shape = parseSvgShape(svg);
if (offset !== undefined && offset !== 0) {
shape = offsetShape(shape, offset);
result.offset = offset;
}
result.shape = shape;
}
return new Response(Buffer.from(serialize(result)), {