@twinfinity/printing 6.0.1-ci.28952-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.
Files changed (82) hide show
  1. package/README.md +3 -3
  2. package/dist/BuildInfo.js +3 -3
  3. package/dist/BuildInfo.js.map +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/layout/analyzeTemplate.js.map +1 -1
  6. package/dist/layout/clipUtils.js.map +1 -1
  7. package/dist/layout/colorConstants.js.map +1 -1
  8. package/dist/layout/elementKinds.js.map +1 -1
  9. package/dist/layout/exportDrawing.js.map +1 -1
  10. package/dist/layout/filters.js.map +1 -1
  11. package/dist/layout/geometryConstants.js.map +1 -1
  12. package/dist/layout/index.js.map +1 -1
  13. package/dist/layout/itemBuilders.js.map +1 -1
  14. package/dist/layout/labelsRender.js.map +1 -1
  15. package/dist/layout/legendConstants.js.map +1 -1
  16. package/dist/layout/legendRender.js.map +1 -1
  17. package/dist/layout/legendRows.js.map +1 -1
  18. package/dist/layout/legendUtils.js.map +1 -1
  19. package/dist/layout/metadata.js.map +1 -1
  20. package/dist/layout/model.js.map +1 -1
  21. package/dist/layout/options.js.map +1 -1
  22. package/dist/layout/presets.js.map +1 -1
  23. package/dist/layout/qrRender.js.map +1 -1
  24. package/dist/layout/renderDrawing.js.map +1 -1
  25. package/dist/layout/renderUtils.js.map +1 -1
  26. package/dist/layout/sectionValidation.js.map +1 -1
  27. package/dist/layout/templateVariables.js.map +1 -1
  28. package/dist/layout/typeGuards.js.map +1 -1
  29. package/dist/layout/types.js.map +1 -1
  30. package/dist/layout/utils.js.map +1 -1
  31. package/dist/layout/validation.js.map +1 -1
  32. package/dist/layout/validationHelpers.js.map +1 -1
  33. package/dist/layout/validationUtils.js.map +1 -1
  34. package/dist/layout/viewportPrep.js.map +1 -1
  35. package/dist/layout/viewportRender.js.map +1 -1
  36. package/dist/layout/viewportUtils.js.map +1 -1
  37. package/dist/layout/warnings.js.map +1 -1
  38. package/dist/printToPdf.js.map +1 -1
  39. package/dist/printToSvg.js.map +1 -1
  40. package/dist/sections.js.map +1 -1
  41. package/dist/types.js.map +1 -1
  42. package/package.json +2 -2
  43. package/src/BuildInfo.ts +41 -41
  44. package/src/index.ts +10 -10
  45. package/src/layout/analyzeTemplate.ts +218 -218
  46. package/src/layout/clipUtils.ts +193 -193
  47. package/src/layout/colorConstants.ts +16 -16
  48. package/src/layout/elementKinds.ts +35 -35
  49. package/src/layout/exportDrawing.ts +206 -206
  50. package/src/layout/filters.ts +80 -80
  51. package/src/layout/geometryConstants.ts +11 -11
  52. package/src/layout/index.ts +671 -671
  53. package/src/layout/itemBuilders.ts +159 -159
  54. package/src/layout/labelsRender.ts +170 -170
  55. package/src/layout/legendConstants.ts +11 -11
  56. package/src/layout/legendRender.ts +543 -543
  57. package/src/layout/legendRows.ts +62 -62
  58. package/src/layout/legendUtils.ts +144 -144
  59. package/src/layout/metadata.ts +210 -210
  60. package/src/layout/model.ts +372 -372
  61. package/src/layout/options.ts +17 -17
  62. package/src/layout/presets.ts +51 -51
  63. package/src/layout/qrRender.ts +126 -126
  64. package/src/layout/qrcode.d.ts +2 -2
  65. package/src/layout/renderDrawing.ts +710 -710
  66. package/src/layout/renderUtils.ts +138 -138
  67. package/src/layout/sectionValidation.ts +2 -2
  68. package/src/layout/templateVariables.ts +317 -317
  69. package/src/layout/typeGuards.ts +9 -9
  70. package/src/layout/types.ts +835 -835
  71. package/src/layout/utils.ts +85 -85
  72. package/src/layout/validation.ts +392 -392
  73. package/src/layout/validationHelpers.ts +59 -59
  74. package/src/layout/validationUtils.ts +6 -6
  75. package/src/layout/viewportPrep.ts +351 -351
  76. package/src/layout/viewportRender.ts +442 -442
  77. package/src/layout/viewportUtils.ts +50 -50
  78. package/src/layout/warnings.ts +3 -3
  79. package/src/printToPdf.ts +108 -108
  80. package/src/printToSvg.ts +188 -188
  81. package/src/sections.ts +1019 -1019
  82. 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
+ }