@twinfinity/printing 6.1.1-beta → 6.1.2-ci.30738-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 (157) hide show
  1. package/dist/BuildInfo.js +3 -3
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/layout/analyzeTemplate.d.ts +4 -4
  7. package/dist/layout/analyzeTemplate.d.ts.map +1 -1
  8. package/dist/layout/analyzeTemplate.js +18 -16
  9. package/dist/layout/analyzeTemplate.js.map +1 -1
  10. package/dist/layout/classClosure.d.ts +41 -0
  11. package/dist/layout/classClosure.d.ts.map +1 -0
  12. package/dist/layout/classClosure.js +143 -0
  13. package/dist/layout/classClosure.js.map +1 -0
  14. package/dist/layout/clipUtils.d.ts +11 -0
  15. package/dist/layout/clipUtils.d.ts.map +1 -1
  16. package/dist/layout/clipUtils.js +67 -6
  17. package/dist/layout/clipUtils.js.map +1 -1
  18. package/dist/layout/filters.d.ts +14 -8
  19. package/dist/layout/filters.d.ts.map +1 -1
  20. package/dist/layout/filters.js +28 -3
  21. package/dist/layout/filters.js.map +1 -1
  22. package/dist/layout/index.d.ts +24 -57
  23. package/dist/layout/index.d.ts.map +1 -1
  24. package/dist/layout/index.js +20 -337
  25. package/dist/layout/index.js.map +1 -1
  26. package/dist/layout/itemBuilders.d.ts +11 -0
  27. package/dist/layout/itemBuilders.d.ts.map +1 -1
  28. package/dist/layout/itemBuilders.js +17 -3
  29. package/dist/layout/itemBuilders.js.map +1 -1
  30. package/dist/layout/labelsRender.d.ts.map +1 -1
  31. package/dist/layout/labelsRender.js +15 -27
  32. package/dist/layout/labelsRender.js.map +1 -1
  33. package/dist/layout/legendRender.d.ts +18 -6
  34. package/dist/layout/legendRender.d.ts.map +1 -1
  35. package/dist/layout/legendRender.js +49 -79
  36. package/dist/layout/legendRender.js.map +1 -1
  37. package/dist/layout/metadata.d.ts.map +1 -1
  38. package/dist/layout/metadata.js +26 -5
  39. package/dist/layout/metadata.js.map +1 -1
  40. package/dist/layout/model.d.ts +4 -3
  41. package/dist/layout/model.d.ts.map +1 -1
  42. package/dist/layout/model.js +17 -57
  43. package/dist/layout/model.js.map +1 -1
  44. package/dist/layout/options.d.ts +1 -2
  45. package/dist/layout/options.d.ts.map +1 -1
  46. package/dist/layout/options.js.map +1 -1
  47. package/dist/layout/preflight.d.ts +32 -0
  48. package/dist/layout/preflight.d.ts.map +1 -0
  49. package/dist/layout/preflight.js +69 -0
  50. package/dist/layout/preflight.js.map +1 -0
  51. package/dist/layout/qrRender.d.ts.map +1 -1
  52. package/dist/layout/qrRender.js +11 -10
  53. package/dist/layout/qrRender.js.map +1 -1
  54. package/dist/layout/renderDrawing.d.ts +28 -17
  55. package/dist/layout/renderDrawing.d.ts.map +1 -1
  56. package/dist/layout/renderDrawing.js +107 -88
  57. package/dist/layout/renderDrawing.js.map +1 -1
  58. package/dist/layout/renderLayout.d.ts +25 -0
  59. package/dist/layout/renderLayout.d.ts.map +1 -0
  60. package/dist/layout/renderLayout.js +315 -0
  61. package/dist/layout/renderLayout.js.map +1 -0
  62. package/dist/layout/renderTemplateSheet.d.ts +99 -0
  63. package/dist/layout/renderTemplateSheet.d.ts.map +1 -0
  64. package/dist/layout/renderTemplateSheet.js +296 -0
  65. package/dist/layout/renderTemplateSheet.js.map +1 -0
  66. package/dist/layout/renderUtils.d.ts +19 -0
  67. package/dist/layout/renderUtils.d.ts.map +1 -1
  68. package/dist/layout/renderUtils.js +47 -4
  69. package/dist/layout/renderUtils.js.map +1 -1
  70. package/dist/layout/sectionDefs.d.ts +65 -0
  71. package/dist/layout/sectionDefs.d.ts.map +1 -0
  72. package/dist/layout/sectionDefs.js +88 -0
  73. package/dist/layout/sectionDefs.js.map +1 -0
  74. package/dist/layout/slabModelView.d.ts +34 -0
  75. package/dist/layout/slabModelView.d.ts.map +1 -0
  76. package/dist/layout/slabModelView.js +175 -0
  77. package/dist/layout/slabModelView.js.map +1 -0
  78. package/dist/layout/templateVariables.d.ts +1 -1
  79. package/dist/layout/templateVariables.d.ts.map +1 -1
  80. package/dist/layout/templateVariables.js +21 -9
  81. package/dist/layout/templateVariables.js.map +1 -1
  82. package/dist/layout/types.d.ts +164 -83
  83. package/dist/layout/types.d.ts.map +1 -1
  84. package/dist/layout/types.js.map +1 -1
  85. package/dist/layout/validation.d.ts.map +1 -1
  86. package/dist/layout/validation.js +78 -33
  87. package/dist/layout/validation.js.map +1 -1
  88. package/dist/layout/viewportPrep.d.ts +8 -6
  89. package/dist/layout/viewportPrep.d.ts.map +1 -1
  90. package/dist/layout/viewportPrep.js +35 -23
  91. package/dist/layout/viewportPrep.js.map +1 -1
  92. package/dist/layout/viewportRender.d.ts +9 -7
  93. package/dist/layout/viewportRender.d.ts.map +1 -1
  94. package/dist/layout/viewportRender.js +93 -126
  95. package/dist/layout/viewportRender.js.map +1 -1
  96. package/dist/layout/viewportUtils.d.ts +20 -0
  97. package/dist/layout/viewportUtils.d.ts.map +1 -1
  98. package/dist/layout/viewportUtils.js +107 -4
  99. package/dist/layout/viewportUtils.js.map +1 -1
  100. package/dist/printToPdf.d.ts +7 -1
  101. package/dist/printToPdf.d.ts.map +1 -1
  102. package/dist/printToPdf.js +81 -14
  103. package/dist/printToPdf.js.map +1 -1
  104. package/dist/printToSvg.d.ts.map +1 -1
  105. package/dist/printToSvg.js +7 -3
  106. package/dist/printToSvg.js.map +1 -1
  107. package/dist/schemaUtils.d.ts +9 -0
  108. package/dist/schemaUtils.d.ts.map +1 -0
  109. package/dist/schemaUtils.js +14 -0
  110. package/dist/schemaUtils.js.map +1 -0
  111. package/dist/templates/default-template.json +30 -8
  112. package/dist/types.d.ts +2 -1
  113. package/dist/types.d.ts.map +1 -1
  114. package/dist/types.js +10 -1
  115. package/dist/types.js.map +1 -1
  116. package/package.json +8 -3
  117. package/src/index.ts +4 -0
  118. package/src/layout/analyzeTemplate.ts +21 -19
  119. package/src/layout/classClosure.ts +142 -0
  120. package/src/layout/clipUtils.ts +65 -6
  121. package/src/layout/filters.ts +31 -3
  122. package/src/layout/index.ts +61 -571
  123. package/src/layout/itemBuilders.ts +20 -5
  124. package/src/layout/labelsRender.ts +23 -40
  125. package/src/layout/legendRender.ts +64 -84
  126. package/src/layout/metadata.ts +26 -5
  127. package/src/layout/model.ts +21 -66
  128. package/src/layout/options.ts +1 -2
  129. package/src/layout/preflight.ts +96 -0
  130. package/src/layout/qrRender.ts +11 -10
  131. package/src/layout/renderDrawing.ts +132 -110
  132. package/src/layout/renderLayout.ts +483 -0
  133. package/src/layout/renderTemplateSheet.ts +435 -0
  134. package/src/layout/renderUtils.ts +53 -4
  135. package/src/layout/sectionDefs.ts +131 -0
  136. package/src/layout/slabModelView.ts +196 -0
  137. package/src/layout/templateVariables.ts +25 -9
  138. package/src/layout/types.ts +160 -70
  139. package/src/layout/validation.ts +98 -32
  140. package/src/layout/viewportPrep.ts +47 -29
  141. package/src/layout/viewportRender.ts +114 -141
  142. package/src/layout/viewportUtils.ts +118 -5
  143. package/src/printToPdf.ts +84 -15
  144. package/src/printToSvg.ts +7 -3
  145. package/src/schemaUtils.ts +13 -0
  146. package/src/templates/default-template.json +30 -8
  147. package/src/types.ts +12 -2
  148. package/templates/default-a0-landscape.json +171 -66
  149. package/templates/default-a0-portrait.json +159 -60
  150. package/templates/default-a1-landscape.json +135 -48
  151. package/templates/default-a1-portrait.json +123 -42
  152. package/templates/default-a3-landscape.json +87 -24
  153. package/templates/default-a3-portrait.json +99 -30
  154. package/templates/default-a4-landscape.json +75 -18
  155. package/templates/default-a4-portrait.json +75 -18
  156. package/templates/llm-authoring-guide.md +247 -0
  157. package/templates/template-schema.json +295 -120
@@ -1 +1 @@
1
- {"version":3,"file":"itemBuilders.js","sourceRoot":"/","sources":["layout/itemBuilders.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAWvC;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAkB,EAAE,aAAgC,EAAyB,EAAE;IACjH,IAAI,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,aAAa,EAAE,QAAQ,IAAI,CAAC,aAAa,EAAE,QAAQ,EAAE,CAAC;QACjF,MAAM,IAAI,KAAK,CACX,kGAAkG,CACrG,CAAC;IACN,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,KAAK,CAAC;IACnD,MAAM,YAAY,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC/C,gFAAgF;IAChF,mFAAmF;IACnF,iEAAiE;IACjE,MAAM,YAAY,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtF,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,MAAM,CAAkB,CAAC,CAAC,eAAe;IACjF,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,IAAI,CAAkB,CAAC,CAAC,uBAAuB;IACrF,sEAAsE;IACtE,MAAM,QAAQ,GAAG,CAAC,SAAS,IAAI,IAAI,CAAkB,CAAC;IACtD,MAAM,UAAU,GAAG,CAAC,YAAY,IAAI,OAAO,IAAI,IAAI,CAAkB,CAAC;IACtE,wCAAwC;IACxC,8BAA8B;IAC9B,iCAAiC;IACjC,oCAAoC;IACpC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;QACjD,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC7D,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,CAAC,GAAG,OAAiB,CAAC;QACpD,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,CAAC,GAAG,OAAiB,CAAC;IACxD,CAAC;IACD,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IACjD,OAAO;QACH,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,CAAuB;QAClD,QAAQ;QACR,UAAU;QACV,YAAY;QACZ,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG;QACtC,WAAW,EAAE,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG;QACxF,IAAI,EAAE,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QAC9E,CAAC;QACD,CAAC;QACD,IAAI,EAAE,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG;QACjF,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;KACpE,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,OAAgB,EAA0C,EAAE;IAC/E,IAAI,CAAC;QACD,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QACtD,MAAM,kBAAkB,GAAG,OAAO,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACtF,MAAM,IAAI,GAAG,OAAO,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAC;QACxD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtE,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACnG,OAAO,EAAE,CAAC,EAAE,CAAE,IAAe,GAAI,IAAe,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,CAAE,IAAe,GAAI,IAAe,CAAC,GAAG,GAAG,EAAE,CAAC;QAC9G,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACL,iGAAiG;QACjG,sEAAsE;IAC1E,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAc,EAA0C,EAAE;IAC7F,IAAI,CAAC;QACD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;YACjD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;QAChC,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC3C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,SAAS;YACrC,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;gBACtB,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;gBAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;gBAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;oBAC7C,IAAI,IAAI,EAAY,CAAC;oBACrB,IAAI,IAAI,EAAY,CAAC;oBACrB,KAAK,EAAE,CAAC;gBACZ,CAAC;YACL,CAAC;QACL,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,CAAC;QAChD,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACL,yCAAyC;IAC7C,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;AAChC,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAC9B,OAAsB,EACtB,OAAmC,EACZ,EAAE;IACzB,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,iFAAiF,CAAC,CAAC;IACvG,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAC7D,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACX,kGAAkG,CACrG,CAAC;IACN,CAAC;IACD,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,aAAc,CAAC,CAAC,CAAC;AACrG,CAAC,CAAC;AAEF,MAAM,UAAU,uBAAuB,CAAC,KAAyB,EAAE,UAAuB;IACtF,MAAM,KAAK,GAAG,qBAAqB,CAC/B,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,iBAAiB,EACvB,UAAU,CACb,CAAC;IACF,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import type { SectionRenderState } from '@twinfinity/geometry';\nimport { normalizePropertySets } from './metadata';\nimport { getPathValue } from './renderUtils';\nimport { buildModelViewFromRaw } from './model';\nimport { ProductFieldPaths, NormalizedProductItem, SvgModelView, LabelStyle } from './types';\n\nimport { getProp } from './typeGuards';\n\nexport interface ProductLike {\n product?: any;\n polylines?: Array<{ points?: Array<{ x: number; z: number }> }>;\n}\n\nexport interface BuildViewportItemsOptions {\n productFields?: ProductFieldPaths;\n}\n\n/**\n * Normalize a single product entry using explicit field paths.\n */\nexport const normalizeProductEntry = (entry: ProductLike, productFields: ProductFieldPaths): NormalizedProductItem => {\n if (!productFields?.idPath || !productFields?.namePath || !productFields?.areaPath) {\n throw new Error(\n 'productFields must include idPath, namePath, and areaPath using propertySets.<Set>.<Prop>.value.'\n );\n }\n const product = getProp(entry, 'product') ?? entry;\n const propertySets = normalizePropertySets(product);\n const getVal = (path: string): unknown => getPathValue({ propertySets }, path);\n const idVal = getVal(productFields.idPath) ?? getProp(product, 'gid');\n const nameVal = getVal(productFields.namePath);\n const areaVal = getVal(productFields.areaPath);\n // class.type = IfcClassType (\"space\", \"walls\", etc.) — used by template filters\n // class.id = PredefinedIfcClassId (\"IfcSpace\", \"IfcWall\", etc.) — used as fallback\n // classPath from propertySets may contain a PredefinedIfcClassId\n const classPathVal = productFields.classPath ? getVal(productFields.classPath) : null;\n const productClass = getProp(product, 'class');\n const classType = getProp(productClass, 'type') as string | null; // IfcClassType\n const classId = getProp(productClass, 'id') as string | null; // PredefinedIfcClassId\n // ifcClass = IfcClassType category; ifcClassId = PredefinedIfcClassId\n const ifcClass = (classType ?? null) as string | null;\n const ifcClassId = (classPathVal ?? classId ?? null) as string | null;\n // Try multiple sources for coordinates:\n // 1. BBox center from product\n // 2. Polylines center from entry\n // 3. Direct x/z on entry or product\n let { x, z } = getBBoxCenter(product);\n if (x === null || z === null) {\n const polyCenter = computePolylinesCenter(entry);\n x = polyCenter.x ?? x;\n z = polyCenter.z ?? z;\n }\n if (x === null || z === null) {\n const directX = getProp(entry, 'x') ?? getProp(product, 'x');\n const directZ = getProp(entry, 'z') ?? getProp(product, 'z');\n if (Number.isFinite(directX)) x = directX as number;\n if (Number.isFinite(directZ)) z = directZ as number;\n }\n const rawPolylines = getProp(entry, 'polylines');\n return {\n gid: getProp(product, 'gid') as string | undefined,\n ifcClass,\n ifcClassId,\n propertySets,\n productId: idVal ? String(idVal) : '-',\n productName: typeof nameVal === 'string' && nameVal.trim().length ? nameVal.trim() : '-',\n area: typeof areaVal === 'number' && Number.isFinite(areaVal) ? areaVal : null,\n x,\n z,\n text: typeof nameVal === 'string' && nameVal.trim().length ? nameVal.trim() : '-',\n polylines: Array.isArray(rawPolylines) ? rawPolylines : undefined\n };\n};\n\nconst getBBoxCenter = (product: unknown): { x: number | null; z: number | null } => {\n try {\n const boundingInfo = getProp(product, 'boundingInfo');\n const boundingInfoResult = typeof boundingInfo === 'function' ? boundingInfo() : null;\n const bbox = getProp(boundingInfoResult, 'boundingBox');\n const min = getProp(bbox, 'minimumWorld') ?? getProp(bbox, 'minimum');\n const max = getProp(bbox, 'maximumWorld') ?? getProp(bbox, 'maximum');\n const minX = getProp(min, 'x');\n const maxX = getProp(max, 'x');\n const minZ = getProp(min, 'z');\n const maxZ = getProp(max, 'z');\n if (Number.isFinite(minX) && Number.isFinite(maxX) && Number.isFinite(minZ) && Number.isFinite(maxZ)) {\n return { x: ((minX as number) + (maxX as number)) * 0.5, z: ((minZ as number) + (maxZ as number)) * 0.5 };\n }\n } catch {\n // Bounding info access may fail for products without geometry or with malformed data structures.\n // This is expected behavior - we return null coordinates as fallback.\n }\n return { x: null, z: null };\n};\n\nexport const computePolylinesCenter = (entry: unknown): { x: number | null; z: number | null } => {\n try {\n const polylines = getProp(entry, 'polylines');\n if (!Array.isArray(polylines) || !polylines.length) {\n return { x: null, z: null };\n }\n let sumX = 0;\n let sumZ = 0;\n let count = 0;\n for (const polyline of polylines) {\n const points = getProp(polyline, 'points');\n if (!Array.isArray(points)) continue;\n for (const pt of points) {\n const px = getProp(pt, 'x');\n const pz = getProp(pt, 'z');\n if (Number.isFinite(px) && Number.isFinite(pz)) {\n sumX += px as number;\n sumZ += pz as number;\n count++;\n }\n }\n }\n if (count > 0) {\n return { x: sumX / count, z: sumZ / count };\n }\n } catch {\n // Polyline access may fail - return null\n }\n return { x: null, z: null };\n};\n\n/**\n * Normalize raw product entries into viewport items using explicit field paths.\n * Requires productFields (id/name/area/class) pointing to propertySets.<Set>.<Prop>.value.\n */\nexport const buildViewportItems = (\n entries: ProductLike[],\n options?: BuildViewportItemsOptions\n): NormalizedProductItem[] => {\n if (!options?.productFields) {\n throw new Error('buildViewportItems requires productFields (idPath/namePath/areaPath/classPath).');\n }\n const { idPath, namePath, areaPath } = options.productFields;\n if (!idPath || !namePath || !areaPath) {\n throw new Error(\n 'productFields must include idPath, namePath, and areaPath using propertySets.<Set>.<Prop>.value.'\n );\n }\n return (entries ?? []).map((entry: any) => normalizeProductEntry(entry, options.productFields!));\n};\n\nexport function sectionStateToModelView(state: SectionRenderState, labelStyle?: LabelStyle): SvgModelView {\n const model = buildModelViewFromRaw(\n state.paths,\n state.labels,\n state.transform,\n state.geometryTransform,\n labelStyle\n );\n if (!model) {\n throw new Error('sectionStateToModelView requires paths, transform, and geometryTransform.');\n }\n return model;\n}\n"]}
1
+ {"version":3,"file":"itemBuilders.js","sourceRoot":"/","sources":["layout/itemBuilders.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SectionRenderState } from '@twinfinity/geometry';\nimport { normalizePropertySets } from './metadata';\nimport { getPathValue } from './renderUtils';\nimport { buildModelViewFromRaw } from './model';\nimport { ProductFieldPaths, NormalizedProductItem, SvgModelView, LabelStyle } from './types';\n\nimport { getProp } from './typeGuards';\n\nexport interface ProductLike {\n product?: any;\n polylines?: Array<{ points?: Array<{ x: number; z: number }> }>;\n}\n\nexport interface BuildViewportItemsOptions {\n productFields?: ProductFieldPaths;\n}\n\n/**\n * Normalize a single product entry using explicit field paths.\n */\nexport const normalizeProductEntry = (entry: ProductLike, productFields: ProductFieldPaths): NormalizedProductItem => {\n if (!productFields?.idPath || !productFields?.namePath || !productFields?.areaPath) {\n throw new Error(\n 'productFields must include idPath, namePath, and areaPath using propertySets.<Set>.<Prop>.value.'\n );\n }\n const product = getProp(entry, 'product') ?? entry;\n const propertySets = normalizePropertySets(product);\n const getVal = (path: string): unknown => getPathValue({ propertySets }, path);\n const idVal = getVal(productFields.idPath) ?? getProp(product, 'gid');\n const nameVal = getVal(productFields.namePath);\n const areaVal = getVal(productFields.areaPath);\n // class.type = IfcClassType (\"space\", \"walls\", etc.) — used by template filters\n // class.id = PredefinedIfcClassId (\"IfcSpace\", \"IfcWall\", etc.) — used as fallback\n // classPath from propertySets may contain a PredefinedIfcClassId\n const classPathVal = productFields.classPath ? getVal(productFields.classPath) : null;\n const productClass = getProp(product, 'class');\n const classType = getProp(productClass, 'type') as string | null; // IfcClassType\n const classId = getProp(productClass, 'id') as string | null; // PredefinedIfcClassId\n // ifcClass = IfcClassType category; ifcClassId = PredefinedIfcClassId\n const ifcClass = (classType ?? null) as string | null;\n const ifcClassId = (classPathVal ?? classId ?? null) as string | null;\n // Try multiple sources for coordinates:\n // 1. BBox center from product\n // 2. Polylines center from entry\n // 3. Direct x/z on entry or product\n let { x, z } = getBBoxCenter(product);\n if (x === null || z === null) {\n const polyCenter = computePolylinesCenter(entry);\n x = polyCenter.x ?? x;\n z = polyCenter.z ?? z;\n }\n if (x === null || z === null) {\n const directX = getProp(entry, 'x') ?? getProp(product, 'x');\n const directZ = getProp(entry, 'z') ?? getProp(product, 'z');\n if (Number.isFinite(directX)) x = directX as number;\n if (Number.isFinite(directZ)) z = directZ as number;\n }\n const rawPolylines = getProp(entry, 'polylines');\n return {\n gid: getProp(product, 'gid') as string | undefined,\n ifcClass,\n ifcClassId,\n propertySets,\n productId: idVal ? String(idVal) : '-',\n productName: typeof nameVal === 'string' && nameVal.trim().length ? nameVal.trim() : '-',\n area: typeof areaVal === 'number' && Number.isFinite(areaVal) ? areaVal : null,\n x,\n z,\n text: typeof nameVal === 'string' && nameVal.trim().length ? nameVal.trim() : '-',\n polylines: Array.isArray(rawPolylines) ? rawPolylines : undefined\n };\n};\n\nconst getBBoxCenter = (product: unknown): { x: number | null; z: number | null } => {\n try {\n const boundingInfo = getProp(product, 'boundingInfo');\n const boundingInfoResult = typeof boundingInfo === 'function' ? boundingInfo() : null;\n const bbox = getProp(boundingInfoResult, 'boundingBox');\n const min = getProp(bbox, 'minimumWorld') ?? getProp(bbox, 'minimum');\n const max = getProp(bbox, 'maximumWorld') ?? getProp(bbox, 'maximum');\n const minX = getProp(min, 'x');\n const maxX = getProp(max, 'x');\n const minZ = getProp(min, 'z');\n const maxZ = getProp(max, 'z');\n if (Number.isFinite(minX) && Number.isFinite(maxX) && Number.isFinite(minZ) && Number.isFinite(maxZ)) {\n return { x: ((minX as number) + (maxX as number)) * 0.5, z: ((minZ as number) + (maxZ as number)) * 0.5 };\n }\n } catch {\n // Bounding info access may fail for products without geometry or with malformed data structures.\n // This is expected behavior - we return null coordinates as fallback.\n }\n return { x: null, z: null };\n};\n\n/**\n * Read the plan-projection X/Z of a polyline point, tolerating both shapes the\n * section pipeline emits: `Vec3` arrays `[x, y, z]` (from\n * `computePlaneSectionsForProducts`) and `{ x, z }` objects. Coords are `null`\n * when the point is neither / non-finite. Shared by every consumer so the dual\n * contract is handled in exactly one place.\n */\nexport const readXZ = (pt: unknown): { x: number | null; z: number | null } => {\n const rawX = Array.isArray(pt) ? pt[0] : getProp(pt, 'x');\n const rawZ = Array.isArray(pt) ? pt[2] : getProp(pt, 'z');\n return {\n x: Number.isFinite(rawX) ? (rawX as number) : null,\n z: Number.isFinite(rawZ) ? (rawZ as number) : null\n };\n};\n\nexport const computePolylinesCenter = (entry: unknown): { x: number | null; z: number | null } => {\n try {\n const polylines = getProp(entry, 'polylines');\n if (!Array.isArray(polylines) || !polylines.length) {\n return { x: null, z: null };\n }\n let sumX = 0;\n let sumZ = 0;\n let count = 0;\n for (const polyline of polylines) {\n const points = getProp(polyline, 'points');\n if (!Array.isArray(points)) continue;\n for (const pt of points) {\n const { x: px, z: pz } = readXZ(pt);\n if (px !== null && pz !== null) {\n sumX += px;\n sumZ += pz;\n count++;\n }\n }\n }\n if (count > 0) {\n return { x: sumX / count, z: sumZ / count };\n }\n } catch {\n // Polyline access may fail - return null\n }\n return { x: null, z: null };\n};\n\n/**\n * Normalize raw product entries into viewport items using explicit field paths.\n * Requires productFields (id/name/area/class) pointing to propertySets.<Set>.<Prop>.value.\n */\nexport const buildViewportItems = (\n entries: ProductLike[],\n options?: BuildViewportItemsOptions\n): NormalizedProductItem[] => {\n if (!options?.productFields) {\n throw new Error('buildViewportItems requires productFields (idPath/namePath/areaPath/classPath).');\n }\n const { idPath, namePath, areaPath } = options.productFields;\n if (!idPath || !namePath || !areaPath) {\n throw new Error(\n 'productFields must include idPath, namePath, and areaPath using propertySets.<Set>.<Prop>.value.'\n );\n }\n return (entries ?? []).map((entry: any) => normalizeProductEntry(entry, options.productFields!));\n};\n\nexport function sectionStateToModelView(state: SectionRenderState, labelStyle?: LabelStyle): SvgModelView {\n const model = buildModelViewFromRaw(\n state.paths,\n state.labels,\n state.transform,\n state.geometryTransform,\n labelStyle\n );\n if (!model) {\n throw new Error('sectionStateToModelView requires paths, transform, and geometryTransform.');\n }\n return model;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"labelsRender.d.ts","sourceRoot":"/","sources":["layout/labelsRender.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,MAAM,WAAW,kBAAkB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,YAAY,GACrB,eAAe,aAAa,EAAE,EAC9B,MAAM,UAAU,EAChB,oBAAoB,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACtD,oBAAoB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3C,2BAA2B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC,EACjF,2BAA2B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC,EACjF,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,kBAAkB,OAAO,EACzB,UAAU,MAAM,EAAE,KACnB,MAAM,EAmIR,CAAC"}
1
+ {"version":3,"file":"labelsRender.d.ts","sourceRoot":"/","sources":["layout/labelsRender.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAepD,MAAM,WAAW,kBAAkB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,YAAY,GACrB,eAAe,aAAa,EAAE,EAC9B,MAAM,UAAU,EAChB,oBAAoB,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACtD,oBAAoB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3C,2BAA2B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC,EACjF,2BAA2B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC,EACjF,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,kBAAkB,OAAO,EACzB,UAAU,MAAM,EAAE,KACnB,MAAM,EA0GR,CAAC"}
@@ -1,6 +1,6 @@
1
- import { matchesV2Filter } from './filters';
1
+ import { matchesFilter } from './filters';
2
2
  import { pointInPolygon } from './clipUtils';
3
- import { getPathValue, resolvePath, escapeAttr, escapeText } from './renderUtils';
3
+ import { getPathValue, resolvePath, escapeAttr, escapeText, formatNumber, splitPlaceholder, applyPlaceholderFilter } from './renderUtils';
4
4
  import { isRecord, getProp } from './typeGuards';
5
5
  export const renderLabels = (labelElements, data, viewportRenderInfo, viewportClipLabels, viewportClipPolygonsLocal, viewportClipPolygonsWorld, styles, dataWithViewport, warnings) => {
6
6
  const parts = [];
@@ -24,32 +24,21 @@ export const renderLabels = (labelElements, data, viewportRenderInfo, viewportCl
24
24
  continue;
25
25
  }
26
26
  // Filter by class + where
27
- let filteredItems = dl.class || dl.where ? items.filter((item) => matchesV2Filter(item, dl.class, dl.where)) : items;
28
- console.log(`[Labels ${dl.id}] Total items:`, items.length, 'After class+where filter:', filteredItems.length);
27
+ let filteredItems = dl.class || dl.where ? items.filter((item) => matchesFilter(item, dl.class, dl.where)) : items;
29
28
  // If matchClipFilter is true, filter by spatial position inside clip polygon
30
29
  if (dl.matchClipFilter && viewportClipPolygonsWorld[dl.viewport]?.length) {
31
- const clipPolygons = viewportClipPolygonsWorld[dl.viewport];
32
- console.log(`[Labels ${dl.id}] Applying matchClipFilter (spatial) with ${clipPolygons.length} polygons`);
33
- const beforeClipFilter = filteredItems.length;
30
+ // pointInPolygon expects y — world polys are {x, z}; map ONCE, not per item.
31
+ const polys2D = viewportClipPolygonsWorld[dl.viewport].map((poly) => poly.map((p) => ({ x: p.x, y: p.z })));
34
32
  filteredItems = filteredItems.filter((item) => {
35
33
  const ix = getProp(item, 'x');
36
34
  const iz = getProp(item, 'z');
37
35
  if (!Number.isFinite(ix) || !Number.isFinite(iz))
38
36
  return false;
39
- return clipPolygons.some((poly) => {
40
- // pointInPolygon expects y coordinate, but we use z for world space
41
- const polyWith2D = poly.map((p) => ({ x: p.x, y: p.z }));
42
- return pointInPolygon(polyWith2D, ix, iz);
43
- });
37
+ return polys2D.some((poly) => pointInPolygon(poly, ix, iz));
44
38
  });
45
- console.log(`[Labels ${dl.id}] After matchClipFilter (spatial):`, filteredItems.length, '(was:', beforeClipFilter, ')');
46
- // Debug: log first few items to see their properties
47
- if (filteredItems.length === 0 && beforeClipFilter > 0) {
48
- console.log('[Labels] Sample item that was filtered out:', items[0]);
49
- }
50
39
  }
51
40
  if (!filteredItems.length) {
52
- console.log(`[Labels ${dl.id}] No items left after filtering, skipping.`);
41
+ warnings.push(`Labels '${dl.id}' matched 0 of ${items.length} items after filtering.`);
53
42
  continue;
54
43
  }
55
44
  const styleName = dl.textStyle;
@@ -58,10 +47,9 @@ export const renderLabels = (labelElements, data, viewportRenderInfo, viewportCl
58
47
  warnings.push(`Labels '${dl.id}' missing textStyle '${styleName ?? '(none)'}'.`);
59
48
  continue;
60
49
  }
61
- const anchor = dl.align === 'center' ? 'middle' : dl.align === 'right' ? 'end' : 'start';
50
+ // Schema default is center labels sit on item centroids.
51
+ const anchor = dl.align === 'left' ? 'start' : dl.align === 'right' ? 'end' : 'middle';
62
52
  const t = vpInfo.transform;
63
- let renderedCount = 0;
64
- let spatiallyFilteredCount = 0;
65
53
  filteredItems.forEach((item) => {
66
54
  // Hardcoded x/z for BIM floor plane convention
67
55
  const xVal = getProp(item, 'x');
@@ -78,15 +66,16 @@ export const renderLabels = (labelElements, data, viewportRenderInfo, viewportCl
78
66
  if (viewportClipLabels[dl.viewport] && viewportClipPolygonsLocal[dl.viewport]?.length) {
79
67
  const inside = viewportClipPolygonsLocal[dl.viewport].some((poly) => pointInPolygon(poly, viewX, viewY));
80
68
  if (!inside) {
81
- spatiallyFilteredCount++;
82
69
  return;
83
70
  }
84
71
  }
85
- renderedCount++;
86
72
  const text = escapeText(dl.text && typeof dl.text === 'string'
87
- ? dl.text.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_m, path) => {
73
+ ? dl.text.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_m, expr) => {
74
+ const { path, filter } = splitPlaceholder(expr);
88
75
  const val = resolvePath(item, path) ?? getPathValue(dataWithViewport, path);
89
- return val === undefined || val === null || typeof val === 'object' ? '' : String(val);
76
+ return val === undefined || val === null || typeof val === 'object'
77
+ ? ''
78
+ : String(applyPlaceholderFilter(val, filter));
90
79
  })
91
80
  : '');
92
81
  const fontFamily = getProp(style, 'fontFamily');
@@ -95,14 +84,13 @@ export const renderLabels = (labelElements, data, viewportRenderInfo, viewportCl
95
84
  const color = getProp(style, 'color');
96
85
  const letterSpacingMm = getProp(style, 'letterSpacingMm');
97
86
  const lineHeightMm = getProp(style, 'lineHeightMm');
98
- parts.push(`<text x="${xMm}" y="${yMm}" font-family="${escapeAttr(String(fontFamily))}" font-size="${fontSizeMm}"` +
87
+ parts.push(`<text x="${formatNumber(xMm)}" y="${formatNumber(yMm)}" font-family="${escapeAttr(String(fontFamily))}" font-size="${fontSizeMm}"` +
99
88
  (fontWeight ? ` font-weight="${escapeAttr(String(fontWeight))}"` : '') +
100
89
  (color ? ` fill="${escapeAttr(String(color))}"` : '') +
101
90
  (letterSpacingMm !== undefined ? ` letter-spacing="${letterSpacingMm}"` : '') +
102
91
  (lineHeightMm !== undefined ? ` line-height="${lineHeightMm}"` : '') +
103
92
  ` text-anchor="${anchor}">${text}</text>`);
104
93
  });
105
- console.log(`[Labels ${dl.id}] Rendered ${renderedCount} labels. Spatially filtered out: ${spatiallyFilteredCount}`);
106
94
  }
107
95
  return parts;
108
96
  };
@@ -1 +1 @@
1
- {"version":3,"file":"labelsRender.js","sourceRoot":"/","sources":["layout/labelsRender.ts"],"names":[],"mappings":"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","sourcesContent":["import { LabelsElement, LayoutData } from './types';\nimport { matchesV2Filter } from './filters';\nimport { pointInPolygon } from './clipUtils';\nimport { getPathValue, resolvePath, escapeAttr, escapeText } from './renderUtils';\n\nimport { isRecord, getProp } from './typeGuards';\n\nexport interface ViewportRenderInfo {\n mmPerModel: number;\n contentWidthMm: number;\n contentHeightMm: number;\n offsetX: number;\n offsetY: number;\n rotationDeg: number;\n transform: {\n viewMinX: number;\n viewMaxX: number;\n viewMinZ: number;\n viewMaxZ: number;\n viewWidth: number;\n viewHeight: number;\n };\n centerX: number;\n centerY: number;\n centerModelX: number;\n centerModelY: number;\n}\n\nexport const renderLabels = (\n labelElements: LabelsElement[],\n data: LayoutData,\n viewportRenderInfo: Record<string, ViewportRenderInfo>,\n viewportClipLabels: Record<string, boolean>,\n viewportClipPolygonsLocal: Record<string, Array<Array<{ x: number; y: number }>>>,\n viewportClipPolygonsWorld: Record<string, Array<Array<{ x: number; z: number }>>>,\n styles: Record<string, unknown>,\n dataWithViewport: unknown,\n warnings: string[]\n): string[] => {\n const parts: string[] = [];\n\n for (const dl of labelElements) {\n const vpInfo = viewportRenderInfo[dl.viewport];\n if (!vpInfo) {\n warnings.push(`Labels '${dl.id}' missing viewport '${dl.viewport}'.`);\n continue;\n }\n if (vpInfo.rotationDeg !== 0) {\n warnings.push(`Labels '${dl.id}' skipped because rotation is not supported.`);\n continue;\n }\n if (!vpInfo.transform) {\n warnings.push(`Labels '${dl.id}' skipped because model transform is missing.`);\n continue;\n }\n // Items come from the bound viewport's data\n const items = getPathValue(data, `viewports.${dl.viewport}.items`);\n if (!Array.isArray(items) || !items.length) {\n continue;\n }\n // Filter by class + where\n let filteredItems =\n dl.class || dl.where ? items.filter((item) => matchesV2Filter(item, dl.class, dl.where)) : items;\n\n console.log(`[Labels ${dl.id}] Total items:`, items.length, 'After class+where filter:', filteredItems.length);\n\n // If matchClipFilter is true, filter by spatial position inside clip polygon\n if (dl.matchClipFilter && viewportClipPolygonsWorld[dl.viewport]?.length) {\n const clipPolygons = viewportClipPolygonsWorld[dl.viewport];\n console.log(`[Labels ${dl.id}] Applying matchClipFilter (spatial) with ${clipPolygons.length} polygons`);\n const beforeClipFilter = filteredItems.length;\n filteredItems = filteredItems.filter((item) => {\n const ix = getProp(item, 'x');\n const iz = getProp(item, 'z');\n if (!Number.isFinite(ix) || !Number.isFinite(iz)) return false;\n return clipPolygons.some((poly) => {\n // pointInPolygon expects y coordinate, but we use z for world space\n const polyWith2D = poly.map((p) => ({ x: p.x, y: p.z }));\n return pointInPolygon(polyWith2D, ix as number, iz as number);\n });\n });\n console.log(\n `[Labels ${dl.id}] After matchClipFilter (spatial):`,\n filteredItems.length,\n '(was:',\n beforeClipFilter,\n ')'\n );\n\n // Debug: log first few items to see their properties\n if (filteredItems.length === 0 && beforeClipFilter > 0) {\n console.log('[Labels] Sample item that was filtered out:', items[0]);\n }\n }\n\n if (!filteredItems.length) {\n console.log(`[Labels ${dl.id}] No items left after filtering, skipping.`);\n continue;\n }\n const styleName = dl.textStyle;\n const style = styleName && isRecord(styles) ? getProp(styles, styleName) : undefined;\n if (!isRecord(style)) {\n warnings.push(`Labels '${dl.id}' missing textStyle '${styleName ?? '(none)'}'.`);\n continue;\n }\n const anchor = dl.align === 'center' ? 'middle' : dl.align === 'right' ? 'end' : 'start';\n const t = vpInfo.transform;\n\n let renderedCount = 0;\n let spatiallyFilteredCount = 0;\n\n filteredItems.forEach((item) => {\n // Hardcoded x/z for BIM floor plane convention\n const xVal = getProp(item, 'x');\n const yVal = getProp(item, 'z');\n if (!Number.isFinite(xVal as number) || !Number.isFinite(yVal as number)) {\n return;\n }\n const viewX = (xVal as number) - t.viewMinX;\n const viewY = t.viewMaxZ - (yVal as number);\n const centerModelX = vpInfo.centerModelX;\n const centerModelY = vpInfo.centerModelY;\n const xMm = vpInfo.centerX + (viewX - centerModelX) * vpInfo.mmPerModel;\n const yMm = vpInfo.centerY + (viewY - centerModelY) * vpInfo.mmPerModel;\n\n if (viewportClipLabels[dl.viewport] && viewportClipPolygonsLocal[dl.viewport]?.length) {\n const inside = viewportClipPolygonsLocal[dl.viewport].some((poly) =>\n pointInPolygon(poly, viewX, viewY)\n );\n if (!inside) {\n spatiallyFilteredCount++;\n return;\n }\n }\n\n renderedCount++;\n\n const text = escapeText(\n dl.text && typeof dl.text === 'string'\n ? dl.text.replace(/\\{\\{\\s*([^}]+?)\\s*\\}\\}/g, (_m, path) => {\n const val = resolvePath(item, path) ?? getPathValue(dataWithViewport, path);\n return val === undefined || val === null || typeof val === 'object' ? '' : String(val);\n })\n : ''\n );\n\n const fontFamily = getProp(style, 'fontFamily');\n const fontSizeMm = getProp(style, 'fontSizeMm');\n const fontWeight = getProp(style, 'fontWeight');\n const color = getProp(style, 'color');\n const letterSpacingMm = getProp(style, 'letterSpacingMm');\n const lineHeightMm = getProp(style, 'lineHeightMm');\n\n parts.push(\n `<text x=\"${xMm}\" y=\"${yMm}\" font-family=\"${escapeAttr(String(fontFamily))}\" font-size=\"${fontSizeMm}\"` +\n (fontWeight ? ` font-weight=\"${escapeAttr(String(fontWeight))}\"` : '') +\n (color ? ` fill=\"${escapeAttr(String(color))}\"` : '') +\n (letterSpacingMm !== undefined ? ` letter-spacing=\"${letterSpacingMm}\"` : '') +\n (lineHeightMm !== undefined ? ` line-height=\"${lineHeightMm}\"` : '') +\n ` text-anchor=\"${anchor}\">${text}</text>`\n );\n });\n\n console.log(\n `[Labels ${dl.id}] Rendered ${renderedCount} labels. Spatially filtered out: ${spatiallyFilteredCount}`\n );\n }\n\n return parts;\n};\n"]}
1
+ {"version":3,"file":"labelsRender.js","sourceRoot":"/","sources":["layout/labelsRender.ts"],"names":[],"mappings":"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","sourcesContent":["import { LabelsElement, LayoutData } from './types';\nimport { matchesFilter } from './filters';\nimport { pointInPolygon } from './clipUtils';\nimport {\n getPathValue,\n resolvePath,\n escapeAttr,\n escapeText,\n formatNumber,\n splitPlaceholder,\n applyPlaceholderFilter\n} from './renderUtils';\n\nimport { isRecord, getProp } from './typeGuards';\n\nexport interface ViewportRenderInfo {\n mmPerModel: number;\n contentWidthMm: number;\n contentHeightMm: number;\n offsetX: number;\n offsetY: number;\n rotationDeg: number;\n transform: {\n viewMinX: number;\n viewMaxX: number;\n viewMinZ: number;\n viewMaxZ: number;\n viewWidth: number;\n viewHeight: number;\n };\n centerX: number;\n centerY: number;\n centerModelX: number;\n centerModelY: number;\n}\n\nexport const renderLabels = (\n labelElements: LabelsElement[],\n data: LayoutData,\n viewportRenderInfo: Record<string, ViewportRenderInfo>,\n viewportClipLabels: Record<string, boolean>,\n viewportClipPolygonsLocal: Record<string, Array<Array<{ x: number; y: number }>>>,\n viewportClipPolygonsWorld: Record<string, Array<Array<{ x: number; z: number }>>>,\n styles: Record<string, unknown>,\n dataWithViewport: unknown,\n warnings: string[]\n): string[] => {\n const parts: string[] = [];\n\n for (const dl of labelElements) {\n const vpInfo = viewportRenderInfo[dl.viewport];\n if (!vpInfo) {\n warnings.push(`Labels '${dl.id}' missing viewport '${dl.viewport}'.`);\n continue;\n }\n if (vpInfo.rotationDeg !== 0) {\n warnings.push(`Labels '${dl.id}' skipped because rotation is not supported.`);\n continue;\n }\n if (!vpInfo.transform) {\n warnings.push(`Labels '${dl.id}' skipped because model transform is missing.`);\n continue;\n }\n // Items come from the bound viewport's data\n const items = getPathValue(data, `viewports.${dl.viewport}.items`);\n if (!Array.isArray(items) || !items.length) {\n continue;\n }\n // Filter by class + where\n let filteredItems =\n dl.class || dl.where ? items.filter((item) => matchesFilter(item, dl.class, dl.where)) : items;\n\n // If matchClipFilter is true, filter by spatial position inside clip polygon\n if (dl.matchClipFilter && viewportClipPolygonsWorld[dl.viewport]?.length) {\n // pointInPolygon expects y — world polys are {x, z}; map ONCE, not per item.\n const polys2D = viewportClipPolygonsWorld[dl.viewport].map((poly) => poly.map((p) => ({ x: p.x, y: p.z })));\n filteredItems = filteredItems.filter((item) => {\n const ix = getProp(item, 'x');\n const iz = getProp(item, 'z');\n if (!Number.isFinite(ix) || !Number.isFinite(iz)) return false;\n return polys2D.some((poly) => pointInPolygon(poly, ix as number, iz as number));\n });\n }\n\n if (!filteredItems.length) {\n warnings.push(`Labels '${dl.id}' matched 0 of ${items.length} items after filtering.`);\n continue;\n }\n const styleName = dl.textStyle;\n const style = styleName && isRecord(styles) ? getProp(styles, styleName) : undefined;\n if (!isRecord(style)) {\n warnings.push(`Labels '${dl.id}' missing textStyle '${styleName ?? '(none)'}'.`);\n continue;\n }\n // Schema default is center — labels sit on item centroids.\n const anchor = dl.align === 'left' ? 'start' : dl.align === 'right' ? 'end' : 'middle';\n const t = vpInfo.transform;\n\n filteredItems.forEach((item) => {\n // Hardcoded x/z for BIM floor plane convention\n const xVal = getProp(item, 'x');\n const yVal = getProp(item, 'z');\n if (!Number.isFinite(xVal as number) || !Number.isFinite(yVal as number)) {\n return;\n }\n const viewX = (xVal as number) - t.viewMinX;\n const viewY = t.viewMaxZ - (yVal as number);\n const centerModelX = vpInfo.centerModelX;\n const centerModelY = vpInfo.centerModelY;\n const xMm = vpInfo.centerX + (viewX - centerModelX) * vpInfo.mmPerModel;\n const yMm = vpInfo.centerY + (viewY - centerModelY) * vpInfo.mmPerModel;\n\n if (viewportClipLabels[dl.viewport] && viewportClipPolygonsLocal[dl.viewport]?.length) {\n const inside = viewportClipPolygonsLocal[dl.viewport].some((poly) =>\n pointInPolygon(poly, viewX, viewY)\n );\n if (!inside) {\n return;\n }\n }\n\n const text = escapeText(\n dl.text && typeof dl.text === 'string'\n ? dl.text.replace(/\\{\\{\\s*([^}]+?)\\s*\\}\\}/g, (_m, expr) => {\n const { path, filter } = splitPlaceholder(expr);\n const val = resolvePath(item, path) ?? getPathValue(dataWithViewport, path);\n return val === undefined || val === null || typeof val === 'object'\n ? ''\n : String(applyPlaceholderFilter(val, filter));\n })\n : ''\n );\n\n const fontFamily = getProp(style, 'fontFamily');\n const fontSizeMm = getProp(style, 'fontSizeMm');\n const fontWeight = getProp(style, 'fontWeight');\n const color = getProp(style, 'color');\n const letterSpacingMm = getProp(style, 'letterSpacingMm');\n const lineHeightMm = getProp(style, 'lineHeightMm');\n\n parts.push(\n `<text x=\"${formatNumber(xMm)}\" y=\"${formatNumber(yMm)}\" font-family=\"${escapeAttr(String(fontFamily))}\" font-size=\"${fontSizeMm}\"` +\n (fontWeight ? ` font-weight=\"${escapeAttr(String(fontWeight))}\"` : '') +\n (color ? ` fill=\"${escapeAttr(String(color))}\"` : '') +\n (letterSpacingMm !== undefined ? ` letter-spacing=\"${letterSpacingMm}\"` : '') +\n (lineHeightMm !== undefined ? ` line-height=\"${lineHeightMm}\"` : '') +\n ` text-anchor=\"${anchor}\">${text}</text>`\n );\n });\n }\n\n return parts;\n};\n"]}
@@ -1,13 +1,25 @@
1
1
  import { LegendElement, PageSpec, StyleMap } from './types';
2
- export declare const applyLegendFilter: (legendData: unknown[], legend: LegendElement, viewports: unknown[], viewportClipPolygonsLocal: Record<string, Array<Array<{
2
+ export declare const applyLegendFilter: (legendData: unknown[], legend: LegendElement, viewportClipPolygonsWorld: Record<string, Array<Array<{
3
3
  x: number;
4
- y: number;
5
- }>>>, viewportRenderInfo: Record<string, {
6
- transform?: unknown;
7
- }>) => unknown[];
4
+ z: number;
5
+ }>>>) => unknown[];
6
+ /**
7
+ * Colors a legend assigned to its rows (assignColors), for consumption by
8
+ * viewport fill rules with `fill: "legend"`. Lookup order for an item:
9
+ * its gid (per-row identity — works without groupBy), then the value of the
10
+ * legend's groupBy property (shared across a group).
11
+ */
12
+ export interface LegendColorAssignment {
13
+ /** The legend's groupBy property path, when set. */
14
+ groupBy?: string;
15
+ /** Row color by item gid/id. */
16
+ byGid: Map<string, string>;
17
+ /** Row color by groupBy property value (only populated with groupBy). */
18
+ byValue: Map<string, string>;
19
+ }
8
20
  export interface LegendResult {
9
21
  svg: string;
10
- colorMap?: Map<string, string>;
22
+ colors?: LegendColorAssignment;
11
23
  }
12
24
  export declare const buildLegendGroup: (legend: LegendElement, styles: StyleMap | undefined, page: PageSpec, marginMm: number | undefined, legendData: unknown[], fillOpacity?: number) => LegendResult | null;
13
25
  //# sourceMappingURL=legendRender.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"legendRender.d.ts","sourceRoot":"/","sources":["layout/legendRender.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA+E5D,eAAO,MAAM,iBAAiB,GAC1B,YAAY,OAAO,EAAE,EACrB,QAAQ,aAAa,EACrB,WAAW,OAAO,EAAE,EACpB,2BAA2B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC,EACjF,oBAAoB,MAAM,CAAC,MAAM,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,KAC5D,OAAO,EA6BT,CAAC;AAEF,MAAM,WAAW,YAAY;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,eAAO,MAAM,gBAAgB,GACzB,QAAQ,aAAa,EACrB,QAAQ,QAAQ,GAAG,SAAS,EAC5B,MAAM,QAAQ,EACd,UAAU,MAAM,GAAG,SAAS,EAC5B,YAAY,OAAO,EAAE,EACrB,cAAc,MAAM,KACrB,YAAY,GAAG,IA8ZjB,CAAC"}
1
+ {"version":3,"file":"legendRender.d.ts","sourceRoot":"/","sources":["layout/legendRender.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA+E5D,eAAO,MAAM,iBAAiB,GAC1B,YAAY,OAAO,EAAE,EACrB,QAAQ,aAAa,EACrB,2BAA2B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC,KAClF,OAAO,EAyBT,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IAClC,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,yEAAyE;IACzE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED,eAAO,MAAM,gBAAgB,GACzB,QAAQ,aAAa,EACrB,QAAQ,QAAQ,GAAG,SAAS,EAC5B,MAAM,QAAQ,EACd,UAAU,MAAM,GAAG,SAAS,EAC5B,YAAY,OAAO,EAAE,EACrB,cAAc,MAAM,KACrB,YAAY,GAAG,IAiYjB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_LEGEND_SWATCH_WIDTH_MM, DEFAULT_LEGEND_CELL_PADDING_MM, DEFAULT_LEGEND_BORDER_WIDTH_MM, DEFAULT_LEGEND_GRID_WIDTH_MM, DEFAULT_LEGEND_HEADER_WIDTH_MM, DEFAULT_LEGEND_OFFSET_MM, MAX_LEGEND_ROWS } from './legendConstants';
2
2
  import { computeLegendColumnWidths, computeLegendTextYOffset, parseLegendColor, formatLegendValue } from './legendUtils';
3
- import { resolvePath } from './renderUtils';
4
- import { matchesV2Filter } from './filters';
3
+ import { resolvePath, escapeText, escapeAttr } from './renderUtils';
4
+ import { matchesFilter } from './filters';
5
5
  import { pointInPolygon } from './clipUtils';
6
6
  /**
7
7
  * Convert HSL to RGB for PDF compatibility.
@@ -58,32 +58,28 @@ function generateLegendColors(count, palette) {
58
58
  return colors;
59
59
  }
60
60
  import { isRecord, getProp } from './typeGuards';
61
- export const applyLegendFilter = (legendData, legend, viewports, viewportClipPolygonsLocal, viewportRenderInfo) => {
61
+ export const applyLegendFilter = (legendData, legend, viewportClipPolygonsWorld) => {
62
62
  // Filter by class + where
63
63
  let rows = Array.isArray(legendData)
64
- ? legendData.filter((row) => matchesV2Filter(row, legend.class, legend.where))
64
+ ? legendData.filter((row) => matchesFilter(row, legend.class, legend.where))
65
65
  : [];
66
66
  if (!rows.length)
67
67
  return rows;
68
+ // clipToViewport filters in WORLD space (same convention as labels and
69
+ // clipAllowedGids) so the result is available before viewports render —
70
+ // legend color maps must exist by then for fill: "legend".
68
71
  if (legend.clipToViewport) {
69
72
  const targetViewportId = legend.viewport;
70
- const polys = targetViewportId ? viewportClipPolygonsLocal[targetViewportId] : null;
71
- const vpInfo = targetViewportId ? viewportRenderInfo[targetViewportId] : null;
72
- const t = isRecord(vpInfo) ? getProp(vpInfo, 'transform') : null;
73
- if (polys && polys.length && isRecord(t)) {
74
- const viewMinX = getProp(t, 'viewMinX');
75
- const viewMaxZ = getProp(t, 'viewMaxZ');
76
- if (typeof viewMinX === 'number' && typeof viewMaxZ === 'number') {
77
- rows = rows.filter((row) => {
78
- const rowX = getProp(row, 'x');
79
- const rowZ = getProp(row, 'z');
80
- if (!Number.isFinite(rowX) || !Number.isFinite(rowZ))
81
- return false;
82
- const viewX = Number(rowX) - viewMinX;
83
- const viewY = viewMaxZ - Number(rowZ);
84
- return polys.some((poly) => pointInPolygon(poly, viewX, viewY));
85
- });
86
- }
73
+ const polys = targetViewportId ? viewportClipPolygonsWorld[targetViewportId] : null;
74
+ if (polys && polys.length) {
75
+ const polys2D = polys.map((poly) => poly.map((p) => ({ x: p.x, y: p.z })));
76
+ rows = rows.filter((row) => {
77
+ const rowX = getProp(row, 'x');
78
+ const rowZ = getProp(row, 'z');
79
+ if (!Number.isFinite(rowX) || !Number.isFinite(rowZ))
80
+ return false;
81
+ return polys2D.some((poly) => pointInPolygon(poly, Number(rowX), Number(rowZ)));
82
+ });
87
83
  }
88
84
  }
89
85
  return rows;
@@ -91,34 +87,24 @@ export const applyLegendFilter = (legendData, legend, viewports, viewportClipPol
91
87
  export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fillOpacity) => {
92
88
  if (!legend || !legendData || !legendData.length)
93
89
  return null;
94
- // Debug: Log raw input data
95
- // console.log(`[LEGEND INPUT] Received ${legendData.length} items for legend "${legend.id}"`);
96
- // if (legend.groupBy) {
97
- // // Count duplicates by groupBy property
98
- // const counts = new Map<string, number>();
99
- // for (const item of legendData) {
100
- // const key = String(resolvePath(item, legend.groupBy) ?? '');
101
- // counts.set(key, (counts.get(key) || 0) + 1);
102
- // }
103
- // console.log('[LEGEND INPUT] Items per group (before grouping):', Array.from(counts.entries()).map(([k, v]) => `${k}: ${v}`).join(', '));
104
- // }
105
90
  const pageMargin = typeof marginMm === 'number' ? marginMm : 0;
106
91
  const fontStyle = (legend.textStyle && styles?.[legend.textStyle]) || styles?.['body'];
107
92
  const fontSizeMm = typeof fontStyle?.fontSizeMm === 'number' ? fontStyle.fontSizeMm : 5;
108
- const fontFamily = fontStyle?.fontFamily ?? 'Arial, sans-serif';
109
- const fontColor = isRecord(fontStyle) ? (getProp(fontStyle, 'color') ?? '#000') : '#000';
93
+ // Escaped once here every emitted <text>/<rect> below interpolates these
94
+ // (and cell text via escapeText) into XML.
95
+ const fontFamily = escapeAttr(String(fontStyle?.fontFamily ?? 'Arial, sans-serif'));
96
+ const fontColor = escapeAttr(String(isRecord(fontStyle) ? (getProp(fontStyle, 'color') ?? '#000') : '#000'));
110
97
  // Show color column if enabled (but swatches will be empty if no fills configured)
111
98
  const showColorColumn = legend.showColorColumn !== false;
112
99
  const colorColumnWidthMm = showColorColumn ? (legend.colorColumnWidthMm ?? DEFAULT_LEGEND_SWATCH_WIDTH_MM) : 0;
113
100
  const hasFills = fillOpacity !== undefined;
114
- // console.log(`[LEGEND RENDER] fillOpacity=${fillOpacity}, hasFills=${hasFills}, showColorColumn=${showColorColumn}`);
115
101
  const cellPaddingMm = legend.cellPaddingMm ?? DEFAULT_LEGEND_CELL_PADDING_MM;
116
- const borderStrokeWidthMm = legend.borderStrokeWidthMm ?? DEFAULT_LEGEND_BORDER_WIDTH_MM;
117
- const borderStrokeColor = legend.borderStrokeColor ?? '#000000';
118
- const gridStrokeWidthMm = legend.gridStrokeWidthMm ?? DEFAULT_LEGEND_GRID_WIDTH_MM;
119
- const gridStrokeColor = legend.gridStrokeColor ?? '#000000';
120
- const headerStrokeWidthMm = legend.headerStrokeWidthMm ?? DEFAULT_LEGEND_HEADER_WIDTH_MM;
121
- const headerStrokeColor = legend.headerStrokeColor ?? '#000000';
102
+ const borderStrokeWidthMm = legend.border?.strokeWidthMm ?? DEFAULT_LEGEND_BORDER_WIDTH_MM;
103
+ const borderStrokeColor = escapeAttr(legend.border?.stroke ?? '#000000');
104
+ const gridStrokeWidthMm = legend.grid?.strokeWidthMm ?? DEFAULT_LEGEND_GRID_WIDTH_MM;
105
+ const gridStrokeColor = escapeAttr(legend.grid?.stroke ?? '#000000');
106
+ const headerStrokeWidthMm = legend.header?.strokeWidthMm ?? DEFAULT_LEGEND_HEADER_WIDTH_MM;
107
+ const headerStrokeColor = escapeAttr(legend.header?.stroke ?? '#000000');
122
108
  const anchor = legend.anchor ?? 'top-right';
123
109
  const legendMarginMm = legend.marginMm ?? DEFAULT_LEGEND_OFFSET_MM;
124
110
  // Group rows if groupBy is specified
@@ -133,12 +119,6 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
133
119
  }
134
120
  groups.get(groupKey).push(row);
135
121
  }
136
- // console.log(`[LEGEND GROUPING] Total input rows: ${legendData.length}, Groups created: ${groups.size}`);
137
- // for (const [key, rows] of groups.entries()) {
138
- // if (rows.length > 1) {
139
- // console.log(`[LEGEND GROUPING] Group "${key}": ${rows.length} rows`);
140
- // }
141
- // }
142
122
  // Aggregate each group
143
123
  processedData = Array.from(groups.values()).map((groupRows) => {
144
124
  if (groupRows.length === 0)
@@ -170,15 +150,6 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
170
150
  const num = typeof v === 'number' ? v : parseFloat(String(v));
171
151
  return acc + num;
172
152
  }, 0);
173
- // Debug logging for aggregation
174
- // if (groupRows.length > 1 && legend.groupBy) {
175
- // const groupKey = resolvePath(groupRows[0], legend.groupBy);
176
- // console.log(`[LEGEND AGGREGATION] Group "${groupKey}", Column "${col.property}":`, {
177
- // itemCount: values.length,
178
- // values: values,
179
- // sum: sum
180
- // });
181
- // }
182
153
  // Set the aggregated value in the nested property structure
183
154
  const pathParts = col.property.split('.');
184
155
  let target = aggregated;
@@ -195,28 +166,27 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
195
166
  return aggregated;
196
167
  });
197
168
  }
198
- // Generate colors if assignColors is enabled
169
+ // Generate colors if assignColors is enabled — consumed by viewport fill
170
+ // rules with fill: "legend" (byGid works without groupBy: every row gets
171
+ // its own color).
199
172
  let generatedColors = null;
200
- let colorMap;
173
+ let colors;
201
174
  if (legend.assignColors?.enabled) {
202
175
  const palette = legend.assignColors.palette ?? 'auto';
203
176
  generatedColors = generateLegendColors(processedData.length, palette);
204
- // console.log(`[LEGEND COLORS] assignColors enabled, generated ${generatedColors.length} colors`);
205
- // Build color map based on groupBy property
206
- if (legend.groupBy) {
207
- colorMap = new Map();
208
- for (let i = 0; i < processedData.length; i++) {
209
- const row = processedData[i];
177
+ colors = { groupBy: legend.groupBy || undefined, byGid: new Map(), byValue: new Map() };
178
+ for (let i = 0; i < processedData.length; i++) {
179
+ const row = processedData[i];
180
+ if (legend.groupBy) {
210
181
  const groupValue = String(resolvePath(row, legend.groupBy) ?? '');
211
- if (!colorMap.has(groupValue)) {
212
- colorMap.set(groupValue, generatedColors[i]);
213
- // console.log(`[LEGEND COLORS] Mapped "${groupValue}" -> ${generatedColors[i]}`);
182
+ if (!colors.byValue.has(groupValue)) {
183
+ colors.byValue.set(groupValue, generatedColors[i]);
214
184
  }
215
185
  }
216
- // console.log(`[LEGEND COLORS] Generated ${colorMap.size} colors for groupBy="${legend.groupBy}"`);
217
- }
218
- else {
219
- console.warn(`[LEGEND COLORS] assignColors enabled but no groupBy property set`);
186
+ const gid = getProp(row, 'gid') ?? getProp(row, 'id');
187
+ if (gid !== null && gid !== undefined) {
188
+ colors.byGid.set(String(gid), generatedColors[i]);
189
+ }
220
190
  }
221
191
  }
222
192
  const rowsMapped = processedData.map((row, index) => {
@@ -316,14 +286,14 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
316
286
  lines.push(`<line x1="${originXmm}" y1="${originYmm + headerHeightMm}" x2="${originXmm + tableWidthMm}" y2="${originYmm + headerHeightMm}" stroke="${headerStrokeColor}" stroke-width="${headerStrokeWidthMm}" />`);
317
287
  for (let i = 2; i < columnCount; i++) {
318
288
  const x = originXmm + colXmm[i];
319
- lines.push(`<line x1="${x}" y1="${originYmm}" x2="${x}" y2="${originYmm + tableHeightMm}" stroke="${gridStrokeColor}" stroke-width="${borderStrokeWidthMm}" />`);
289
+ lines.push(`<line x1="${x}" y1="${originYmm}" x2="${x}" y2="${originYmm + tableHeightMm}" stroke="${gridStrokeColor}" stroke-width="${gridStrokeWidthMm}" />`);
320
290
  }
321
291
  for (let r = 0; r <= rowsMapped.length; r++) {
322
292
  // Skip last grid line if summary enabled (we draw a heavier separator line instead)
323
293
  if (r === rowsMapped.length && summaryEnabled)
324
294
  continue;
325
295
  const y = originYmm + headerHeightMm + r * rowHeightMm;
326
- lines.push(`<line x1="${originXmm}" y1="${y}" x2="${originXmm + tableWidthMm}" y2="${y}" stroke="${gridStrokeColor}" stroke-width="${borderStrokeWidthMm}" />`);
296
+ lines.push(`<line x1="${originXmm}" y1="${y}" x2="${originXmm + tableWidthMm}" y2="${y}" stroke="${gridStrokeColor}" stroke-width="${gridStrokeWidthMm}" />`);
327
297
  }
328
298
  const textYOffsetMm = computeLegendTextYOffset(fontSizeMm);
329
299
  legend.columns.slice(0, columnCount - dataColStartIdx).forEach((col, idx) => {
@@ -337,7 +307,7 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
337
307
  : colStart + innerPad;
338
308
  const y = originYmm + headerHeightMm / 2 + textYOffsetMm;
339
309
  const anchorText = col.align === 'center' ? 'middle' : col.align === 'right' ? 'end' : 'start';
340
- texts.push(`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" dominant-baseline="middle" text-anchor="${anchorText}">${col.label}</text>`);
310
+ texts.push(`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" dominant-baseline="middle" text-anchor="${anchorText}">${escapeText(col.label)}</text>`);
341
311
  });
342
312
  const swatchOpacity = typeof fillOpacity === 'number' ? Math.min(Math.max(fillOpacity, 0), 1) : 1;
343
313
  rowsMapped.slice(0, MAX_LEGEND_ROWS).forEach((row, rIndex) => {
@@ -348,7 +318,7 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
348
318
  // row.color can be either a CSS color string (from generated colors) or RGB string
349
319
  const swatchFill = row.color; // Already a CSS color string
350
320
  const opacityAttr = swatchOpacity < 1 ? ` fill-opacity="${swatchOpacity.toFixed(3)}"` : '';
351
- rects.push(`<rect x="${originXmm + colXmm[0]}" y="${yTop}" width="${colWidthsMm[0]}" height="${rowHeightMm}" fill="${swatchFill}"${opacityAttr} stroke="none" />`);
321
+ rects.push(`<rect x="${originXmm + colXmm[0]}" y="${yTop}" width="${colWidthsMm[0]}" height="${rowHeightMm}" fill="${escapeAttr(String(swatchFill))}"${opacityAttr} stroke="none" />`);
352
322
  }
353
323
  else {
354
324
  // Empty swatch when no fills configured - just the border cell
@@ -369,7 +339,7 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
369
339
  : colStart + innerPad;
370
340
  const y = yTop + rowHeightMm / 2 + textYOffsetMm;
371
341
  const anchorText = col.align === 'center' ? 'middle' : col.align === 'right' ? 'end' : 'start';
372
- texts.push(`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" dominant-baseline="middle" text-anchor="${anchorText}">${text}</text>`);
342
+ texts.push(`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" dominant-baseline="middle" text-anchor="${anchorText}">${escapeText(text)}</text>`);
373
343
  });
374
344
  });
375
345
  // Render summary row (if enabled)
@@ -400,7 +370,7 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
400
370
  const innerPad = cellPaddingMm + gridStrokeWidthMm * 0.5;
401
371
  const labelX = contentOriginXmm + innerPad;
402
372
  const labelY = yTop + rowHeightMm / 2 + textYOffsetMm;
403
- texts.push(`<text x="${labelX}" y="${labelY}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" font-weight="bold" dominant-baseline="middle" text-anchor="start">${summaryLabel}</text>`);
373
+ texts.push(`<text x="${labelX}" y="${labelY}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" font-weight="bold" dominant-baseline="middle" text-anchor="start">${escapeText(summaryLabel)}</text>`);
404
374
  // Render summary values for other columns
405
375
  legend.columns.slice(1, columnCount - dataColStartIdx).forEach((col, cIdx) => {
406
376
  const summaryText = summaryValues[cIdx + 1] ?? '';
@@ -415,10 +385,10 @@ export const buildLegendGroup = (legend, styles, page, marginMm, legendData, fil
415
385
  : colStart + innerPad;
416
386
  const y = yTop + rowHeightMm / 2 + textYOffsetMm;
417
387
  const anchorText = col.align === 'center' ? 'middle' : col.align === 'right' ? 'end' : 'start';
418
- texts.push(`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" font-weight="bold" dominant-baseline="middle" text-anchor="${anchorText}">${summaryText}</text>`);
388
+ texts.push(`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" font-weight="bold" dominant-baseline="middle" text-anchor="${anchorText}">${escapeText(summaryText)}</text>`);
419
389
  });
420
390
  }
421
391
  const svg = [`<g id="legend">`, ...rects, ...lines, ...texts, `</g>`].join('');
422
- return { svg, colorMap };
392
+ return { svg, colors };
423
393
  };
424
394
  //# sourceMappingURL=legendRender.js.map