lsa-planer

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

/ src render canvasSurface.ts

4,2 KB Rohdatei
src/render/canvasSurface.ts — 136 Zeilen
1 import type { Surface, TextStyle } from './surface';
2
3 /** Zeichenflaeche auf einem HTML-Canvas. */
4 export class CanvasSurface implements Surface {
5 readonly width: number;
6 readonly height: number;
7
8 constructor(
9 private readonly ctx: CanvasRenderingContext2D,
10 width: number,
11 height: number,
12 private readonly fontFamily = 'system-ui, "Segoe UI", Roboto, Arial, sans-serif',
13 ) {
14 this.width = width;
15 this.height = height;
16 }
17
18 /**
19 * Legt einen Canvas in der Geraeteaufloesung an, damit die Darstellung auf
20 * hochaufloesenden Bildschirmen scharf bleibt. Der Altbestand zeichnete in
21 * CSS-Pixeln; Beschriftungen waren dadurch unscharf.
22 */
23 static create(canvas: HTMLCanvasElement, width: number, height: number): CanvasSurface | null {
24 const ratio = Math.min(3, Math.max(1, window.devicePixelRatio || 1));
25 canvas.width = Math.round(width * ratio);
26 canvas.height = Math.round(height * ratio);
27 canvas.style.width = `${width}px`;
28 canvas.style.height = `${height}px`;
29
30 const ctx = canvas.getContext('2d');
31 if (!ctx) return null;
32 ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
33 ctx.clearRect(0, 0, width, height);
34 return new CanvasSurface(ctx, width, height);
35 }
36
37 rect(x: number, y: number, width: number, height: number, fill: string): void {
38 this.ctx.fillStyle = fill;
39 this.ctx.fillRect(x, y, width, height);
40 }
41
42 polygon(points: readonly { readonly x: number; readonly y: number }[], fill: string): void {
43 if (points.length < 3) return;
44 const [erster, ...weitere] = points;
45 if (erster === undefined) return;
46 this.ctx.beginPath();
47 this.ctx.moveTo(erster.x, erster.y);
48 for (const punkt of weitere) this.ctx.lineTo(punkt.x, punkt.y);
49 this.ctx.closePath();
50 this.ctx.fillStyle = fill;
51 this.ctx.fill();
52 }
53
54 strokeRect(
55 x: number,
56 y: number,
57 width: number,
58 height: number,
59 color: string,
60 lineWidth = 1,
61 ): void {
62 this.ctx.strokeStyle = color;
63 this.ctx.lineWidth = lineWidth;
64 // Der halbe Pixel-Versatz haelt duenne Linien scharf.
65 const offset = lineWidth % 2 === 1 ? 0.5 : 0;
66 this.ctx.strokeRect(x + offset, y + offset, width, height);
67 }
68
69 line(x1: number, y1: number, x2: number, y2: number, color: string, lineWidth = 1): void {
70 this.ctx.save();
71 this.ctx.setLineDash([]);
72 this.drawLine(x1, y1, x2, y2, color, lineWidth);
73 this.ctx.restore();
74 }
75
76 circle(x: number, y: number, radius: number, fill: string, stroke: string, lineWidth = 1): void {
77 if (radius <= 0) return;
78 this.ctx.save();
79 this.ctx.setLineDash([]);
80 this.ctx.beginPath();
81 this.ctx.arc(x, y, radius, 0, Math.PI * 2);
82 if (fill !== '') {
83 this.ctx.fillStyle = fill;
84 this.ctx.fill();
85 }
86 if (stroke !== '') {
87 this.ctx.strokeStyle = stroke;
88 this.ctx.lineWidth = lineWidth;
89 this.ctx.stroke();
90 }
91 this.ctx.restore();
92 }
93
94 dashedLine(x1: number, y1: number, x2: number, y2: number, color: string, lineWidth = 1): void {
95 this.ctx.save();
96 this.ctx.setLineDash([3, 3]);
97 this.drawLine(x1, y1, x2, y2, color, lineWidth);
98 this.ctx.restore();
99 }
100
101 private drawLine(
102 x1: number,
103 y1: number,
104 x2: number,
105 y2: number,
106 color: string,
107 lineWidth: number,
108 ): void {
109 const offset = lineWidth % 2 === 1 ? 0.5 : 0;
110 this.ctx.strokeStyle = color;
111 this.ctx.lineWidth = lineWidth;
112 this.ctx.beginPath();
113 this.ctx.moveTo(x1 + offset, y1 + offset);
114 this.ctx.lineTo(x2 + offset, y2 + offset);
115 this.ctx.stroke();
116 }
117
118 text(value: string, x: number, y: number, style: TextStyle): void {
119 this.ctx.font = this.font(style);
120 this.ctx.fillStyle = style.color ?? '#000000';
121 this.ctx.textAlign =
122 style.align === 'mitte' ? 'center' : style.align === 'rechts' ? 'right' : 'left';
123 this.ctx.textBaseline =
124 style.baseline === 'mitte' ? 'middle' : style.baseline === 'unten' ? 'alphabetic' : 'top';
125 this.ctx.fillText(value, x, y);
126 }
127
128 measureText(value: string, style: TextStyle): number {
129 this.ctx.font = this.font(style);
130 return this.ctx.measureText(value).width;
131 }
132
133 private font(style: TextStyle): string {
134 return `${style.bold === true ? '600 ' : ''}${style.size}px ${this.fontFamily}`;
135 }
136 }