@twinfinity/printing 6.0.1-ci.28782-beta → 6.0.2-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/BuildInfo.js +3 -3
- package/dist/BuildInfo.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/layout/analyzeTemplate.js.map +1 -1
- package/dist/layout/clipUtils.js.map +1 -1
- package/dist/layout/colorConstants.js.map +1 -1
- package/dist/layout/elementKinds.js.map +1 -1
- package/dist/layout/exportDrawing.js.map +1 -1
- package/dist/layout/filters.js.map +1 -1
- package/dist/layout/geometryConstants.js.map +1 -1
- package/dist/layout/index.js.map +1 -1
- package/dist/layout/itemBuilders.js.map +1 -1
- package/dist/layout/labelsRender.js.map +1 -1
- package/dist/layout/legendConstants.js.map +1 -1
- package/dist/layout/legendRender.js.map +1 -1
- package/dist/layout/legendRows.js.map +1 -1
- package/dist/layout/legendUtils.js.map +1 -1
- package/dist/layout/metadata.js.map +1 -1
- package/dist/layout/model.js.map +1 -1
- package/dist/layout/options.js.map +1 -1
- package/dist/layout/presets.js.map +1 -1
- package/dist/layout/qrRender.js.map +1 -1
- package/dist/layout/renderDrawing.js.map +1 -1
- package/dist/layout/renderUtils.js.map +1 -1
- package/dist/layout/sectionValidation.js.map +1 -1
- package/dist/layout/templateVariables.js.map +1 -1
- package/dist/layout/typeGuards.js.map +1 -1
- package/dist/layout/types.js.map +1 -1
- package/dist/layout/utils.js.map +1 -1
- package/dist/layout/validation.js.map +1 -1
- package/dist/layout/validationHelpers.js.map +1 -1
- package/dist/layout/validationUtils.js.map +1 -1
- package/dist/layout/viewportPrep.js.map +1 -1
- package/dist/layout/viewportRender.js.map +1 -1
- package/dist/layout/viewportUtils.js.map +1 -1
- package/dist/layout/warnings.js.map +1 -1
- package/dist/printToPdf.js.map +1 -1
- package/dist/printToSvg.js.map +1 -1
- package/dist/sections.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/BuildInfo.ts +41 -41
- package/src/index.ts +10 -10
- package/src/layout/analyzeTemplate.ts +218 -218
- package/src/layout/clipUtils.ts +193 -193
- package/src/layout/colorConstants.ts +16 -16
- package/src/layout/elementKinds.ts +35 -35
- package/src/layout/exportDrawing.ts +206 -206
- package/src/layout/filters.ts +80 -80
- package/src/layout/geometryConstants.ts +11 -11
- package/src/layout/index.ts +671 -671
- package/src/layout/itemBuilders.ts +159 -159
- package/src/layout/labelsRender.ts +170 -170
- package/src/layout/legendConstants.ts +11 -11
- package/src/layout/legendRender.ts +543 -543
- package/src/layout/legendRows.ts +62 -62
- package/src/layout/legendUtils.ts +144 -144
- package/src/layout/metadata.ts +210 -210
- package/src/layout/model.ts +372 -372
- package/src/layout/options.ts +17 -17
- package/src/layout/presets.ts +51 -51
- package/src/layout/qrRender.ts +126 -126
- package/src/layout/qrcode.d.ts +2 -2
- package/src/layout/renderDrawing.ts +710 -710
- package/src/layout/renderUtils.ts +138 -138
- package/src/layout/sectionValidation.ts +2 -2
- package/src/layout/templateVariables.ts +317 -317
- package/src/layout/typeGuards.ts +9 -9
- package/src/layout/types.ts +835 -835
- package/src/layout/utils.ts +85 -85
- package/src/layout/validation.ts +392 -392
- package/src/layout/validationHelpers.ts +59 -59
- package/src/layout/validationUtils.ts +6 -6
- package/src/layout/viewportPrep.ts +351 -351
- package/src/layout/viewportRender.ts +442 -442
- package/src/layout/viewportUtils.ts +50 -50
- package/src/layout/warnings.ts +3 -3
- package/src/printToPdf.ts +108 -108
- package/src/printToSvg.ts +188 -188
- package/src/sections.ts +1019 -1019
- package/src/types.ts +49 -49
package/src/printToSvg.ts
CHANGED
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
import type { SlabSectionGeometry, Vec3 } from '@twinfinity/geometry';
|
|
2
|
-
import type { PrintToSvgOptions, SvgRenderStyles, SvgPlaneBasis } from './types';
|
|
3
|
-
import { DEFAULT_SVG_STYLES } from './types';
|
|
4
|
-
|
|
5
|
-
type Pt2 = { x: number; y: number };
|
|
6
|
-
type Bounds = { minX: number; maxX: number; minY: number; maxY: number };
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Render SlabSectionGeometry to an SVG string.
|
|
10
|
-
*
|
|
11
|
-
* @param geometry The computed slab section geometry
|
|
12
|
-
* @param options Rendering options (viewBox, styles, planeBasis, etc.)
|
|
13
|
-
* @returns Complete SVG string
|
|
14
|
-
*/
|
|
15
|
-
export function printToSvg(geometry: SlabSectionGeometry, options: PrintToSvgOptions = {}): string {
|
|
16
|
-
if (!geometry) throw new Error('geometry is required');
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
options.boundsPadding !== undefined &&
|
|
20
|
-
(typeof options.boundsPadding !== 'number' || options.boundsPadding < 0)
|
|
21
|
-
) {
|
|
22
|
-
throw new Error('boundsPadding must be a non-negative number');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (options.viewBox) {
|
|
26
|
-
if (!Array.isArray(options.viewBox) || options.viewBox.length !== 4) {
|
|
27
|
-
throw new Error('viewBox must be an array of 4 numbers [minX, minY, width, height]');
|
|
28
|
-
}
|
|
29
|
-
for (let i = 0; i < 4; i++) {
|
|
30
|
-
if (typeof options.viewBox[i] !== 'number' || !Number.isFinite(options.viewBox[i])) {
|
|
31
|
-
throw new Error(`viewBox[${i}] must be a finite number`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const styles = mergeStyles(options.styles);
|
|
37
|
-
const includeXml = options.includeXmlDeclaration ?? true;
|
|
38
|
-
|
|
39
|
-
// Project all polylines to 2D once
|
|
40
|
-
const proj = {
|
|
41
|
-
hiddenLines: projectPolylines(geometry.hiddenLines, options.planeBasis),
|
|
42
|
-
outlineHidden: projectPolylines(geometry.outlineHidden, options.planeBasis),
|
|
43
|
-
visibleLines: projectPolylines(geometry.visibleLines, options.planeBasis),
|
|
44
|
-
outlineVisible: projectPolylines(geometry.outlineVisible, options.planeBasis),
|
|
45
|
-
silhouettes: projectPolylines(geometry.silhouettes, options.planeBasis),
|
|
46
|
-
featureEdges: projectPolylines(geometry.featureEdges, options.planeBasis),
|
|
47
|
-
cutLines: projectPolylines(geometry.cutLines, options.planeBasis),
|
|
48
|
-
stairArrows: projectPolylines(geometry.stairArrows, options.planeBasis)
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const bounds = options.bounds ?? computeBoundsFromProjected(proj);
|
|
52
|
-
const viewBox = options.viewBox ?? boundsToViewBox(bounds, options.boundsPadding ?? 0.05);
|
|
53
|
-
|
|
54
|
-
const parts: string[] = [];
|
|
55
|
-
|
|
56
|
-
if (includeXml) parts.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
57
|
-
|
|
58
|
-
const [minX, minY, width, height] = viewBox;
|
|
59
|
-
parts.push(
|
|
60
|
-
`<svg xmlns="http://www.w3.org/2000/svg" ` +
|
|
61
|
-
`viewBox="${minX} ${minY} ${width} ${height}" ` +
|
|
62
|
-
`width="${width}" height="${height}">`
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
if (options.backgroundColor) {
|
|
66
|
-
parts.push(
|
|
67
|
-
`<rect x="${minX}" y="${minY}" width="${width}" height="${height}" fill="${options.backgroundColor}"/>`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
parts.push('<g fill="none" stroke-linecap="round" stroke-linejoin="round">');
|
|
72
|
-
|
|
73
|
-
const categories: Array<[keyof typeof proj, keyof Required<SvgRenderStyles>]> = [
|
|
74
|
-
['hiddenLines', 'hiddenLines'],
|
|
75
|
-
['outlineHidden', 'outlineHidden'],
|
|
76
|
-
['visibleLines', 'visibleLines'],
|
|
77
|
-
['outlineVisible', 'outlineVisible'],
|
|
78
|
-
['silhouettes', 'silhouettes'],
|
|
79
|
-
['featureEdges', 'featureEdges'],
|
|
80
|
-
['cutLines', 'cutLines'],
|
|
81
|
-
['stairArrows', 'stairArrows']
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
for (const [key, styleKey] of categories) {
|
|
85
|
-
const style = styles[styleKey];
|
|
86
|
-
if (style.enabled) renderProjectedCategory(parts, proj[key], style, bounds);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
parts.push('</g>');
|
|
90
|
-
parts.push('</svg>');
|
|
91
|
-
return parts.join('\n');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function projectPolylines(polylines: Vec3[][], planeBasis?: SvgPlaneBasis): Pt2[][] {
|
|
95
|
-
const result: Pt2[][] = [];
|
|
96
|
-
for (const polyline of polylines) {
|
|
97
|
-
if (polyline.length === 0) continue;
|
|
98
|
-
const projected: Pt2[] = [];
|
|
99
|
-
for (const point of polyline) {
|
|
100
|
-
if (!Number.isFinite(point[0]) || !Number.isFinite(point[1]) || !Number.isFinite(point[2])) continue;
|
|
101
|
-
if (planeBasis) {
|
|
102
|
-
const dx = point[0] - planeBasis.origin.x;
|
|
103
|
-
const dy = point[1] - planeBasis.origin.y;
|
|
104
|
-
const dz = point[2] - planeBasis.origin.z;
|
|
105
|
-
projected.push({
|
|
106
|
-
x: dx * planeBasis.right.x + dy * planeBasis.right.y + dz * planeBasis.right.z,
|
|
107
|
-
y: dx * planeBasis.up.x + dy * planeBasis.up.y + dz * planeBasis.up.z
|
|
108
|
-
});
|
|
109
|
-
} else {
|
|
110
|
-
projected.push({ x: point[0], y: -point[2] });
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (projected.length > 0) result.push(projected);
|
|
114
|
-
}
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function computeBoundsFromProjected(categories: Record<string, Pt2[][]>): Bounds {
|
|
119
|
-
let minX = Infinity,
|
|
120
|
-
minY = Infinity,
|
|
121
|
-
maxX = -Infinity,
|
|
122
|
-
maxY = -Infinity;
|
|
123
|
-
for (const polylines of Object.values(categories)) {
|
|
124
|
-
for (const polyline of polylines) {
|
|
125
|
-
for (const pt of polyline) {
|
|
126
|
-
if (pt.x < minX) minX = pt.x;
|
|
127
|
-
if (pt.x > maxX) maxX = pt.x;
|
|
128
|
-
if (pt.y < minY) minY = pt.y;
|
|
129
|
-
if (pt.y > maxY) maxY = pt.y;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (!Number.isFinite(minX)) return { minX: 0, maxX: 1, minY: 0, maxY: 1 };
|
|
134
|
-
return { minX, maxX, minY, maxY };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function renderProjectedCategory(
|
|
138
|
-
parts: string[],
|
|
139
|
-
polylines: Pt2[][],
|
|
140
|
-
style: { stroke?: string; strokeWidth?: number; strokeDasharray?: string; opacity?: number },
|
|
141
|
-
bounds: Bounds
|
|
142
|
-
): void {
|
|
143
|
-
if (!polylines.length) return;
|
|
144
|
-
const pathData = projectedToPathData(polylines, bounds);
|
|
145
|
-
if (!pathData) return;
|
|
146
|
-
|
|
147
|
-
let pathElement = `<path d="${pathData}"`;
|
|
148
|
-
if (style.stroke) pathElement += ` stroke="${style.stroke}"`;
|
|
149
|
-
if (style.strokeWidth !== undefined) pathElement += ` stroke-width="${style.strokeWidth}"`;
|
|
150
|
-
if (style.strokeDasharray) pathElement += ` stroke-dasharray="${style.strokeDasharray}"`;
|
|
151
|
-
if (style.opacity !== undefined && style.opacity !== 1) pathElement += ` opacity="${style.opacity}"`;
|
|
152
|
-
pathElement += '/>';
|
|
153
|
-
parts.push(pathElement);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function projectedToPathData(polylines: Pt2[][], bounds: Bounds): string {
|
|
157
|
-
const parts: string[] = [];
|
|
158
|
-
const ox = bounds.minX;
|
|
159
|
-
const oy = bounds.maxY;
|
|
160
|
-
for (const polyline of polylines) {
|
|
161
|
-
if (polyline.length === 0) continue;
|
|
162
|
-
parts.push(`M ${(polyline[0].x - ox).toFixed(6)} ${(oy - polyline[0].y).toFixed(6)}`);
|
|
163
|
-
for (let i = 1; i < polyline.length; i++) {
|
|
164
|
-
parts.push(`L ${(polyline[i].x - ox).toFixed(6)} ${(oy - polyline[i].y).toFixed(6)}`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return parts.join(' ');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function boundsToViewBox(bounds: Bounds, padding: number): [number, number, number, number] {
|
|
171
|
-
const width = bounds.maxX - bounds.minX;
|
|
172
|
-
const height = bounds.maxY - bounds.minY;
|
|
173
|
-
const padAmount = Math.max(width, height) * padding;
|
|
174
|
-
return [-padAmount, -padAmount, width + padAmount * 2, height + padAmount * 2];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function mergeStyles(userStyles?: SvgRenderStyles): Required<SvgRenderStyles> {
|
|
178
|
-
return {
|
|
179
|
-
cutLines: { ...DEFAULT_SVG_STYLES.cutLines, ...userStyles?.cutLines },
|
|
180
|
-
visibleLines: { ...DEFAULT_SVG_STYLES.visibleLines, ...userStyles?.visibleLines },
|
|
181
|
-
hiddenLines: { ...DEFAULT_SVG_STYLES.hiddenLines, ...userStyles?.hiddenLines },
|
|
182
|
-
silhouettes: { ...DEFAULT_SVG_STYLES.silhouettes, ...userStyles?.silhouettes },
|
|
183
|
-
featureEdges: { ...DEFAULT_SVG_STYLES.featureEdges, ...userStyles?.featureEdges },
|
|
184
|
-
stairArrows: { ...DEFAULT_SVG_STYLES.stairArrows, ...userStyles?.stairArrows },
|
|
185
|
-
outlineVisible: { ...DEFAULT_SVG_STYLES.outlineVisible, ...userStyles?.outlineVisible },
|
|
186
|
-
outlineHidden: { ...DEFAULT_SVG_STYLES.outlineHidden, ...userStyles?.outlineHidden }
|
|
187
|
-
};
|
|
188
|
-
}
|
|
1
|
+
import type { SlabSectionGeometry, Vec3 } from '@twinfinity/geometry';
|
|
2
|
+
import type { PrintToSvgOptions, SvgRenderStyles, SvgPlaneBasis } from './types';
|
|
3
|
+
import { DEFAULT_SVG_STYLES } from './types';
|
|
4
|
+
|
|
5
|
+
type Pt2 = { x: number; y: number };
|
|
6
|
+
type Bounds = { minX: number; maxX: number; minY: number; maxY: number };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Render SlabSectionGeometry to an SVG string.
|
|
10
|
+
*
|
|
11
|
+
* @param geometry The computed slab section geometry
|
|
12
|
+
* @param options Rendering options (viewBox, styles, planeBasis, etc.)
|
|
13
|
+
* @returns Complete SVG string
|
|
14
|
+
*/
|
|
15
|
+
export function printToSvg(geometry: SlabSectionGeometry, options: PrintToSvgOptions = {}): string {
|
|
16
|
+
if (!geometry) throw new Error('geometry is required');
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
options.boundsPadding !== undefined &&
|
|
20
|
+
(typeof options.boundsPadding !== 'number' || options.boundsPadding < 0)
|
|
21
|
+
) {
|
|
22
|
+
throw new Error('boundsPadding must be a non-negative number');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (options.viewBox) {
|
|
26
|
+
if (!Array.isArray(options.viewBox) || options.viewBox.length !== 4) {
|
|
27
|
+
throw new Error('viewBox must be an array of 4 numbers [minX, minY, width, height]');
|
|
28
|
+
}
|
|
29
|
+
for (let i = 0; i < 4; i++) {
|
|
30
|
+
if (typeof options.viewBox[i] !== 'number' || !Number.isFinite(options.viewBox[i])) {
|
|
31
|
+
throw new Error(`viewBox[${i}] must be a finite number`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const styles = mergeStyles(options.styles);
|
|
37
|
+
const includeXml = options.includeXmlDeclaration ?? true;
|
|
38
|
+
|
|
39
|
+
// Project all polylines to 2D once
|
|
40
|
+
const proj = {
|
|
41
|
+
hiddenLines: projectPolylines(geometry.hiddenLines, options.planeBasis),
|
|
42
|
+
outlineHidden: projectPolylines(geometry.outlineHidden, options.planeBasis),
|
|
43
|
+
visibleLines: projectPolylines(geometry.visibleLines, options.planeBasis),
|
|
44
|
+
outlineVisible: projectPolylines(geometry.outlineVisible, options.planeBasis),
|
|
45
|
+
silhouettes: projectPolylines(geometry.silhouettes, options.planeBasis),
|
|
46
|
+
featureEdges: projectPolylines(geometry.featureEdges, options.planeBasis),
|
|
47
|
+
cutLines: projectPolylines(geometry.cutLines, options.planeBasis),
|
|
48
|
+
stairArrows: projectPolylines(geometry.stairArrows, options.planeBasis)
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const bounds = options.bounds ?? computeBoundsFromProjected(proj);
|
|
52
|
+
const viewBox = options.viewBox ?? boundsToViewBox(bounds, options.boundsPadding ?? 0.05);
|
|
53
|
+
|
|
54
|
+
const parts: string[] = [];
|
|
55
|
+
|
|
56
|
+
if (includeXml) parts.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
57
|
+
|
|
58
|
+
const [minX, minY, width, height] = viewBox;
|
|
59
|
+
parts.push(
|
|
60
|
+
`<svg xmlns="http://www.w3.org/2000/svg" ` +
|
|
61
|
+
`viewBox="${minX} ${minY} ${width} ${height}" ` +
|
|
62
|
+
`width="${width}" height="${height}">`
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (options.backgroundColor) {
|
|
66
|
+
parts.push(
|
|
67
|
+
`<rect x="${minX}" y="${minY}" width="${width}" height="${height}" fill="${options.backgroundColor}"/>`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
parts.push('<g fill="none" stroke-linecap="round" stroke-linejoin="round">');
|
|
72
|
+
|
|
73
|
+
const categories: Array<[keyof typeof proj, keyof Required<SvgRenderStyles>]> = [
|
|
74
|
+
['hiddenLines', 'hiddenLines'],
|
|
75
|
+
['outlineHidden', 'outlineHidden'],
|
|
76
|
+
['visibleLines', 'visibleLines'],
|
|
77
|
+
['outlineVisible', 'outlineVisible'],
|
|
78
|
+
['silhouettes', 'silhouettes'],
|
|
79
|
+
['featureEdges', 'featureEdges'],
|
|
80
|
+
['cutLines', 'cutLines'],
|
|
81
|
+
['stairArrows', 'stairArrows']
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const [key, styleKey] of categories) {
|
|
85
|
+
const style = styles[styleKey];
|
|
86
|
+
if (style.enabled) renderProjectedCategory(parts, proj[key], style, bounds);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
parts.push('</g>');
|
|
90
|
+
parts.push('</svg>');
|
|
91
|
+
return parts.join('\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function projectPolylines(polylines: Vec3[][], planeBasis?: SvgPlaneBasis): Pt2[][] {
|
|
95
|
+
const result: Pt2[][] = [];
|
|
96
|
+
for (const polyline of polylines) {
|
|
97
|
+
if (polyline.length === 0) continue;
|
|
98
|
+
const projected: Pt2[] = [];
|
|
99
|
+
for (const point of polyline) {
|
|
100
|
+
if (!Number.isFinite(point[0]) || !Number.isFinite(point[1]) || !Number.isFinite(point[2])) continue;
|
|
101
|
+
if (planeBasis) {
|
|
102
|
+
const dx = point[0] - planeBasis.origin.x;
|
|
103
|
+
const dy = point[1] - planeBasis.origin.y;
|
|
104
|
+
const dz = point[2] - planeBasis.origin.z;
|
|
105
|
+
projected.push({
|
|
106
|
+
x: dx * planeBasis.right.x + dy * planeBasis.right.y + dz * planeBasis.right.z,
|
|
107
|
+
y: dx * planeBasis.up.x + dy * planeBasis.up.y + dz * planeBasis.up.z
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
projected.push({ x: point[0], y: -point[2] });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (projected.length > 0) result.push(projected);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function computeBoundsFromProjected(categories: Record<string, Pt2[][]>): Bounds {
|
|
119
|
+
let minX = Infinity,
|
|
120
|
+
minY = Infinity,
|
|
121
|
+
maxX = -Infinity,
|
|
122
|
+
maxY = -Infinity;
|
|
123
|
+
for (const polylines of Object.values(categories)) {
|
|
124
|
+
for (const polyline of polylines) {
|
|
125
|
+
for (const pt of polyline) {
|
|
126
|
+
if (pt.x < minX) minX = pt.x;
|
|
127
|
+
if (pt.x > maxX) maxX = pt.x;
|
|
128
|
+
if (pt.y < minY) minY = pt.y;
|
|
129
|
+
if (pt.y > maxY) maxY = pt.y;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!Number.isFinite(minX)) return { minX: 0, maxX: 1, minY: 0, maxY: 1 };
|
|
134
|
+
return { minX, maxX, minY, maxY };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function renderProjectedCategory(
|
|
138
|
+
parts: string[],
|
|
139
|
+
polylines: Pt2[][],
|
|
140
|
+
style: { stroke?: string; strokeWidth?: number; strokeDasharray?: string; opacity?: number },
|
|
141
|
+
bounds: Bounds
|
|
142
|
+
): void {
|
|
143
|
+
if (!polylines.length) return;
|
|
144
|
+
const pathData = projectedToPathData(polylines, bounds);
|
|
145
|
+
if (!pathData) return;
|
|
146
|
+
|
|
147
|
+
let pathElement = `<path d="${pathData}"`;
|
|
148
|
+
if (style.stroke) pathElement += ` stroke="${style.stroke}"`;
|
|
149
|
+
if (style.strokeWidth !== undefined) pathElement += ` stroke-width="${style.strokeWidth}"`;
|
|
150
|
+
if (style.strokeDasharray) pathElement += ` stroke-dasharray="${style.strokeDasharray}"`;
|
|
151
|
+
if (style.opacity !== undefined && style.opacity !== 1) pathElement += ` opacity="${style.opacity}"`;
|
|
152
|
+
pathElement += '/>';
|
|
153
|
+
parts.push(pathElement);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function projectedToPathData(polylines: Pt2[][], bounds: Bounds): string {
|
|
157
|
+
const parts: string[] = [];
|
|
158
|
+
const ox = bounds.minX;
|
|
159
|
+
const oy = bounds.maxY;
|
|
160
|
+
for (const polyline of polylines) {
|
|
161
|
+
if (polyline.length === 0) continue;
|
|
162
|
+
parts.push(`M ${(polyline[0].x - ox).toFixed(6)} ${(oy - polyline[0].y).toFixed(6)}`);
|
|
163
|
+
for (let i = 1; i < polyline.length; i++) {
|
|
164
|
+
parts.push(`L ${(polyline[i].x - ox).toFixed(6)} ${(oy - polyline[i].y).toFixed(6)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return parts.join(' ');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function boundsToViewBox(bounds: Bounds, padding: number): [number, number, number, number] {
|
|
171
|
+
const width = bounds.maxX - bounds.minX;
|
|
172
|
+
const height = bounds.maxY - bounds.minY;
|
|
173
|
+
const padAmount = Math.max(width, height) * padding;
|
|
174
|
+
return [-padAmount, -padAmount, width + padAmount * 2, height + padAmount * 2];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function mergeStyles(userStyles?: SvgRenderStyles): Required<SvgRenderStyles> {
|
|
178
|
+
return {
|
|
179
|
+
cutLines: { ...DEFAULT_SVG_STYLES.cutLines, ...userStyles?.cutLines },
|
|
180
|
+
visibleLines: { ...DEFAULT_SVG_STYLES.visibleLines, ...userStyles?.visibleLines },
|
|
181
|
+
hiddenLines: { ...DEFAULT_SVG_STYLES.hiddenLines, ...userStyles?.hiddenLines },
|
|
182
|
+
silhouettes: { ...DEFAULT_SVG_STYLES.silhouettes, ...userStyles?.silhouettes },
|
|
183
|
+
featureEdges: { ...DEFAULT_SVG_STYLES.featureEdges, ...userStyles?.featureEdges },
|
|
184
|
+
stairArrows: { ...DEFAULT_SVG_STYLES.stairArrows, ...userStyles?.stairArrows },
|
|
185
|
+
outlineVisible: { ...DEFAULT_SVG_STYLES.outlineVisible, ...userStyles?.outlineVisible },
|
|
186
|
+
outlineHidden: { ...DEFAULT_SVG_STYLES.outlineHidden, ...userStyles?.outlineHidden }
|
|
187
|
+
};
|
|
188
|
+
}
|