Scale a symbol into the unit circle
64-bitCenters the symbol's entities and scales them so the farthest point is at radius 1.
x′ = round((x − cx) ÷ R × 10⁴) ÷ 10⁴
Every stored coordinate and radius is rounded to 4 decimals of the unit radius, and arc angles to 4 decimals of a degree. The rounding is applied to the value that is saved in the project, not only to a display.
/** Entities (circle / arc / line / poly with [x, y] points) → centred on their box, scaled so the farthest point is at radius 1. */
function normalize(ents) {
const ext = []; // [x, y, r]: a point, or a circle's centre and radius
for (const e of ents) {
if (e.type === 'circle' || e.type === 'arc') ext.push([e.x, e.y, e.r]);
else if (e.type === 'line') ext.push([e.x1, e.y1, 0], [e.x2, e.y2, 0]);
else for (const p of e.pts) ext.push([p[0], p[1], 0]);
}
if (!ext.length) throw new Error('empty');
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
for (const [x, y, r] of ext) { x0 = Math.min(x0, x - r); x1 = Math.max(x1, x + r); y0 = Math.min(y0, y - r); y1 = Math.max(y1, y + r); }
const cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
let R = 0;
for (const [x, y, r] of ext) R = Math.max(R, Math.hypot(x - cx, y - cy) + r);
if (!(R > 0)) throw new Error('empty');
const X = (x) => r4((x - cx) / R), Y = (y) => r4((y - cy) / R);
return ents.map((e) => (e.type === 'circle' ? { type: 'circle', x: X(e.x), y: Y(e.y), r: r4(e.r / R) } : e.type === 'arc' ? { type: 'arc', x: X(e.x), y: Y(e.y), r: r4(e.r / R), a0: r4(e.a0), a1: r4(e.a1) }
: e.type === 'line' ? { type: 'line', x1: X(e.x1), y1: Y(e.y1), x2: X(e.x2), y2: Y(e.y2) } : { type: 'poly', pts: e.pts.map((p) => [X(p[0]), Y(p[1])]), closed: !!e.closed }));
}