task(C2a): pure shape-primitive factory for all core drawing types
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* elementDrawers – Reine Beschreibungs-Fabrik (Task C2).
|
||||
*
|
||||
* Wandelt CADElemente in eine Liste von Primitiven um (line/rect/circle/
|
||||
* polyline/text/arc). Der PixiRenderer (C3) konsumiert diese Deskriptoren
|
||||
* und überführt sie in konkrete Grafikobjekte.
|
||||
*
|
||||
* Vorteil dieser Zwischenschicht:
|
||||
* - Vollständig unit-testbar OHNE WebGL/GPU
|
||||
* - Backend-unabhängig (Pixi jetzt, Canvas2D oder andere später)
|
||||
*
|
||||
* Konventionen entsprechen dem Legacy RenderEngine.draw*:
|
||||
* - rect: x/y = Zentrum der BBox
|
||||
* - circle/arc: radius über properties.radius (Fallback width/2)
|
||||
* - arc-Winkel in Grad, 0° = 3 Uhr, gegen den Uhrzeigersinn
|
||||
*/
|
||||
import type { CADElement } from '../../types/cad.types';
|
||||
import type { Pt } from '../../tools/modification/geometry';
|
||||
|
||||
/** Zeichenprimitiv — kleinste darstellbare Einheit. */
|
||||
export type ShapePrimitive =
|
||||
| { kind: 'line'; from: Pt; to: Pt; stroke: string; strokeWidth: number }
|
||||
| { kind: 'rect'; x: number; y: number; w: number; h: number; fill: string | null; stroke: string; strokeWidth: number }
|
||||
| { kind: 'circle'; center: Pt; radius: number; fill: string | null; stroke: string; strokeWidth: number }
|
||||
| { kind: 'polyline'; points: Pt[]; close: boolean; stroke: string; strokeWidth: number }
|
||||
| { kind: 'text'; at: Pt; text: string; fontSize: number; color: string }
|
||||
| { kind: 'arc'; center: Pt; radius: number; startDeg: number; endDeg: number; stroke: string; strokeWidth: number };
|
||||
|
||||
const FALLBACK_STROKE = '#e0e0e0';
|
||||
|
||||
function asPoint(p: unknown): Pt {
|
||||
const q = p as { x?: number; y?: number };
|
||||
return { x: Number(q?.x ?? 0), y: Number(q?.y ?? 0) };
|
||||
}
|
||||
|
||||
function fillOrNull(raw: unknown): string | null {
|
||||
return typeof raw === 'string' && raw !== 'none' ? raw : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wandelt ein CADElement in seine Zeichenprimitive um.
|
||||
* Gibt ein leeres Array zurück für unbekannte Typen bzw. Elemente,
|
||||
* die nichts Sichtbares beitragen (z.B. leerer Text).
|
||||
*/
|
||||
export function elementToPrimitives(el: CADElement): ShapePrimitive[] {
|
||||
const props = (el.properties ?? {}) as Record<string, unknown>;
|
||||
const stroke = (props.stroke as string | undefined) ?? FALLBACK_STROKE;
|
||||
const sw = (props.strokeWidth as number | undefined) ?? 1;
|
||||
|
||||
switch (el.type) {
|
||||
case 'line': {
|
||||
if (
|
||||
props.x1 === undefined || props.y1 === undefined ||
|
||||
props.x2 === undefined || props.y2 === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return [{
|
||||
kind: 'line',
|
||||
from: { x: props.x1 as number, y: props.y1 as number },
|
||||
to: { x: props.x2 as number, y: props.y2 as number },
|
||||
stroke,
|
||||
strokeWidth: sw,
|
||||
}];
|
||||
}
|
||||
|
||||
case 'rect': {
|
||||
// Legacy-BBox-Konvention: x/y ist das Zentrum des Rechtecks
|
||||
return [{
|
||||
kind: 'rect',
|
||||
x: el.x - el.width / 2,
|
||||
y: el.y - el.height / 2,
|
||||
w: el.width,
|
||||
h: el.height,
|
||||
fill: fillOrNull(props.fill),
|
||||
stroke,
|
||||
strokeWidth: sw,
|
||||
}];
|
||||
}
|
||||
|
||||
case 'circle': {
|
||||
const radius = (props.radius as number | undefined) ?? el.width / 2;
|
||||
return [{
|
||||
kind: 'circle',
|
||||
center: { x: el.x, y: el.y },
|
||||
radius,
|
||||
fill: fillOrNull(props.fill),
|
||||
stroke,
|
||||
strokeWidth: sw,
|
||||
}];
|
||||
}
|
||||
|
||||
case 'arc': {
|
||||
const radius = (props.radius as number | undefined) ?? el.width / 2;
|
||||
return [{
|
||||
kind: 'arc',
|
||||
center: { x: el.x, y: el.y },
|
||||
radius,
|
||||
startDeg: (props.startAngle as number | undefined) ?? 0,
|
||||
endDeg: (props.endAngle as number | undefined) ?? 360,
|
||||
stroke,
|
||||
strokeWidth: sw,
|
||||
}];
|
||||
}
|
||||
|
||||
case 'polyline':
|
||||
case 'polygon': {
|
||||
const pts = Array.isArray(props.points)
|
||||
? (props.points as unknown[]).map(asPoint)
|
||||
: [];
|
||||
if (pts.length < 2) return [];
|
||||
return [{
|
||||
kind: 'polyline',
|
||||
points: pts,
|
||||
close: el.type === 'polygon',
|
||||
stroke,
|
||||
strokeWidth: sw,
|
||||
}];
|
||||
}
|
||||
|
||||
case 'text': {
|
||||
const txt = String(props.text ?? '');
|
||||
if (!txt) return []; // leeren Platzhalter nicht rendern
|
||||
return [{
|
||||
kind: 'text',
|
||||
at: { x: el.x, y: el.y },
|
||||
text: txt,
|
||||
fontSize: (props.fontSize as number | undefined) ?? 12,
|
||||
color: (props.color as string | undefined) ?? stroke,
|
||||
}];
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user