Compare commits
2 Commits
f6b5046a60
...
a093630d34
| Author | SHA1 | Date | |
|---|---|---|---|
| a093630d34 | |||
| cb23638610 |
+4
-3
@@ -634,7 +634,7 @@ Kurz-Tasks (je gleiche Struktur: implementieren im passenden builtin-Paket + Wor
|
||||
- [x] G1 Datenmodell layouts (Yjs Map: layoutId→{name, viewport{center,scale}, frameBlockRef}), Backend persistence folgt automatisch via drawing json *(2026-08-29: LayoutDefinition/LayoutViewport in cad.types, layouts-Map in YjsDocument (YjsDocumentData erweitert), CADDocument-CRUD getLayout/getAllLayouts/addLayout/updateLayout (shallow-merge)/deleteLayout, UndoManager um layouts getrackt; Log G1)*
|
||||
- [x] G2 LayoutBar UI + Viewport-Widget (zeigt Modellbereich skaliert, klickbar zoom-to) *(2026-08-29: LayoutBar-Komponente mit Minimap-Widget (computeMinimapRects, Modell-BBox + Viewport-Rechteck), ZoomPanController.setView(center,scale), CanvasArea-__v2LayoutBridge, App-Mount; Log G2)*
|
||||
- [x] G3 Titelblock-Block mit Attributen (Projekt/Datum/Maßstab/Autor) auto-insert *(2026-08-29: titleBlockService mit createTitleBlockElements (Rahmen + Titel + 4 attributierte Texte nach F5/F6-ATTDEF-Konvention tag/attdef/prompt), insertTitleBlockForLayout setzt frameBlockRef; CADDocument-Block-CRUD getBlock/addBlock; LayoutBar-Button 🏷; Log G3)*
|
||||
- [ ] G4 PDF-Export je Layout im Ausgabemaßstab (pdf-lib; Linienbreiten aus Layern; monochrome Option)
|
||||
- [x] G4 PDF-Export je Layout im Ausgabemaßstab (pdf-lib; Linienbreiten aus Layern; monochrome Option) *(2026-08-29: exportLayoutPDF über elementToPrimitives (C2-Konventionen — korrekte Circle-Zentrum-Behandlung, im Gegensatz zum Legacy-exportPDF mit x+width/2-Bug), Viewport-skaliert A4 landscape, Layer-Dash-Patterns + Breiten, monochrome, Titelblock-Mitzeichnung; Log G4)*
|
||||
- [ ] G5 Browser-Print stylesheet (@media print: nur Layout)
|
||||
|
||||
## Phase H — Modi & Branchenneutralität
|
||||
@@ -667,6 +667,7 @@ Format: `YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/of
|
||||
|
||||
| Datum | Task | Zusammenfassung | Tests | Commit | Notizen |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-08-29 | G4 | PDF-Export je Layout: exportLayoutPDF(doc, layoutId, options{monochrome,margin}) NEU in pdfExport.ts (additiv — Legacy-exportPDF unangetastet): A4 landscape 842×595pt; Ausgabemaßstab = layout.viewport.scale, sichtbarer Welt-Ausschnitt aus center berechnet; Y-Flip (PDF bottom-left vs CAD top-down) in toPage-Transform; Rendering über elementToPrimitives (C2) statt duplizierter Typ-Switches → garantiert identische Konventionen wie Pixi-Renderer (Legacy-exportPDF hätte Circle um width/2 verschoben gezeichnet — gleiche Bugklasse wie DXF-Writer vor F6, dort belassen und im Log vermerkt); Linienbreiten: properties.strokeWidth sonst Layer-lineType (solid 0.7pt/dashed 0.9pt/dotted 0.5pt) mit Dash-Patterns [4,3]/[0.5,2]; monochrome erzwingt rgb(0,0,0) + keine Dashes; unsichtbare Layer gefiltert; Arc→32 Segmente (Grad CCW); Titelblock aus frameBlockRef wird unskaliert unten-rechts mitgezeichnet (Block-Elemente um Ursprung an Ecken-Offset); drawPrimitive deckt alle 6 Primitive-Kinds | Frontend: 580/580 Tests (+6 pdfLayoutExport.test.ts — PDF via PDFDocument.load verifiziert: Seitenzahl 1, Größe 842×595, Frame-Block-Fall, monochrome, leerer Viewport, unknown-ID-throw, unsichtbare Layer), tsc 0, Build EXIT 0 | cb23638 | G4 COMPLETE; nächster Task G5 (Print-Stylesheet) |
|
||||
| 2026-08-29 | G3 | Titelblock-Block mit Attributen: (1) titleBlockService NEU — createTitleBlockElements (reine Funktion): Rahmen rect 180×100 (App-Konvention x/y=Zentrum) + Titel-Label + 4 attributierte Texte PROJEKT/DATUM/MASSSTAB/AUTOR mit tag/attdef:true/prompt-Metadaten und text=TAG=Wert (exakt die F5-Parser- und F6-Writer-Konvention → DXF-ATTDEF-Roundtrip & D12-Formular-ready), leere Meta → Fallback —; (2) insertTitleBlockForLayout: Blockdefinition (category frame) via doc.addBlock + frameBlockRef im Layout via updateLayout, Re-Insert ersetzt Referenz (neue Block-ID), fehlendes Layout → throw; (3) CADDocument um getBlock/addBlock erweitert (bewusst NICHT undo-getrackt — konsistent zum UndoManager-Design-Kommentar); (4) LayoutBar-Button 🏷 je Layout: fügt/aktualisiert Titelblock mit Layout-Name als Projekt, heutiges Datum, Maßstab aus viewport.scale (1:1/scale gerundet), Autor WebCAD; Test-Zwischenfall: eigene Assertion prüfte attdef über alle 5 Texte inkl. Titel-Label (bewusst kein Attribut) — auf getaggte Texte gescoped | Frontend: 574/574 Tests (+6 titleBlock.test.ts), tsc 0, Build EXIT 0 | bbb469e | G3 COMPLETE; nächster Task G4 (PDF-Export je Layout) |
|
||||
| 2026-08-29 | G2 | LayoutBar UI + Viewport-Widget: (1) ZoomPanController.setView(center, scale) NEU — Welt-Punkt exakt ins Canvas-Zentrum bei Scale, geclamped 0.01–100; (2) LayoutBar-Komponente: listet Layouts des aktiven Docs (documentService-Fallback mit onActiveDocumentChanged-Subscription + onChanged-Live-Reload), je Eintrag SVG-Minimap (Modellbereich als Rechteck + Viewport-Ausschnitt blau — scale-abhängig sichtbar kleiner), Klick = zoom-to via Bridge, „+“ speichert aktuellen Viewport als Layout, 🗑 löscht; (3) computeMinimapRects als reine exportierte Funktion (Modell-BBox füllt Widget mit 6px Padding, ohne Elemente Volles-Widget-Fallback, Viewport = 800×600/scale nominal); (4) CanvasArea-__v2LayoutBridge nach E6b-Muster (zoomTo mit render-Trigger, getView aus getViewport+getScale); (5) App.tsx mountet LayoutBar über StatusBar; CSS angehängt; Zwischenfälle selbst gefixt: view-Parameter-Kollision in computeMinimapRects (Umbenennung viewRect) + Regex-Double-Escape im Test | Frontend: 568/568 Tests (+11 layoutBar.test.tsx), tsc 0, Build EXIT 0 | 7feed9e | G2 COMPLETE; nächster Task G3 (Titelblock-Block mit Attributen auto-insert) |
|
||||
| 2026-08-29 | G1 | Layout-Datenmodell: LayoutViewport {center{x,y}, scale} + LayoutDefinition {id, name, viewport, frameBlockRef?} in cad.types.ts; YjsDocument.data um layouts-Map (YjsDocumentData-Interface additiv erweitert + getLayouts-Getter); CADDocument um Layout-CRUD erweitert: getLayout/getAllLayouts/addLayout/updateLayout (Top-Level ersetzt, viewport + center SHALLOW gemergt — center/scale einzeln änderbar, frameBlockRef bleibt bei reinem name-Patch erhalten)/deleteLayout (still bei unbekannter ID); Y.UndoManager um layouts-Map getrackt → jedes add/update/delete = eigener Undo-Schritt (2-Schritte-Undo-Test: add→delete→undo→undo verifiziert); Persistence läuft automatisch über bestehende drawing-json-Serialisierung (Y-Doc-Snapshot) | Frontend: 557/557 Tests (+6 layouts.test.ts), tsc 0, Build EXIT 0 | e750a45 | G1 COMPLETE — Datenmodell steht; nächster Task G2 (LayoutBar UI + Viewport-Widget) |
|
||||
@@ -735,5 +736,5 @@ Format: `YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/of
|
||||
- `frontend/src/utils/__tests__/format.test.ts` liegt in src statt tests/ — vereinheitlichen (kleiner Task in I2)
|
||||
|
||||
---
|
||||
*Nächster offener Schritt: **G4** (PDF-Export je Layout im Ausgabemaßstab: pdf-lib, Linienbreiten aus Layern, monochrome Option). Danach: G5 Print-Stylesheet, Phase H, Rest-D-Tools.*
|
||||
*Status 2026-08-29: Phasen B+A+C+D+E+F komplett, G1+G2+G3 fertig (Layouts: Datenmodell + Bar + Titelblock), BUG-1-Kollisionsklasse geschlossen. FE 574/574 Tests, BE 272/272, tsc/Build grün. Alle Commits auf Forgejo main.*
|
||||
*Nächster offener Schritt: **G5** (Browser-Print stylesheet: @media print nur Layout). Danach: Rest-D-Tools (D1–D4/D9/D10-path/D11/D12), Phase H (Modi/Templates/Rollen/i18n/Flächen), Phase I (Release).*
|
||||
*Status 2026-08-29: Phasen B+A+C+D+E+F komplett, G1–G4 fertig (Layouts: Datenmodell, Bar, Titelblock, PDF-Export), BUG-1-Kollisionsklasse geschlossen. FE 580/580 Tests, BE 272/272, tsc/Build grün. Alle Commits auf Forgejo main.*
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
*/
|
||||
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
|
||||
import type { CADElement, CADLayer, ProjectData } from '../types/cad.types';
|
||||
import { elementToPrimitives, type ShapePrimitive } from '../render/pixi/elementDrawers';
|
||||
import type { CADDocument } from '../kernel/document/CADDocument';
|
||||
|
||||
export interface LayoutPDFOptions {
|
||||
/** Alles in Schwarz drucken (Task G4). */
|
||||
monochrome?: boolean;
|
||||
/** Seitenränder in pt (Default 20). */
|
||||
margin?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export project data to a PDF byte array.
|
||||
@@ -172,3 +181,196 @@ function hexToRgb(hex: string | undefined): ReturnType<typeof rgb> | null {
|
||||
const b = parseInt(h.substring(4, 6), 16) / 255;
|
||||
return rgb(r, g, b);
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Task G4 — PDF-Export je Layout im Ausgabemaßstab
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/** A4 landscape in Punkten. */
|
||||
const A4_LANDSCAPE_W = 842;
|
||||
const A4_LANDSCAPE_H = 595;
|
||||
|
||||
/**
|
||||
* Exportiert EIN Layout als PDF (A4 landscape).
|
||||
*
|
||||
* Der gespeicherte Viewport (center/scale) definiert den sichtbaren
|
||||
* Welt-Ausschnitt; die Zeichnung wird in den Ausgabemaßstab skaliert
|
||||
* (Welt-pt × viewport.scale). Geometrie läuft über elementToPrimitives
|
||||
* (C2) — identische Konventionen wie Pixi-Renderer (circle el.x/el.y =
|
||||
* Zentrum). Linienbreiten: properties.strokeWidth, andernfalls Linientyp
|
||||
* aus dem Layer (solid 0.7pt, dashed 0.9pt, dotted 0.5pt) mit passenden
|
||||
* Dash-Patterns. `monochrome` erzwingt Schwarz. Der Titelblock aus
|
||||
* layout.frameBlockRef wird mitgezeichnet (unskaliert als Rahmen unten
|
||||
* rechts, Elemente relativ zur Ecke).
|
||||
*/
|
||||
export async function exportLayoutPDF(
|
||||
doc: CADDocument,
|
||||
layoutId: string,
|
||||
options: LayoutPDFOptions = {},
|
||||
): Promise<Uint8Array> {
|
||||
const layout = doc.getLayout(layoutId);
|
||||
if (!layout) throw new Error(`exportLayoutPDF: Layout '${layoutId}' nicht gefunden`);
|
||||
|
||||
const margin = options.margin ?? 20;
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
const page = pdfDoc.addPage([A4_LANDSCAPE_W, A4_LANDSCAPE_H]);
|
||||
const mono = options.monochrome === true;
|
||||
|
||||
const layers = doc.getAllLayers();
|
||||
const visibleLayerIds = new Set(layers.filter((l) => l.visible).map((l) => l.id));
|
||||
const layerById = new Map(layers.map((l) => [l.id, l]));
|
||||
|
||||
// ── Ausgabemaßstab: Welt → pt ──
|
||||
const scale = layout.viewport.scale;
|
||||
const viewW = (A4_LANDSCAPE_W - 2 * margin) / scale;
|
||||
const viewH = (A4_LANDSCAPE_H - 2 * margin) / scale;
|
||||
const worldMinX = layout.viewport.center.x - viewW / 2;
|
||||
const worldMinY = layout.viewport.center.y - viewH / 2;
|
||||
|
||||
// PDF-Y-Achse zeigt nach OBEN (bottom-left origin) — CAD nach unten:
|
||||
const toPage = (wx: number, wy: number): { x: number; y: number } => ({
|
||||
x: margin + (wx - worldMinX) * scale,
|
||||
y: A4_LANDSCAPE_H - margin - (wy - worldMinY) * scale,
|
||||
});
|
||||
|
||||
const colorFor = (el: CADElement): ReturnType<typeof rgb> => {
|
||||
if (mono) return rgb(0, 0, 0);
|
||||
const layer = layerById.get(el.layerId);
|
||||
const hex = (el.properties.stroke as string | undefined) ?? layer?.color;
|
||||
return hexToRgb(hex) ?? rgb(0, 0, 0);
|
||||
};
|
||||
|
||||
const lineWidthFor = (el: CADElement): number => {
|
||||
const explicit = el.properties.strokeWidth as number | undefined;
|
||||
if (typeof explicit === 'number' && explicit > 0) return explicit;
|
||||
const layer = layerById.get(el.layerId);
|
||||
switch (layer?.lineType) {
|
||||
case 'dashed': return 0.9;
|
||||
case 'dotted': return 0.5;
|
||||
default: return 0.7;
|
||||
}
|
||||
};
|
||||
|
||||
const dashFor = (el: CADElement): number[] | undefined => {
|
||||
if (mono) return undefined;
|
||||
const layer = layerById.get(el.layerId);
|
||||
if (layer?.lineType === 'dashed') return [4, 3];
|
||||
if (layer?.lineType === 'dotted') return [0.5, 2];
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const drawPrimitives = (els: CADElement[], transform: (wx: number, wy: number) => { x: number; y: number }, scaleOverride?: number): void => {
|
||||
for (const el of els) {
|
||||
if (!visibleLayerIds.has(el.layerId)) continue;
|
||||
const prims = elementToPrimitives(el);
|
||||
const lw = lineWidthFor(el) * (scaleOverride ?? scale);
|
||||
const dash = dashFor(el);
|
||||
const color = colorFor(el);
|
||||
for (const prim of prims) {
|
||||
drawPrimitive(page, prim, transform, lw, color, dash, font, mono);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ── Modellinhalt ──
|
||||
drawPrimitives(doc.getAllElements(), toPage);
|
||||
|
||||
// ── Titelblock (frameBlockRef) unten rechts, unskaliert ──
|
||||
if (layout.frameBlockRef) {
|
||||
const block = doc.getBlock(layout.frameBlockRef);
|
||||
if (block) {
|
||||
const FRAME_MARGIN = 12;
|
||||
// Block-Elemente liegen um (0,0) herum — an Ecke unten rechts verschieben
|
||||
const frameOriginX = A4_LANDSCAPE_W - margin - 190 - FRAME_MARGIN;
|
||||
const frameOriginY = margin + FRAME_MARGIN;
|
||||
const frameTransform = (wx: number, wy: number) => ({
|
||||
x: frameOriginX + wx,
|
||||
y: frameOriginY + wy,
|
||||
});
|
||||
drawPrimitives(block.elements, frameTransform, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return pdfDoc.save();
|
||||
}
|
||||
|
||||
/** Zeichnet ein einzelnes ShapePrimitive auf die PDF-Seite. */
|
||||
function drawPrimitive(
|
||||
page: any,
|
||||
prim: ShapePrimitive,
|
||||
transform: (wx: number, wy: number) => { x: number; y: number },
|
||||
lineWidth: number,
|
||||
color: ReturnType<typeof rgb>,
|
||||
dash: number[] | undefined,
|
||||
font: any,
|
||||
mono: boolean,
|
||||
): void {
|
||||
switch (prim.kind) {
|
||||
case 'line': {
|
||||
const s = transform(prim.from.x, prim.from.y);
|
||||
const e = transform(prim.to.x, prim.to.y);
|
||||
page.drawLine({ start: s, end: e, thickness: lineWidth, color, dashArray: dash });
|
||||
break;
|
||||
}
|
||||
case 'rect': {
|
||||
const tl = transform(prim.x, prim.y + prim.h);
|
||||
page.drawRectangle({
|
||||
x: tl.x, y: tl.y, width: prim.w, height: prim.h,
|
||||
borderColor: color, borderWidth: lineWidth,
|
||||
...(prim.fill ? { color: hexToRgb(prim.fill) ?? color } : {}),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'circle': {
|
||||
const c = transform(prim.center.x, prim.center.y);
|
||||
page.drawCircle({
|
||||
x: c.x, y: c.y, radius: prim.radius,
|
||||
borderColor: color, borderWidth: lineWidth,
|
||||
...(prim.fill ? { color: hexToRgb(prim.fill) ?? color } : {}),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'arc': {
|
||||
// Arc → 32 Liniensegmente (Grad, 0° = 3 Uhr, CCW — CAD-Konvention)
|
||||
const c = transform(prim.center.x, prim.center.y);
|
||||
const SEG = 32;
|
||||
const a0 = (prim.startDeg * Math.PI) / 180;
|
||||
const a1 = (prim.endDeg * Math.PI) / 180;
|
||||
let span = a1 - a0;
|
||||
if (span <= 0) span += 2 * Math.PI;
|
||||
let prev = { x: c.x + Math.cos(a0) * prim.radius, y: c.y + Math.sin(a0) * prim.radius };
|
||||
for (let i = 1; i <= SEG; i++) {
|
||||
const a = a0 + (span * i) / SEG;
|
||||
const pt = { x: c.x + Math.cos(a) * prim.radius, y: c.y + Math.sin(a) * prim.radius };
|
||||
page.drawLine({ start: prev, end: pt, thickness: lineWidth, color, dashArray: dash });
|
||||
prev = pt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'polyline': {
|
||||
for (let i = 0; i < prim.points.length - 1; i++) {
|
||||
const s = transform(prim.points[i].x, prim.points[i].y);
|
||||
const e = transform(prim.points[i + 1].x, prim.points[i + 1].y);
|
||||
page.drawLine({ start: s, end: e, thickness: lineWidth, color, dashArray: dash });
|
||||
}
|
||||
if (prim.close && prim.points.length > 2) {
|
||||
const s = transform(prim.points[prim.points.length - 1].x, prim.points[prim.points.length - 1].y);
|
||||
const e = transform(prim.points[0].x, prim.points[0].y);
|
||||
page.drawLine({ start: s, end: e, thickness: lineWidth, color, dashArray: dash });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'text': {
|
||||
const at = transform(prim.at.x, prim.at.y);
|
||||
page.drawText(prim.text, {
|
||||
x: at.x, y: at.y - prim.fontSize,
|
||||
size: prim.fontSize,
|
||||
font,
|
||||
color: mono ? rgb(0, 0, 0) : (hexToRgb(prim.color) ?? color),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Task G4 – PDF-Export je Layout im Ausgabemaßstab.
|
||||
*
|
||||
* exportLayoutPDF rendert den Layout-Viewport (center/scale) auf eine A4-
|
||||
* Seite (landscape), skaliert Weltkoordinaten in den Ausgabemaßstab,
|
||||
* berücksichtigt Linienbreiten aus Layern (lineType) und unterstützt
|
||||
* eine monochrome Option. Titelblock (frameBlockRef) wird mitgezeichnet.
|
||||
* Rückgabe: PDF-Bytes, per PDFDocument.load verifizierbar.
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { CADDocument } from '../src/kernel/document/CADDocument';
|
||||
import { createInMemoryDoc } from './helpers/inMemoryDoc';
|
||||
import { exportLayoutPDF } from '../src/services/pdfExport';
|
||||
import type { CADElement, LayoutDefinition } from '../src/types/cad.types';
|
||||
|
||||
function line(id: string, x1: number, y1: number, x2: number, y2: number, layerId = 'layer-0'): CADElement {
|
||||
return {
|
||||
id, type: 'line', layerId,
|
||||
x: Math.min(x1, x2), y: Math.min(y1, y2),
|
||||
width: Math.abs(x2 - x1), height: Math.abs(y2 - y1),
|
||||
properties: { x1, y1, x2, y2, stroke: '#ff0000' },
|
||||
} as CADElement;
|
||||
}
|
||||
|
||||
function mkDoc(els: CADElement[], layouts: LayoutDefinition[]): CADDocument {
|
||||
const { ydoc } = createInMemoryDoc();
|
||||
const doc = new CADDocument(ydoc);
|
||||
for (const e of els) doc.addElement(e);
|
||||
for (const l of layouts) doc.addLayout(l);
|
||||
return doc;
|
||||
}
|
||||
|
||||
const LAYOUT: LayoutDefinition = {
|
||||
id: 'layout-1', name: 'A4 Plan',
|
||||
viewport: { center: { x: 50, y: 50 }, scale: 1 },
|
||||
};
|
||||
|
||||
describe('G4: exportLayoutPDF', () => {
|
||||
it('liefert valides PDF mit genau einer A4-landscape-Seite', async () => {
|
||||
const doc = mkDoc([line('l1', 0, 0, 100, 100)], [LAYOUT]);
|
||||
const bytes = await exportLayoutPDF(doc, 'layout-1');
|
||||
expect(bytes.byteLength).toBeGreaterThan(500);
|
||||
const pdf = await PDFDocument.load(bytes);
|
||||
expect(pdf.getPageCount()).toBe(1);
|
||||
const { width, height } = pdf.getPage(0).getSize();
|
||||
expect(width).toBe(842); // A4 landscape pt
|
||||
expect(height).toBe(595);
|
||||
});
|
||||
|
||||
it('Titelblock-Elemente (frameBlockRef) werden mitgezeichnet', async () => {
|
||||
const doc = mkDoc([line('l1', 0, 0, 100, 0)], [LAYOUT]);
|
||||
// Titelblock-Definition anlegen und referenzieren
|
||||
const blockId = 'title-1';
|
||||
doc.addBlock({
|
||||
id: blockId, name: 'Titelblock', description: '', category: 'frame',
|
||||
elements: [line('tb1', 0, 0, 50, 0)],
|
||||
});
|
||||
doc.updateLayout('layout-1', { frameBlockRef: blockId });
|
||||
const bytes = await exportLayoutPDF(doc, 'layout-1');
|
||||
const pdf = await PDFDocument.load(bytes);
|
||||
expect(pdf.getPageCount()).toBe(1);
|
||||
// Strukturelle Prüfung: Export läuft fehlerfrei mit Frame-Block
|
||||
expect(bytes.byteLength).toBeGreaterThan(500);
|
||||
});
|
||||
|
||||
it('monochrome Option exportiert ohne Farbinformationen zu verlieren', async () => {
|
||||
const doc = mkDoc([line('l1', 0, 0, 100, 100)], [LAYOUT]);
|
||||
const bytes = await exportLayoutPDF(doc, 'layout-1', { monochrome: true });
|
||||
const pdf = await PDFDocument.load(bytes);
|
||||
expect(pdf.getPageCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('leeres Layout (keine Elemente im Viewport) exportiert trotzdem eine Seite', async () => {
|
||||
const doc = mkDoc([], [LAYOUT]);
|
||||
const bytes = await exportLayoutPDF(doc, 'layout-1');
|
||||
const pdf = await PDFDocument.load(bytes);
|
||||
expect(pdf.getPageCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('Layout ohne existierende ID → throw', async () => {
|
||||
const doc = mkDoc([], []);
|
||||
await expect(exportLayoutPDF(doc, 'gibts-nicht')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('unsichtbare Layer werden übersprungen (Export läuft fehlerfrei)', async () => {
|
||||
const { ydoc } = createInMemoryDoc();
|
||||
const doc = new CADDocument(ydoc);
|
||||
ydoc.data.layers.set('layer-0', {
|
||||
id: 'layer-0', name: '0', visible: false, locked: false,
|
||||
color: '#ffffff', lineType: 'solid', transparency: 0, sortOrder: 0, parentId: null,
|
||||
});
|
||||
doc.addElement(line('l1', 0, 0, 100, 100));
|
||||
doc.addLayout(LAYOUT);
|
||||
const bytes = await exportLayoutPDF(doc, 'layout-1');
|
||||
const pdf = await PDFDocument.load(bytes);
|
||||
expect(pdf.getPageCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user