lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests export alphaAufPapier.test.ts

8,4 KB Rohdatei
tests/export/alphaAufPapier.test.ts — 189 Zeilen
1 import { readFileSync } from 'node:fs';
2 import { jsPDF } from 'jspdf';
3 import { describe, expect, it, vi } from 'vitest';
4 import { farbanteil, halbdurchsichtig } from '@/render/farbe';
5 import { PdfSurface } from '@/render/pdfSurface';
6
7 /**
8 * Halbdurchsichtige Farben auf Papier.
9 *
10 * BEFUND AUS DEM BETRIEB (11.09.2026, mit Ausdruck): Das Lageplanblatt druckte
11 * den Arbeitsbereich als SCHWARZEN KLOTZ und die Haltlinien als schwarze
12 * Balken. Beide sind auf dem Bildschirm blass - `#c2410c22` und `#b3060455`,
13 * also achtstellige Farben mit Alphakanal. Der Bildschirm kann das, jsPDF
14 * nicht: `setDrawColor` versteht die achte Stelle nicht und faellt auf Schwarz
15 * zurueck. Aus einer 13 Prozent deckenden Flaeche wurde die deckendste, die es
16 * gibt, und sie verdeckte gerade die Fahrlinien, um die es auf dem Blatt geht.
17 *
18 * Der Fehler ist AELTER als der Arbeitsbereich - die Haltlinien traf er, seit
19 * es das Blatt gibt. Er fiel nur nicht auf, weil ein schwarzer Strich wie ein
20 * gewollter schwarzer Strich aussieht; eine schwarze Flaeche tut das nicht.
21 *
22 * ZWEITE BEHEBUNG (Fassung 5.40.0). Die erste verrechnete die Farbe ueber
23 * Weiss und gab das Ergebnis deckend an jsPDF. Das war richtig, solange unter
24 * der Zeichnung nur Papier liegt. Seit dem Luftbildblatt liegt dort ein Bild,
25 * und eine deckende blasse Flaeche verdeckt es vollstaendig - derselbe Fehler,
26 * nur umgefaerbt. jsPDF KANN Transparenz, ueber den Grafikzustand; das war
27 * beim ersten Mal nicht nachgesehen worden. Diese Datei bewacht seitdem den
28 * einen Mechanismus, der beide Faelle traegt.
29 */
30
31 describe('Eine Farbe wird in Grundfarbe und Deckung zerlegt', () => {
32 it('laesst eine gewoehnliche Farbe unangetastet und voll deckend', () => {
33 expect(farbanteil('#1b2230')).toEqual({ grund: '#1b2230', deckung: 1 });
34 expect(farbanteil('#000000')).toEqual({ grund: '#000000', deckung: 1 });
35 });
36
37 it('trennt die achte Stelle ab', () => {
38 expect(farbanteil('#c2410cff')).toEqual({ grund: '#c2410c', deckung: 1 });
39 expect(farbanteil('#c2410c00')).toEqual({ grund: '#c2410c', deckung: 0 });
40 });
41
42 it('liest die Deckung des Arbeitsbereichs als rund 13 Prozent', () => {
43 const { grund, deckung } = farbanteil('#c2410c22');
44 expect(grund).toBe('#c2410c');
45 expect(deckung).toBeCloseTo(0.133, 3);
46 });
47
48 it('liest die Deckung der Haltlinie als rund 33 Prozent', () => {
49 // Der aeltere Teil desselben Fehlers.
50 expect(farbanteil('#b3060455').deckung).toBeCloseTo(0.333, 3);
51 });
52
53 it('laesst eine unbrauchbare Angabe durch, statt sie zu erfinden', () => {
54 // Was nicht als Farbe mit Alphakanal zu lesen ist, geht unveraendert an
55 // jsPDF - dort ist die Behandlung unbekannter Angaben zu Hause und nicht
56 // hier.
57 expect(farbanteil('rot')).toEqual({ grund: 'rot', deckung: 1 });
58 expect(farbanteil('#abc')).toEqual({ grund: '#abc', deckung: 1 });
59 });
60
61 it('beantwortet die Frage nach dem Alphakanal fuer die Zeichnung mit', () => {
62 // Die Zeichenbeschreibung des Blattes braucht dieselbe Auskunft: Sie
63 // unterlegt nur DECKENDE Striche (siehe lageplanDrawing.ts).
64 expect(halbdurchsichtig('#c2410c22')).toBe(true);
65 expect(halbdurchsichtig('#b3060455')).toBe(true);
66 expect(halbdurchsichtig('#1b2230')).toBe(false);
67 expect(halbdurchsichtig('#c2410cff')).toBe(false);
68 });
69 });
70
71 /** Ein Dokument in der Einrichtung der Planunterlage. */
72 function dokument(): jsPDF {
73 return new jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4' });
74 }
75
76 function alsText(doc: jsPDF): string {
77 return Buffer.from(doc.output('arraybuffer')).toString('latin1');
78 }
79
80 describe('Die Deckung erreicht das PDF als Grafikzustand', () => {
81 it('schreibt fuer den Arbeitsbereich einen Zustand mit rund 13 Prozent', () => {
82 const doc = dokument();
83 const flaeche = new PdfSurface(doc, 36, 36, 700, 300);
84 flaeche.line(10, 10, 300, 10, '#c2410c22', 12);
85 const text = alsText(doc);
86 expect(text).toContain('/ExtGState');
87 expect(text, 'Fuellung halbdurchsichtig').toMatch(/\/ca 0\.13/);
88 expect(text, 'Strich halbdurchsichtig').toMatch(/\/CA 0\.13/);
89 });
90
91 it('laesst ein Dokument ohne halbdurchsichtige Farbe ganz ohne Grafikzustand', () => {
92 // Sonst traegt jede Unterlage einen Eintrag mit, den sie nicht braucht.
93 const doc = dokument();
94 const flaeche = new PdfSurface(doc, 36, 36, 700, 300);
95 flaeche.line(10, 10, 300, 10, '#1b2230', 2);
96 flaeche.text('Zufahrt Nord', 10, 40, { size: 8, color: '#1b2230' });
97 flaeche.rect(10, 50, 40, 20, '#c8cfdb');
98 expect(alsText(doc)).not.toContain('/ExtGState');
99 });
100
101 it('gibt jsPDF die Farbe ohne die achte Stelle', () => {
102 // Sonst faellt jsPDF wieder auf Schwarz zurueck - der urspruengliche
103 // Befund.
104 const doc = dokument();
105 const gesetzt: unknown[] = [];
106 vi.spyOn(doc, 'setDrawColor').mockImplementation((...args: unknown[]) => {
107 gesetzt.push(args[0]);
108 return doc;
109 });
110 new PdfSurface(doc, 36, 36, 700, 300).line(10, 10, 300, 10, '#c2410c22', 12);
111 expect(gesetzt).toEqual(['#c2410c']);
112 });
113
114 it('setzt den Grafikzustand danach wieder auf deckend zurueck', () => {
115 /*
116 * DIE GEFAEHRLICHE RICHTUNG DIESES UMBAUS: Bliebe der Zustand stehen, waere
117 * der GANZE Rest des Dokuments halbdurchsichtig - Zwischenzeitenmatrix,
118 * Signalzeitenplan, Unterschriftenfeld. Der Ausdruck saehe aus wie ein
119 * Entwurf, und niemand kaeme auf das Lageplanblatt als Ursache.
120 */
121 const doc = dokument();
122 const stufen: number[] = [];
123 vi.spyOn(doc, 'setGState').mockImplementation((zustand: unknown) => {
124 stufen.push((zustand as { opacity: number }).opacity);
125 return doc;
126 });
127 const flaeche = new PdfSurface(doc, 36, 36, 700, 300);
128 flaeche.line(10, 10, 300, 10, '#c2410c22', 12);
129 flaeche.line(10, 20, 300, 20, '#1b2230', 2);
130 expect(stufen, 'gesetzt, dann zurueckgenommen').toEqual([0x22 / 255, 1]);
131 });
132
133 it('nimmt den Zustand auch zurueck, wenn das Zeichnen fehlschlaegt', () => {
134 const doc = dokument();
135 const stufen: number[] = [];
136 vi.spyOn(doc, 'setGState').mockImplementation((zustand: unknown) => {
137 stufen.push((zustand as { opacity: number }).opacity);
138 return doc;
139 });
140 vi.spyOn(doc, 'line').mockImplementation(() => {
141 throw new Error('Ausgabe abgebrochen');
142 });
143 const flaeche = new PdfSurface(doc, 36, 36, 700, 300);
144 expect(() => {
145 flaeche.line(10, 10, 300, 10, '#c2410c22', 12);
146 }).toThrow('Ausgabe abgebrochen');
147 expect(stufen.at(-1), 'zuletzt wieder deckend').toBe(1);
148 });
149 });
150
151 describe('Keine Zeichenfarbe erreicht jsPDF mit Alphakanal', () => {
152 it('gibt jede Farbstelle der PDF-Flaeche die zerlegte Grundfarbe weiter', () => {
153 /*
154 * ZWEI ZULAESSIGE SCHREIBWEISEN, und beide zerlegen.
155 *
156 * Der Regelfall ist `setXColor(grund)`: `mitDeckung` hat die Farbe zerlegt
157 * und reicht die Grundfarbe herein. Seit dem Kreisbefehl (Fassung 5.42.0)
158 * gibt es einen zweiten Fall - `circle` traegt ZWEI Farben, Fuellung und
159 * Umriss, und `mitDeckung` fuehrt nur eine. Die zweite wird an Ort und
160 * Stelle ueber `farbanteil(...).grund` zerlegt.
161 *
162 * Was die Regel sachlich verlangt, ist in beiden Faellen erfuellt: Keine
163 * achtstellige Farbe erreicht jsPDF. Geprueft wird die Form, weil sie sich
164 * pruefen laesst - und kommt eine zweite Form hinzu, gehoert sie hierher
165 * und nicht als Ausnahmevermerk in den Quelltext.
166 */
167 const quelle = readFileSync('src/render/pdfSurface.ts', 'utf8');
168 const ungefiltert = quelle
169 .split('\n')
170 .filter((zeile) => /set(Draw|Fill|Text)Color\(/.test(zeile))
171 .filter((zeile) => !/set(Draw|Fill|Text)Color\(grund\)/.test(zeile))
172 .filter((zeile) => !/set(Draw|Fill|Text)Color\(farbanteil\(\w+\)\.grund\)/.test(zeile));
173 expect(ungefiltert, 'Farbe ohne Zerlegung an jsPDF').toEqual([]);
174 });
175
176 it('gibt es diese Stellen ueberhaupt', () => {
177 const quelle = readFileSync('src/render/pdfSurface.ts', 'utf8');
178 const stellen = quelle.split('\n').filter((z) => /set(Draw|Fill|Text)Color\(/.test(z));
179 expect(stellen.length, 'keine Farbstelle gefunden').toBeGreaterThanOrEqual(5);
180 });
181
182 it('fuehren die Zeichenbeschreibungen weiterhin achtstellige Farben', () => {
183 // Sie sind erlaubt und noetig - der Bildschirm braucht sie. Dieser Fall
184 // haelt fest, dass es sie gibt, damit die beiden darueber etwas zu pruefen
185 // haben.
186 const quelle = readFileSync('src/render/lageplanDrawing.ts', 'utf8');
187 expect(quelle).toMatch(/#[0-9a-f]{6}[0-9a-f]{2}'/);
188 });
189 });