@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.
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
@@ -1,62 +1,62 @@
1
- import { getPathValue } from './renderUtils';
2
- import { parseLegendColor } from './legendUtils';
3
-
4
- import { isRecord, getProp } from './typeGuards';
5
-
6
- export interface LegendRowOptions {
7
- colorKeyPath?: string;
8
- classPath?: string;
9
- }
10
-
11
- export interface LegendRow {
12
- productId?: string;
13
- productName?: string;
14
- area?: number | string | null;
15
- className?: string | null;
16
- x?: number;
17
- z?: number;
18
- properties?: unknown;
19
- propertySets?: unknown;
20
- color?: [number, number, number, number];
21
- [key: string]: unknown;
22
- }
23
-
24
- /**
25
- * Build standard legend rows from product items.
26
- * Expected fields on input item:
27
- * - productId / productName / area
28
- * - x / z (model coords)
29
- * - properties / propertySets
30
- * - gid (fallback id)
31
- */
32
- export const buildLegendRows = (items: unknown[], options?: LegendRowOptions): LegendRow[] => {
33
- const colorKeyPath = options?.colorKeyPath ?? 'productId';
34
- return (Array.isArray(items) ? items : []).map((item): LegendRow => {
35
- const colorKey = getPathValue(item, colorKeyPath) ?? getProp(item, 'gid') ?? JSON.stringify(item);
36
- const itemColor = getProp(item, 'color');
37
- const color = parseLegendColor(itemColor, String(colorKey));
38
- const classPath = options?.classPath ?? 'ifcClass';
39
- const className = (() => {
40
- const explicit = getPathValue(item, classPath);
41
- if (explicit !== undefined && explicit !== null) return String(explicit);
42
- const propSets = getProp(item, 'propertySets');
43
- if (isRecord(propSets)) {
44
- for (const set of Object.values(propSets)) {
45
- const ifcClassProp = isRecord(set) ? getProp(set, 'ifcClass') : null;
46
- const candidate = isRecord(ifcClassProp) ? getProp(ifcClassProp, 'value') : null;
47
- if (candidate !== undefined && candidate !== null) return String(candidate);
48
- }
49
- }
50
- return null;
51
- })();
52
- const areaVal = getProp(item, 'area');
53
- const area = typeof areaVal === 'number' || typeof areaVal === 'string' ? areaVal : null;
54
- const baseObj = isRecord(item) ? (item as Record<string, unknown>) : {};
55
- return {
56
- ...baseObj,
57
- className,
58
- area,
59
- color
60
- };
61
- });
62
- };
1
+ import { getPathValue } from './renderUtils';
2
+ import { parseLegendColor } from './legendUtils';
3
+
4
+ import { isRecord, getProp } from './typeGuards';
5
+
6
+ export interface LegendRowOptions {
7
+ colorKeyPath?: string;
8
+ classPath?: string;
9
+ }
10
+
11
+ export interface LegendRow {
12
+ productId?: string;
13
+ productName?: string;
14
+ area?: number | string | null;
15
+ className?: string | null;
16
+ x?: number;
17
+ z?: number;
18
+ properties?: unknown;
19
+ propertySets?: unknown;
20
+ color?: [number, number, number, number];
21
+ [key: string]: unknown;
22
+ }
23
+
24
+ /**
25
+ * Build standard legend rows from product items.
26
+ * Expected fields on input item:
27
+ * - productId / productName / area
28
+ * - x / z (model coords)
29
+ * - properties / propertySets
30
+ * - gid (fallback id)
31
+ */
32
+ export const buildLegendRows = (items: unknown[], options?: LegendRowOptions): LegendRow[] => {
33
+ const colorKeyPath = options?.colorKeyPath ?? 'productId';
34
+ return (Array.isArray(items) ? items : []).map((item): LegendRow => {
35
+ const colorKey = getPathValue(item, colorKeyPath) ?? getProp(item, 'gid') ?? JSON.stringify(item);
36
+ const itemColor = getProp(item, 'color');
37
+ const color = parseLegendColor(itemColor, String(colorKey));
38
+ const classPath = options?.classPath ?? 'ifcClass';
39
+ const className = (() => {
40
+ const explicit = getPathValue(item, classPath);
41
+ if (explicit !== undefined && explicit !== null) return String(explicit);
42
+ const propSets = getProp(item, 'propertySets');
43
+ if (isRecord(propSets)) {
44
+ for (const set of Object.values(propSets)) {
45
+ const ifcClassProp = isRecord(set) ? getProp(set, 'ifcClass') : null;
46
+ const candidate = isRecord(ifcClassProp) ? getProp(ifcClassProp, 'value') : null;
47
+ if (candidate !== undefined && candidate !== null) return String(candidate);
48
+ }
49
+ }
50
+ return null;
51
+ })();
52
+ const areaVal = getProp(item, 'area');
53
+ const area = typeof areaVal === 'number' || typeof areaVal === 'string' ? areaVal : null;
54
+ const baseObj = isRecord(item) ? (item as Record<string, unknown>) : {};
55
+ return {
56
+ ...baseObj,
57
+ className,
58
+ area,
59
+ color
60
+ };
61
+ });
62
+ };
@@ -1,144 +1,144 @@
1
- import {
2
- LEGEND_TEXT_WIDTH_FACTOR,
3
- LEGEND_MIN_CONTENT_WIDTH_EM,
4
- LEGEND_TEXT_Y_OFFSET_FACTOR,
5
- PX_PER_MM
6
- } from './legendConstants';
7
- // v2 format: { digits?: number; prefix?: string; suffix?: string } or undefined
8
- type LegendFormat = { digits?: number; prefix?: string; suffix?: string } | string | undefined;
9
- import {
10
- RGB_MAX,
11
- RED_SHIFT,
12
- GREEN_SHIFT,
13
- HASH_COLOR_MODULO,
14
- HASH_GREEN_SHIFT,
15
- HASH_BLUE_SHIFT,
16
- RGB_MIN
17
- } from './colorConstants';
18
-
19
- import { isRecord, getProp } from './typeGuards';
20
-
21
- export function parseLegendColor(value: unknown, fallbackKey: string): [number, number, number, number] {
22
- if (Array.isArray(value) && (value.length === 3 || value.length === 4)) {
23
- const [r, g, b, a = RGB_MAX] = value.map((v) => Number(v) || 0);
24
- return [clampColor(r), clampColor(g), clampColor(b), clampColor(a)];
25
- }
26
- if (typeof value === 'string') {
27
- const hex = value.trim();
28
- const match = /^#?([a-fA-F0-9]{6})([a-fA-F0-9]{2})?$/.exec(hex);
29
- if (match) {
30
- const intVal = parseInt(match[1], 16);
31
- const aHex = match[2];
32
- return [
33
- (intVal >> RED_SHIFT) & 0xff,
34
- (intVal >> GREEN_SHIFT) & 0xff,
35
- intVal & 0xff,
36
- aHex ? parseInt(aHex, 16) : RGB_MAX
37
- ];
38
- }
39
- }
40
- const hash = Math.abs(hashCode(fallbackKey || ''));
41
- return [
42
- hash % HASH_COLOR_MODULO,
43
- (hash >> HASH_GREEN_SHIFT) % HASH_COLOR_MODULO,
44
- (hash >> HASH_BLUE_SHIFT) % HASH_COLOR_MODULO,
45
- RGB_MAX
46
- ];
47
- }
48
-
49
- export function formatLegendValue(value: unknown, fmt?: LegendFormat): string {
50
- if (fmt === undefined || fmt === null || fmt === 'string') {
51
- return value === undefined || value === null ? '' : String(value);
52
- }
53
-
54
- if (typeof fmt === 'string') {
55
- const fixedMatch = /^fixed\((\d+)(?:,\s*([^)]*))?\)$/i.exec(fmt);
56
- if (fixedMatch) {
57
- const digits = parseInt(fixedMatch[1], 10);
58
- const suffix = fixedMatch[2] || '';
59
- const num = Number(value);
60
- if (Number.isFinite(num)) {
61
- return `${num.toFixed(digits)}${suffix ? ` ${suffix.trim()}` : ''}`.trim();
62
- }
63
- }
64
- return String(value ?? '');
65
- }
66
-
67
- if (isRecord(fmt)) {
68
- const digits = getProp(fmt, 'digits');
69
- const prefix = getProp(fmt, 'prefix');
70
- const suffix = getProp(fmt, 'suffix');
71
- const num = Number(value);
72
- if (Number.isFinite(num) && typeof digits === 'number') {
73
- const prefixStr = typeof prefix === 'string' ? prefix : '';
74
- const suffixStr = typeof suffix === 'string' ? suffix : '';
75
- return `${prefixStr}${num.toFixed(digits)}${suffixStr}`;
76
- }
77
- return String(value ?? '');
78
- }
79
-
80
- return String(value ?? '');
81
- }
82
-
83
- export function computeLegendColumnWidths(
84
- columnLabels: { label: string; minWidthMm?: number; maxWidthMm?: number }[],
85
- rows: { values: string[] }[],
86
- fontSizeMm: number,
87
- cellPaddingMm: number,
88
- swatchWidthMm: number,
89
- fontFamily: string
90
- ): { colWidthsMm: number[]; columnCount: number } {
91
- const columnCount = Math.min(6, Math.max(2, columnLabels.length + 1));
92
- const colWidthsMm: number[] = new Array(columnCount).fill(0);
93
- colWidthsMm[0] = swatchWidthMm;
94
- const measureTextWidthMm = createLegendTextMeasurer(fontFamily, fontSizeMm);
95
-
96
- for (let c = 1; c < columnCount; c++) {
97
- const colIdx = c - 1;
98
- const col = columnLabels[colIdx];
99
- const minWidth = col.minWidthMm ?? fontSizeMm * LEGEND_MIN_CONTENT_WIDTH_EM;
100
- const maxWidth = col.maxWidthMm ?? Number.POSITIVE_INFINITY;
101
- let maxWidthMeasured = measureTextWidthMm(col.label ?? '');
102
- rows.forEach((row) => {
103
- const text = row.values[colIdx] ?? '';
104
- maxWidthMeasured = Math.max(maxWidthMeasured, measureTextWidthMm(text));
105
- });
106
- const contentWidth = Math.max(minWidth, Math.min(maxWidth, maxWidthMeasured + cellPaddingMm * 2));
107
- colWidthsMm[c] = contentWidth;
108
- }
109
-
110
- return { colWidthsMm, columnCount };
111
- }
112
-
113
- export function computeLegendTextYOffset(fontSizeMm: number): number {
114
- return fontSizeMm * LEGEND_TEXT_Y_OFFSET_FACTOR;
115
- }
116
-
117
- export const createLegendTextMeasurer = (fontFamily: string, fontSizeMm: number): ((text: string) => number) => {
118
- if (typeof document !== 'undefined') {
119
- const canvas = document.createElement('canvas');
120
- canvas.width = 1024;
121
- canvas.height = 64;
122
- const canvasCtx = canvas.getContext('2d');
123
- if (canvasCtx) {
124
- canvasCtx.font = `${fontSizeMm * PX_PER_MM}px ${fontFamily}`;
125
- const measureTextWidthMm = (text: string): number => {
126
- const metrics = canvasCtx.measureText(text);
127
- return metrics.width / PX_PER_MM;
128
- };
129
- return measureTextWidthMm;
130
- }
131
- }
132
- return (text: string): number => text.length * fontSizeMm * LEGEND_TEXT_WIDTH_FACTOR;
133
- };
134
-
135
- const clampColor = (value: number): number => Math.max(RGB_MIN, Math.min(RGB_MAX, Math.round(value)));
136
-
137
- const hashCode = (str: string): number => {
138
- let hash = 0;
139
- for (let i = 0; i < str.length; i++) {
140
- hash = (hash << 5) - hash + str.charCodeAt(i);
141
- hash |= 0;
142
- }
143
- return hash;
144
- };
1
+ import {
2
+ LEGEND_TEXT_WIDTH_FACTOR,
3
+ LEGEND_MIN_CONTENT_WIDTH_EM,
4
+ LEGEND_TEXT_Y_OFFSET_FACTOR,
5
+ PX_PER_MM
6
+ } from './legendConstants';
7
+ // v2 format: { digits?: number; prefix?: string; suffix?: string } or undefined
8
+ type LegendFormat = { digits?: number; prefix?: string; suffix?: string } | string | undefined;
9
+ import {
10
+ RGB_MAX,
11
+ RED_SHIFT,
12
+ GREEN_SHIFT,
13
+ HASH_COLOR_MODULO,
14
+ HASH_GREEN_SHIFT,
15
+ HASH_BLUE_SHIFT,
16
+ RGB_MIN
17
+ } from './colorConstants';
18
+
19
+ import { isRecord, getProp } from './typeGuards';
20
+
21
+ export function parseLegendColor(value: unknown, fallbackKey: string): [number, number, number, number] {
22
+ if (Array.isArray(value) && (value.length === 3 || value.length === 4)) {
23
+ const [r, g, b, a = RGB_MAX] = value.map((v) => Number(v) || 0);
24
+ return [clampColor(r), clampColor(g), clampColor(b), clampColor(a)];
25
+ }
26
+ if (typeof value === 'string') {
27
+ const hex = value.trim();
28
+ const match = /^#?([a-fA-F0-9]{6})([a-fA-F0-9]{2})?$/.exec(hex);
29
+ if (match) {
30
+ const intVal = parseInt(match[1], 16);
31
+ const aHex = match[2];
32
+ return [
33
+ (intVal >> RED_SHIFT) & 0xff,
34
+ (intVal >> GREEN_SHIFT) & 0xff,
35
+ intVal & 0xff,
36
+ aHex ? parseInt(aHex, 16) : RGB_MAX
37
+ ];
38
+ }
39
+ }
40
+ const hash = Math.abs(hashCode(fallbackKey || ''));
41
+ return [
42
+ hash % HASH_COLOR_MODULO,
43
+ (hash >> HASH_GREEN_SHIFT) % HASH_COLOR_MODULO,
44
+ (hash >> HASH_BLUE_SHIFT) % HASH_COLOR_MODULO,
45
+ RGB_MAX
46
+ ];
47
+ }
48
+
49
+ export function formatLegendValue(value: unknown, fmt?: LegendFormat): string {
50
+ if (fmt === undefined || fmt === null || fmt === 'string') {
51
+ return value === undefined || value === null ? '' : String(value);
52
+ }
53
+
54
+ if (typeof fmt === 'string') {
55
+ const fixedMatch = /^fixed\((\d+)(?:,\s*([^)]*))?\)$/i.exec(fmt);
56
+ if (fixedMatch) {
57
+ const digits = parseInt(fixedMatch[1], 10);
58
+ const suffix = fixedMatch[2] || '';
59
+ const num = Number(value);
60
+ if (Number.isFinite(num)) {
61
+ return `${num.toFixed(digits)}${suffix ? ` ${suffix.trim()}` : ''}`.trim();
62
+ }
63
+ }
64
+ return String(value ?? '');
65
+ }
66
+
67
+ if (isRecord(fmt)) {
68
+ const digits = getProp(fmt, 'digits');
69
+ const prefix = getProp(fmt, 'prefix');
70
+ const suffix = getProp(fmt, 'suffix');
71
+ const num = Number(value);
72
+ if (Number.isFinite(num) && typeof digits === 'number') {
73
+ const prefixStr = typeof prefix === 'string' ? prefix : '';
74
+ const suffixStr = typeof suffix === 'string' ? suffix : '';
75
+ return `${prefixStr}${num.toFixed(digits)}${suffixStr}`;
76
+ }
77
+ return String(value ?? '');
78
+ }
79
+
80
+ return String(value ?? '');
81
+ }
82
+
83
+ export function computeLegendColumnWidths(
84
+ columnLabels: { label: string; minWidthMm?: number; maxWidthMm?: number }[],
85
+ rows: { values: string[] }[],
86
+ fontSizeMm: number,
87
+ cellPaddingMm: number,
88
+ swatchWidthMm: number,
89
+ fontFamily: string
90
+ ): { colWidthsMm: number[]; columnCount: number } {
91
+ const columnCount = Math.min(6, Math.max(2, columnLabels.length + 1));
92
+ const colWidthsMm: number[] = new Array(columnCount).fill(0);
93
+ colWidthsMm[0] = swatchWidthMm;
94
+ const measureTextWidthMm = createLegendTextMeasurer(fontFamily, fontSizeMm);
95
+
96
+ for (let c = 1; c < columnCount; c++) {
97
+ const colIdx = c - 1;
98
+ const col = columnLabels[colIdx];
99
+ const minWidth = col.minWidthMm ?? fontSizeMm * LEGEND_MIN_CONTENT_WIDTH_EM;
100
+ const maxWidth = col.maxWidthMm ?? Number.POSITIVE_INFINITY;
101
+ let maxWidthMeasured = measureTextWidthMm(col.label ?? '');
102
+ rows.forEach((row) => {
103
+ const text = row.values[colIdx] ?? '';
104
+ maxWidthMeasured = Math.max(maxWidthMeasured, measureTextWidthMm(text));
105
+ });
106
+ const contentWidth = Math.max(minWidth, Math.min(maxWidth, maxWidthMeasured + cellPaddingMm * 2));
107
+ colWidthsMm[c] = contentWidth;
108
+ }
109
+
110
+ return { colWidthsMm, columnCount };
111
+ }
112
+
113
+ export function computeLegendTextYOffset(fontSizeMm: number): number {
114
+ return fontSizeMm * LEGEND_TEXT_Y_OFFSET_FACTOR;
115
+ }
116
+
117
+ export const createLegendTextMeasurer = (fontFamily: string, fontSizeMm: number): ((text: string) => number) => {
118
+ if (typeof document !== 'undefined') {
119
+ const canvas = document.createElement('canvas');
120
+ canvas.width = 1024;
121
+ canvas.height = 64;
122
+ const canvasCtx = canvas.getContext('2d');
123
+ if (canvasCtx) {
124
+ canvasCtx.font = `${fontSizeMm * PX_PER_MM}px ${fontFamily}`;
125
+ const measureTextWidthMm = (text: string): number => {
126
+ const metrics = canvasCtx.measureText(text);
127
+ return metrics.width / PX_PER_MM;
128
+ };
129
+ return measureTextWidthMm;
130
+ }
131
+ }
132
+ return (text: string): number => text.length * fontSizeMm * LEGEND_TEXT_WIDTH_FACTOR;
133
+ };
134
+
135
+ const clampColor = (value: number): number => Math.max(RGB_MIN, Math.min(RGB_MAX, Math.round(value)));
136
+
137
+ const hashCode = (str: string): number => {
138
+ let hash = 0;
139
+ for (let i = 0; i < str.length; i++) {
140
+ hash = (hash << 5) - hash + str.charCodeAt(i);
141
+ hash |= 0;
142
+ }
143
+ return hash;
144
+ };