@twinfinity/printing 5.0.3-ci.28538-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 (209) hide show
  1. package/README.md +208 -0
  2. package/dist/BuildInfo.d.ts +25 -0
  3. package/dist/BuildInfo.d.ts.map +1 -0
  4. package/dist/BuildInfo.js +15 -0
  5. package/dist/BuildInfo.js.map +1 -0
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +9 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/layout/analyzeTemplate.d.ts +29 -0
  11. package/dist/layout/analyzeTemplate.d.ts.map +1 -0
  12. package/dist/layout/analyzeTemplate.js +168 -0
  13. package/dist/layout/analyzeTemplate.js.map +1 -0
  14. package/dist/layout/clipUtils.d.ts +18 -0
  15. package/dist/layout/clipUtils.d.ts.map +1 -0
  16. package/dist/layout/clipUtils.js +165 -0
  17. package/dist/layout/clipUtils.js.map +1 -0
  18. package/dist/layout/colorConstants.d.ts +11 -0
  19. package/dist/layout/colorConstants.d.ts.map +1 -0
  20. package/dist/layout/colorConstants.js +14 -0
  21. package/dist/layout/colorConstants.js.map +1 -0
  22. package/dist/layout/elementKinds.d.ts +21 -0
  23. package/dist/layout/elementKinds.d.ts.map +1 -0
  24. package/dist/layout/elementKinds.js +17 -0
  25. package/dist/layout/elementKinds.js.map +1 -0
  26. package/dist/layout/exportDrawing.d.ts +143 -0
  27. package/dist/layout/exportDrawing.d.ts.map +1 -0
  28. package/dist/layout/exportDrawing.js +146 -0
  29. package/dist/layout/exportDrawing.js.map +1 -0
  30. package/dist/layout/filters.d.ts +33 -0
  31. package/dist/layout/filters.d.ts.map +1 -0
  32. package/dist/layout/filters.js +77 -0
  33. package/dist/layout/filters.js.map +1 -0
  34. package/dist/layout/geometryConstants.d.ts +8 -0
  35. package/dist/layout/geometryConstants.d.ts.map +1 -0
  36. package/dist/layout/geometryConstants.js +10 -0
  37. package/dist/layout/geometryConstants.js.map +1 -0
  38. package/dist/layout/index.d.ts +86 -0
  39. package/dist/layout/index.d.ts.map +1 -0
  40. package/dist/layout/index.js +363 -0
  41. package/dist/layout/index.js.map +1 -0
  42. package/dist/layout/itemBuilders.d.ts +29 -0
  43. package/dist/layout/itemBuilders.d.ts.map +1 -0
  44. package/dist/layout/itemBuilders.js +135 -0
  45. package/dist/layout/itemBuilders.js.map +1 -0
  46. package/dist/layout/labelsRender.d.ts +29 -0
  47. package/dist/layout/labelsRender.d.ts.map +1 -0
  48. package/dist/layout/labelsRender.js +109 -0
  49. package/dist/layout/labelsRender.js.map +1 -0
  50. package/dist/layout/legendConstants.d.ts +12 -0
  51. package/dist/layout/legendConstants.d.ts.map +1 -0
  52. package/dist/layout/legendConstants.js +12 -0
  53. package/dist/layout/legendConstants.js.map +1 -0
  54. package/dist/layout/legendRender.d.ts +13 -0
  55. package/dist/layout/legendRender.d.ts.map +1 -0
  56. package/dist/layout/legendRender.js +424 -0
  57. package/dist/layout/legendRender.js.map +1 -0
  58. package/dist/layout/legendRows.d.ts +26 -0
  59. package/dist/layout/legendRows.d.ts.map +1 -0
  60. package/dist/layout/legendRows.js +45 -0
  61. package/dist/layout/legendRows.js.map +1 -0
  62. package/dist/layout/legendUtils.d.ts +21 -0
  63. package/dist/layout/legendUtils.d.ts.map +1 -0
  64. package/dist/layout/legendUtils.js +110 -0
  65. package/dist/layout/legendUtils.js.map +1 -0
  66. package/dist/layout/metadata.d.ts +43 -0
  67. package/dist/layout/metadata.d.ts.map +1 -0
  68. package/dist/layout/metadata.js +191 -0
  69. package/dist/layout/metadata.js.map +1 -0
  70. package/dist/layout/model.d.ts +15 -0
  71. package/dist/layout/model.d.ts.map +1 -0
  72. package/dist/layout/model.js +310 -0
  73. package/dist/layout/model.js.map +1 -0
  74. package/dist/layout/options.d.ts +17 -0
  75. package/dist/layout/options.d.ts.map +1 -0
  76. package/dist/layout/options.js +2 -0
  77. package/dist/layout/options.js.map +1 -0
  78. package/dist/layout/presets.d.ts +23 -0
  79. package/dist/layout/presets.d.ts.map +1 -0
  80. package/dist/layout/presets.js +41 -0
  81. package/dist/layout/presets.js.map +1 -0
  82. package/dist/layout/qrRender.d.ts +13 -0
  83. package/dist/layout/qrRender.d.ts.map +1 -0
  84. package/dist/layout/qrRender.js +97 -0
  85. package/dist/layout/qrRender.js.map +1 -0
  86. package/dist/layout/renderDrawing.d.ts +62 -0
  87. package/dist/layout/renderDrawing.d.ts.map +1 -0
  88. package/dist/layout/renderDrawing.js +555 -0
  89. package/dist/layout/renderDrawing.js.map +1 -0
  90. package/dist/layout/renderUtils.d.ts +36 -0
  91. package/dist/layout/renderUtils.d.ts.map +1 -0
  92. package/dist/layout/renderUtils.js +117 -0
  93. package/dist/layout/renderUtils.js.map +1 -0
  94. package/dist/layout/sectionValidation.d.ts +3 -0
  95. package/dist/layout/sectionValidation.d.ts.map +1 -0
  96. package/dist/layout/sectionValidation.js +2 -0
  97. package/dist/layout/sectionValidation.js.map +1 -0
  98. package/dist/layout/templateVariables.d.ts +137 -0
  99. package/dist/layout/templateVariables.d.ts.map +1 -0
  100. package/dist/layout/templateVariables.js +295 -0
  101. package/dist/layout/templateVariables.js.map +1 -0
  102. package/dist/layout/typeGuards.d.ts +5 -0
  103. package/dist/layout/typeGuards.d.ts.map +1 -0
  104. package/dist/layout/typeGuards.js +9 -0
  105. package/dist/layout/typeGuards.js.map +1 -0
  106. package/dist/layout/types.d.ts +816 -0
  107. package/dist/layout/types.d.ts.map +1 -0
  108. package/dist/layout/types.js +2 -0
  109. package/dist/layout/types.js.map +1 -0
  110. package/dist/layout/utils.d.ts +9 -0
  111. package/dist/layout/utils.d.ts.map +1 -0
  112. package/dist/layout/utils.js +84 -0
  113. package/dist/layout/utils.js.map +1 -0
  114. package/dist/layout/validation.d.ts +7 -0
  115. package/dist/layout/validation.d.ts.map +1 -0
  116. package/dist/layout/validation.js +352 -0
  117. package/dist/layout/validation.js.map +1 -0
  118. package/dist/layout/validationHelpers.d.ts +15 -0
  119. package/dist/layout/validationHelpers.d.ts.map +1 -0
  120. package/dist/layout/validationHelpers.js +43 -0
  121. package/dist/layout/validationHelpers.js.map +1 -0
  122. package/dist/layout/validationUtils.d.ts +5 -0
  123. package/dist/layout/validationUtils.d.ts.map +1 -0
  124. package/dist/layout/validationUtils.js +5 -0
  125. package/dist/layout/validationUtils.js.map +1 -0
  126. package/dist/layout/viewportPrep.d.ts +47 -0
  127. package/dist/layout/viewportPrep.d.ts.map +1 -0
  128. package/dist/layout/viewportPrep.js +275 -0
  129. package/dist/layout/viewportPrep.js.map +1 -0
  130. package/dist/layout/viewportRender.d.ts +42 -0
  131. package/dist/layout/viewportRender.d.ts.map +1 -0
  132. package/dist/layout/viewportRender.js +337 -0
  133. package/dist/layout/viewportRender.js.map +1 -0
  134. package/dist/layout/viewportUtils.d.ts +9 -0
  135. package/dist/layout/viewportUtils.d.ts.map +1 -0
  136. package/dist/layout/viewportUtils.js +40 -0
  137. package/dist/layout/viewportUtils.js.map +1 -0
  138. package/dist/layout/warnings.d.ts +2 -0
  139. package/dist/layout/warnings.d.ts.map +1 -0
  140. package/dist/layout/warnings.js +4 -0
  141. package/dist/layout/warnings.js.map +1 -0
  142. package/dist/printToPdf.d.ts +38 -0
  143. package/dist/printToPdf.d.ts.map +1 -0
  144. package/dist/printToPdf.js +79 -0
  145. package/dist/printToPdf.js.map +1 -0
  146. package/dist/printToSvg.d.ts +11 -0
  147. package/dist/printToSvg.d.ts.map +1 -0
  148. package/dist/printToSvg.js +168 -0
  149. package/dist/printToSvg.js.map +1 -0
  150. package/dist/sections.d.ts +118 -0
  151. package/dist/sections.d.ts.map +1 -0
  152. package/dist/sections.js +839 -0
  153. package/dist/sections.js.map +1 -0
  154. package/dist/templates/default-template.json +137 -0
  155. package/dist/types.d.ts +54 -0
  156. package/dist/types.d.ts.map +1 -0
  157. package/dist/types.js +11 -0
  158. package/dist/types.js.map +1 -0
  159. package/package.json +47 -0
  160. package/src/BuildInfo.ts +41 -0
  161. package/src/index.ts +10 -0
  162. package/src/layout/analyzeTemplate.ts +218 -0
  163. package/src/layout/clipUtils.ts +193 -0
  164. package/src/layout/colorConstants.ts +16 -0
  165. package/src/layout/elementKinds.ts +35 -0
  166. package/src/layout/exportDrawing.ts +206 -0
  167. package/src/layout/filters.ts +80 -0
  168. package/src/layout/geometryConstants.ts +11 -0
  169. package/src/layout/index.ts +671 -0
  170. package/src/layout/itemBuilders.ts +159 -0
  171. package/src/layout/labelsRender.ts +170 -0
  172. package/src/layout/legendConstants.ts +11 -0
  173. package/src/layout/legendRender.ts +543 -0
  174. package/src/layout/legendRows.ts +62 -0
  175. package/src/layout/legendUtils.ts +144 -0
  176. package/src/layout/metadata.ts +210 -0
  177. package/src/layout/model.ts +372 -0
  178. package/src/layout/options.ts +17 -0
  179. package/src/layout/presets.ts +51 -0
  180. package/src/layout/qrRender.ts +126 -0
  181. package/src/layout/qrcode.d.ts +2 -0
  182. package/src/layout/renderDrawing.ts +710 -0
  183. package/src/layout/renderUtils.ts +138 -0
  184. package/src/layout/sectionValidation.ts +2 -0
  185. package/src/layout/templateVariables.ts +317 -0
  186. package/src/layout/typeGuards.ts +9 -0
  187. package/src/layout/types.ts +835 -0
  188. package/src/layout/utils.ts +85 -0
  189. package/src/layout/validation.ts +392 -0
  190. package/src/layout/validationHelpers.ts +59 -0
  191. package/src/layout/validationUtils.ts +6 -0
  192. package/src/layout/viewportPrep.ts +351 -0
  193. package/src/layout/viewportRender.ts +442 -0
  194. package/src/layout/viewportUtils.ts +50 -0
  195. package/src/layout/warnings.ts +3 -0
  196. package/src/printToPdf.ts +108 -0
  197. package/src/printToSvg.ts +188 -0
  198. package/src/sections.ts +1019 -0
  199. package/src/templates/default-template.json +137 -0
  200. package/src/types.ts +49 -0
  201. package/templates/default-a0-landscape.json +209 -0
  202. package/templates/default-a0-portrait.json +195 -0
  203. package/templates/default-a1-landscape.json +167 -0
  204. package/templates/default-a1-portrait.json +153 -0
  205. package/templates/default-a3-landscape.json +111 -0
  206. package/templates/default-a3-portrait.json +125 -0
  207. package/templates/default-a4-landscape.json +97 -0
  208. package/templates/default-a4-portrait.json +97 -0
  209. package/templates/template-schema.json +1395 -0
@@ -0,0 +1,159 @@
1
+ import type { SectionRenderState } from '@twinfinity/geometry';
2
+ import { normalizePropertySets } from './metadata';
3
+ import { getPathValue } from './renderUtils';
4
+ import { buildModelViewFromRaw } from './model';
5
+ import { ProductFieldPaths, NormalizedProductItem, SvgModelView, LabelStyle } from './types';
6
+
7
+ import { getProp } from './typeGuards';
8
+
9
+ export interface ProductLike {
10
+ product?: any;
11
+ polylines?: Array<{ points?: Array<{ x: number; z: number }> }>;
12
+ }
13
+
14
+ export interface BuildViewportItemsOptions {
15
+ productFields?: ProductFieldPaths;
16
+ }
17
+
18
+ /**
19
+ * Normalize a single product entry using explicit field paths.
20
+ */
21
+ export const normalizeProductEntry = (entry: ProductLike, productFields: ProductFieldPaths): NormalizedProductItem => {
22
+ if (!productFields?.idPath || !productFields?.namePath || !productFields?.areaPath) {
23
+ throw new Error(
24
+ 'productFields must include idPath, namePath, and areaPath using propertySets.<Set>.<Prop>.value.'
25
+ );
26
+ }
27
+ const product = getProp(entry, 'product') ?? entry;
28
+ const propertySets = normalizePropertySets(product);
29
+ const getVal = (path: string): unknown => getPathValue({ propertySets }, path);
30
+ const idVal = getVal(productFields.idPath) ?? getProp(product, 'gid');
31
+ const nameVal = getVal(productFields.namePath);
32
+ const areaVal = getVal(productFields.areaPath);
33
+ // class.type = IfcClassType ("space", "walls", etc.) — used by template filters
34
+ // class.id = PredefinedIfcClassId ("IfcSpace", "IfcWall", etc.) — used as fallback
35
+ // classPath from propertySets may contain a PredefinedIfcClassId
36
+ const classPathVal = productFields.classPath ? getVal(productFields.classPath) : null;
37
+ const productClass = getProp(product, 'class');
38
+ const classType = getProp(productClass, 'type') as string | null; // IfcClassType
39
+ const classId = getProp(productClass, 'id') as string | null; // PredefinedIfcClassId
40
+ // ifcClass = IfcClassType category; ifcClassId = PredefinedIfcClassId
41
+ const ifcClass = (classType ?? null) as string | null;
42
+ const ifcClassId = (classPathVal ?? classId ?? null) as string | null;
43
+ // Try multiple sources for coordinates:
44
+ // 1. BBox center from product
45
+ // 2. Polylines center from entry
46
+ // 3. Direct x/z on entry or product
47
+ let { x, z } = getBBoxCenter(product);
48
+ if (x === null || z === null) {
49
+ const polyCenter = computePolylinesCenter(entry);
50
+ x = polyCenter.x ?? x;
51
+ z = polyCenter.z ?? z;
52
+ }
53
+ if (x === null || z === null) {
54
+ const directX = getProp(entry, 'x') ?? getProp(product, 'x');
55
+ const directZ = getProp(entry, 'z') ?? getProp(product, 'z');
56
+ if (Number.isFinite(directX)) x = directX as number;
57
+ if (Number.isFinite(directZ)) z = directZ as number;
58
+ }
59
+ const rawPolylines = getProp(entry, 'polylines');
60
+ return {
61
+ gid: getProp(product, 'gid') as string | undefined,
62
+ ifcClass,
63
+ ifcClassId,
64
+ propertySets,
65
+ productId: idVal ? String(idVal) : '-',
66
+ productName: typeof nameVal === 'string' && nameVal.trim().length ? nameVal.trim() : '-',
67
+ area: typeof areaVal === 'number' && Number.isFinite(areaVal) ? areaVal : null,
68
+ x,
69
+ z,
70
+ text: typeof nameVal === 'string' && nameVal.trim().length ? nameVal.trim() : '-',
71
+ polylines: Array.isArray(rawPolylines) ? rawPolylines : undefined
72
+ };
73
+ };
74
+
75
+ const getBBoxCenter = (product: unknown): { x: number | null; z: number | null } => {
76
+ try {
77
+ const boundingInfo = getProp(product, 'boundingInfo');
78
+ const boundingInfoResult = typeof boundingInfo === 'function' ? boundingInfo() : null;
79
+ const bbox = getProp(boundingInfoResult, 'boundingBox');
80
+ const min = getProp(bbox, 'minimumWorld') ?? getProp(bbox, 'minimum');
81
+ const max = getProp(bbox, 'maximumWorld') ?? getProp(bbox, 'maximum');
82
+ const minX = getProp(min, 'x');
83
+ const maxX = getProp(max, 'x');
84
+ const minZ = getProp(min, 'z');
85
+ const maxZ = getProp(max, 'z');
86
+ if (Number.isFinite(minX) && Number.isFinite(maxX) && Number.isFinite(minZ) && Number.isFinite(maxZ)) {
87
+ return { x: ((minX as number) + (maxX as number)) * 0.5, z: ((minZ as number) + (maxZ as number)) * 0.5 };
88
+ }
89
+ } catch {
90
+ // Bounding info access may fail for products without geometry or with malformed data structures.
91
+ // This is expected behavior - we return null coordinates as fallback.
92
+ }
93
+ return { x: null, z: null };
94
+ };
95
+
96
+ export const computePolylinesCenter = (entry: unknown): { x: number | null; z: number | null } => {
97
+ try {
98
+ const polylines = getProp(entry, 'polylines');
99
+ if (!Array.isArray(polylines) || !polylines.length) {
100
+ return { x: null, z: null };
101
+ }
102
+ let sumX = 0;
103
+ let sumZ = 0;
104
+ let count = 0;
105
+ for (const polyline of polylines) {
106
+ const points = getProp(polyline, 'points');
107
+ if (!Array.isArray(points)) continue;
108
+ for (const pt of points) {
109
+ const px = getProp(pt, 'x');
110
+ const pz = getProp(pt, 'z');
111
+ if (Number.isFinite(px) && Number.isFinite(pz)) {
112
+ sumX += px as number;
113
+ sumZ += pz as number;
114
+ count++;
115
+ }
116
+ }
117
+ }
118
+ if (count > 0) {
119
+ return { x: sumX / count, z: sumZ / count };
120
+ }
121
+ } catch {
122
+ // Polyline access may fail - return null
123
+ }
124
+ return { x: null, z: null };
125
+ };
126
+
127
+ /**
128
+ * Normalize raw product entries into viewport items using explicit field paths.
129
+ * Requires productFields (id/name/area/class) pointing to propertySets.<Set>.<Prop>.value.
130
+ */
131
+ export const buildViewportItems = (
132
+ entries: ProductLike[],
133
+ options?: BuildViewportItemsOptions
134
+ ): NormalizedProductItem[] => {
135
+ if (!options?.productFields) {
136
+ throw new Error('buildViewportItems requires productFields (idPath/namePath/areaPath/classPath).');
137
+ }
138
+ const { idPath, namePath, areaPath } = options.productFields;
139
+ if (!idPath || !namePath || !areaPath) {
140
+ throw new Error(
141
+ 'productFields must include idPath, namePath, and areaPath using propertySets.<Set>.<Prop>.value.'
142
+ );
143
+ }
144
+ return (entries ?? []).map((entry: any) => normalizeProductEntry(entry, options.productFields!));
145
+ };
146
+
147
+ export function sectionStateToModelView(state: SectionRenderState, labelStyle?: LabelStyle): SvgModelView {
148
+ const model = buildModelViewFromRaw(
149
+ state.paths,
150
+ state.labels,
151
+ state.transform,
152
+ state.geometryTransform,
153
+ labelStyle
154
+ );
155
+ if (!model) {
156
+ throw new Error('sectionStateToModelView requires paths, transform, and geometryTransform.');
157
+ }
158
+ return model;
159
+ }
@@ -0,0 +1,170 @@
1
+ import { LabelsElement, LayoutData } from './types';
2
+ import { matchesV2Filter } from './filters';
3
+ import { pointInPolygon } from './clipUtils';
4
+ import { getPathValue, resolvePath, escapeAttr, escapeText } from './renderUtils';
5
+
6
+ import { isRecord, getProp } from './typeGuards';
7
+
8
+ export interface ViewportRenderInfo {
9
+ mmPerModel: number;
10
+ contentWidthMm: number;
11
+ contentHeightMm: number;
12
+ offsetX: number;
13
+ offsetY: number;
14
+ rotationDeg: number;
15
+ transform: {
16
+ viewMinX: number;
17
+ viewMaxX: number;
18
+ viewMinZ: number;
19
+ viewMaxZ: number;
20
+ viewWidth: number;
21
+ viewHeight: number;
22
+ };
23
+ centerX: number;
24
+ centerY: number;
25
+ centerModelX: number;
26
+ centerModelY: number;
27
+ }
28
+
29
+ export const renderLabels = (
30
+ labelElements: LabelsElement[],
31
+ data: LayoutData,
32
+ viewportRenderInfo: Record<string, ViewportRenderInfo>,
33
+ viewportClipLabels: Record<string, boolean>,
34
+ viewportClipPolygonsLocal: Record<string, Array<Array<{ x: number; y: number }>>>,
35
+ viewportClipPolygonsWorld: Record<string, Array<Array<{ x: number; z: number }>>>,
36
+ styles: Record<string, unknown>,
37
+ dataWithViewport: unknown,
38
+ warnings: string[]
39
+ ): string[] => {
40
+ const parts: string[] = [];
41
+
42
+ for (const dl of labelElements) {
43
+ const vpInfo = viewportRenderInfo[dl.viewport];
44
+ if (!vpInfo) {
45
+ warnings.push(`Labels '${dl.id}' missing viewport '${dl.viewport}'.`);
46
+ continue;
47
+ }
48
+ if (vpInfo.rotationDeg !== 0) {
49
+ warnings.push(`Labels '${dl.id}' skipped because rotation is not supported.`);
50
+ continue;
51
+ }
52
+ if (!vpInfo.transform) {
53
+ warnings.push(`Labels '${dl.id}' skipped because model transform is missing.`);
54
+ continue;
55
+ }
56
+ // Items come from the bound viewport's data
57
+ const items = getPathValue(data, `viewports.${dl.viewport}.items`);
58
+ if (!Array.isArray(items) || !items.length) {
59
+ continue;
60
+ }
61
+ // Filter by class + where
62
+ let filteredItems =
63
+ dl.class || dl.where ? items.filter((item) => matchesV2Filter(item, dl.class, dl.where)) : items;
64
+
65
+ console.log(`[Labels ${dl.id}] Total items:`, items.length, 'After class+where filter:', filteredItems.length);
66
+
67
+ // If matchClipFilter is true, filter by spatial position inside clip polygon
68
+ if (dl.matchClipFilter && viewportClipPolygonsWorld[dl.viewport]?.length) {
69
+ const clipPolygons = viewportClipPolygonsWorld[dl.viewport];
70
+ console.log(`[Labels ${dl.id}] Applying matchClipFilter (spatial) with ${clipPolygons.length} polygons`);
71
+ const beforeClipFilter = filteredItems.length;
72
+ filteredItems = filteredItems.filter((item) => {
73
+ const ix = getProp(item, 'x');
74
+ const iz = getProp(item, 'z');
75
+ if (!Number.isFinite(ix) || !Number.isFinite(iz)) return false;
76
+ return clipPolygons.some((poly) => {
77
+ // pointInPolygon expects y coordinate, but we use z for world space
78
+ const polyWith2D = poly.map((p) => ({ x: p.x, y: p.z }));
79
+ return pointInPolygon(polyWith2D, ix as number, iz as number);
80
+ });
81
+ });
82
+ console.log(
83
+ `[Labels ${dl.id}] After matchClipFilter (spatial):`,
84
+ filteredItems.length,
85
+ '(was:',
86
+ beforeClipFilter,
87
+ ')'
88
+ );
89
+
90
+ // Debug: log first few items to see their properties
91
+ if (filteredItems.length === 0 && beforeClipFilter > 0) {
92
+ console.log('[Labels] Sample item that was filtered out:', items[0]);
93
+ }
94
+ }
95
+
96
+ if (!filteredItems.length) {
97
+ console.log(`[Labels ${dl.id}] No items left after filtering, skipping.`);
98
+ continue;
99
+ }
100
+ const styleName = dl.textStyle;
101
+ const style = styleName && isRecord(styles) ? getProp(styles, styleName) : undefined;
102
+ if (!isRecord(style)) {
103
+ warnings.push(`Labels '${dl.id}' missing textStyle '${styleName ?? '(none)'}'.`);
104
+ continue;
105
+ }
106
+ const anchor = dl.align === 'center' ? 'middle' : dl.align === 'right' ? 'end' : 'start';
107
+ const t = vpInfo.transform;
108
+
109
+ let renderedCount = 0;
110
+ let spatiallyFilteredCount = 0;
111
+
112
+ filteredItems.forEach((item) => {
113
+ // Hardcoded x/z for BIM floor plane convention
114
+ const xVal = getProp(item, 'x');
115
+ const yVal = getProp(item, 'z');
116
+ if (!Number.isFinite(xVal as number) || !Number.isFinite(yVal as number)) {
117
+ return;
118
+ }
119
+ const viewX = (xVal as number) - t.viewMinX;
120
+ const viewY = t.viewMaxZ - (yVal as number);
121
+ const centerModelX = vpInfo.centerModelX;
122
+ const centerModelY = vpInfo.centerModelY;
123
+ const xMm = vpInfo.centerX + (viewX - centerModelX) * vpInfo.mmPerModel;
124
+ const yMm = vpInfo.centerY + (viewY - centerModelY) * vpInfo.mmPerModel;
125
+
126
+ if (viewportClipLabels[dl.viewport] && viewportClipPolygonsLocal[dl.viewport]?.length) {
127
+ const inside = viewportClipPolygonsLocal[dl.viewport].some((poly) =>
128
+ pointInPolygon(poly, viewX, viewY)
129
+ );
130
+ if (!inside) {
131
+ spatiallyFilteredCount++;
132
+ return;
133
+ }
134
+ }
135
+
136
+ renderedCount++;
137
+
138
+ const text = escapeText(
139
+ dl.text && typeof dl.text === 'string'
140
+ ? dl.text.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_m, path) => {
141
+ const val = resolvePath(item, path) ?? getPathValue(dataWithViewport, path);
142
+ return val === undefined || val === null || typeof val === 'object' ? '' : String(val);
143
+ })
144
+ : ''
145
+ );
146
+
147
+ const fontFamily = getProp(style, 'fontFamily');
148
+ const fontSizeMm = getProp(style, 'fontSizeMm');
149
+ const fontWeight = getProp(style, 'fontWeight');
150
+ const color = getProp(style, 'color');
151
+ const letterSpacingMm = getProp(style, 'letterSpacingMm');
152
+ const lineHeightMm = getProp(style, 'lineHeightMm');
153
+
154
+ parts.push(
155
+ `<text x="${xMm}" y="${yMm}" font-family="${escapeAttr(String(fontFamily))}" font-size="${fontSizeMm}"` +
156
+ (fontWeight ? ` font-weight="${escapeAttr(String(fontWeight))}"` : '') +
157
+ (color ? ` fill="${escapeAttr(String(color))}"` : '') +
158
+ (letterSpacingMm !== undefined ? ` letter-spacing="${letterSpacingMm}"` : '') +
159
+ (lineHeightMm !== undefined ? ` line-height="${lineHeightMm}"` : '') +
160
+ ` text-anchor="${anchor}">${text}</text>`
161
+ );
162
+ });
163
+
164
+ console.log(
165
+ `[Labels ${dl.id}] Rendered ${renderedCount} labels. Spatially filtered out: ${spatiallyFilteredCount}`
166
+ );
167
+ }
168
+
169
+ return parts;
170
+ };
@@ -0,0 +1,11 @@
1
+ export const PX_PER_MM = 96 / 25.4;
2
+ export const DEFAULT_LEGEND_SWATCH_WIDTH_MM = 8;
3
+ export const DEFAULT_LEGEND_CELL_PADDING_MM = 2;
4
+ export const DEFAULT_LEGEND_BORDER_WIDTH_MM = 0.25;
5
+ export const DEFAULT_LEGEND_GRID_WIDTH_MM = 0.25;
6
+ export const DEFAULT_LEGEND_HEADER_WIDTH_MM = 0.65;
7
+ export const DEFAULT_LEGEND_OFFSET_MM = 5;
8
+ export const LEGEND_TEXT_WIDTH_FACTOR = 0.7;
9
+ export const LEGEND_MIN_CONTENT_WIDTH_EM = 4;
10
+ export const LEGEND_TEXT_Y_OFFSET_FACTOR = 0.3;
11
+ export const MAX_LEGEND_ROWS = 200;