@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
@@ -9,21 +9,124 @@ export const rotateBounds = (width, height, rotationDeg) => {
9
9
  const rotatedHeight = Math.abs(width * sin) + Math.abs(height * cos);
10
10
  return { width: rotatedWidth, height: rotatedHeight };
11
11
  };
12
+ // Absolute move/line sequences only (what our section renderers emit). Anything
13
+ // else — relative commands, curves, arcs — falls back to AABB-based rotation.
14
+ const ABSOLUTE_POLYLINE_D = /^[MLZ\s,0-9eE+\-.]*$/;
15
+ const NUMBER_IN_D = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
16
+ const extentsCache = new WeakMap();
17
+ /**
18
+ * Tight bounds of the model's actual geometry under viewport rotation.
19
+ *
20
+ * Rotating the model's AABB *rectangle* only ever inflates it — for content that
21
+ * sits diagonally in model space (the main reason to set rotationDeg: straighten
22
+ * a building on the sheet), the true rotated bounds SHRINK instead, and fitting
23
+ * against the inflated rectangle picks a scale several preset steps too small.
24
+ * Returns null when tight bounds can't be derived (no rotation, no rawPaths /
25
+ * transform, or path data that isn't plain absolute polylines) — callers then
26
+ * keep the AABB-based rotateBounds behavior.
27
+ */
28
+ export const rotatedContentExtents = (model, rotationDeg) => {
29
+ const norm = ((rotationDeg % 360) + 360) % 360;
30
+ const paths = model.rawPaths;
31
+ const t = model.transform;
32
+ if (!norm || !paths?.length || !t)
33
+ return null;
34
+ // Keyed on the transform origin as well as the rotation. The extents below are computed through
35
+ // t.viewMinX / t.viewMaxZ, so two models sharing a rawPaths array but placed with different
36
+ // origins have different answers — keying on rotation alone handed the second the first's.
37
+ const cacheKey = `${norm}|${t.viewMinX}|${t.viewMaxZ}`;
38
+ let perRotation = extentsCache.get(paths);
39
+ const cached = perRotation?.get(cacheKey);
40
+ if (cached !== undefined)
41
+ return cached;
42
+ if (!perRotation) {
43
+ perRotation = new Map();
44
+ extentsCache.set(paths, perRotation);
45
+ }
46
+ const rad = (norm * Math.PI) / 180;
47
+ const cos = Math.cos(rad);
48
+ const sin = Math.sin(rad);
49
+ let minX = Infinity;
50
+ let minY = Infinity;
51
+ let maxX = -Infinity;
52
+ let maxY = -Infinity;
53
+ for (const p of paths) {
54
+ if (!p.d)
55
+ continue;
56
+ if (!ABSOLUTE_POLYLINE_D.test(p.d)) {
57
+ perRotation.set(cacheKey, null);
58
+ return null;
59
+ }
60
+ const nums = p.d.match(NUMBER_IN_D);
61
+ if (!nums)
62
+ continue;
63
+ for (let i = 0; i + 1 < nums.length; i += 2) {
64
+ // Raw coords are projected y-up space; geometryTransform maps them to
65
+ // view space (y-down, origin at the model's min corner) — mirror that
66
+ // here so extents/center land in the space the render transform uses.
67
+ const vx = Number(nums[i]) - t.viewMinX;
68
+ const vy = t.viewMaxZ - Number(nums[i + 1]);
69
+ const rx = vx * cos - vy * sin;
70
+ const ry = vx * sin + vy * cos;
71
+ if (rx < minX)
72
+ minX = rx;
73
+ if (rx > maxX)
74
+ maxX = rx;
75
+ if (ry < minY)
76
+ minY = ry;
77
+ if (ry > maxY)
78
+ maxY = ry;
79
+ }
80
+ }
81
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || maxX - minX <= 0 || maxY - minY <= 0) {
82
+ perRotation.set(cacheKey, null);
83
+ return null;
84
+ }
85
+ const cx = (minX + maxX) / 2;
86
+ const cy = (minY + maxY) / 2;
87
+ const result = {
88
+ width: maxX - minX,
89
+ height: maxY - minY,
90
+ // Inverse-rotate the rotated-space center: using this as the transform
91
+ // pivot makes the tight content box land centered in the viewport.
92
+ centerViewX: cx * cos + cy * sin,
93
+ centerViewY: -cx * sin + cy * cos
94
+ };
95
+ perRotation.set(cacheKey, result);
96
+ return result;
97
+ };
12
98
  export const pickScale = (viewport, model, preferred, scalePresets) => {
99
+ const rotationDeg = viewport.rotationDeg ?? 0;
100
+ // Callers substitute clip-region dims into widthModel/heightModel; tight
101
+ // rotated bounds of the FULL geometry would be wrong there, so only use
102
+ // them when the passed dims still match the model's own view bounds.
103
+ const clipAdjusted = !!model.transform &&
104
+ (Math.abs(model.widthModel - model.transform.viewWidth) > 1e-9 ||
105
+ Math.abs(model.heightModel - model.transform.viewHeight) > 1e-9);
106
+ const tight = clipAdjusted ? null : rotatedContentExtents(model, rotationDeg);
107
+ // 'fit': exact fill of the viewport — the denominator is whatever ratio
108
+ // makes the (rotated) model touch the viewport bounds; no rounding to a
109
+ // preferred-scale list, so the scale text can be odd (e.g. 1:73.4).
110
+ if (viewport.scaleMode === 'fit') {
111
+ const rotatedModel = tight ?? rotateBounds(model.widthModel, model.heightModel, rotationDeg);
112
+ const mmPerModel = Math.min(viewport.widthMm / (rotatedModel.width || 1), viewport.heightMm / (rotatedModel.height || 1));
113
+ if (Number.isFinite(mmPerModel) && mmPerModel > 0) {
114
+ return { denom: Math.round((1000 / mmPerModel) * 10) / 10, mmPerModelUnit: mmPerModel, fits: true };
115
+ }
116
+ }
13
117
  const scalePresetName = viewport.scalePreset;
14
118
  const scalePreset = scalePresetName && scalePresets[scalePresetName] ? scalePresets[scalePresetName] : null;
15
119
  const denoms = viewport.scaleMode === 'fixed' && viewport.fixedScaleDenom
16
120
  ? [viewport.fixedScaleDenom]
17
121
  : uniquePositiveDenoms(viewport.preferredScales ?? scalePreset?.preferredScales ?? preferred);
18
- const rotationDeg = viewport.rotationDeg ?? 0;
19
122
  let selectedDenom = denoms[denoms.length - 1];
20
123
  let selectedMmPerModel = 1000 / selectedDenom;
21
124
  let fits = false;
22
125
  for (const denom of denoms) {
23
126
  const mmPerModel = 1000 / denom;
24
- const widthMm = model.widthModel * mmPerModel;
25
- const heightMm = model.heightModel * mmPerModel;
26
- const rotated = rotateBounds(widthMm, heightMm, rotationDeg);
127
+ const rotated = tight
128
+ ? { width: tight.width * mmPerModel, height: tight.height * mmPerModel }
129
+ : rotateBounds(model.widthModel * mmPerModel, model.heightModel * mmPerModel, rotationDeg);
27
130
  if (rotated.width <= viewport.widthMm && rotated.height <= viewport.heightMm) {
28
131
  selectedDenom = denom;
29
132
  selectedMmPerModel = mmPerModel;
@@ -1 +1 @@
1
- {"version":3,"file":"viewportUtils.js","sourceRoot":"/","sources":["layout/viewportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAG/C,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,MAAc,EAAE,WAAmB,EAAqC,EAAE;IAClH,IAAI,CAAC,WAAW;QAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAC3C,MAAM,GAAG,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IACrE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CACrB,QAAyB,EACzB,KAAmB,EACnB,SAAmB,EACnB,YAA4D,EACvC,EAAE;IACvB,MAAM,eAAe,GAAG,QAAQ,CAAC,WAAW,CAAC;IAC7C,MAAM,WAAW,GAAG,eAAe,IAAI,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5G,MAAM,MAAM,GACR,QAAQ,CAAC,SAAS,KAAK,OAAO,IAAI,QAAQ,CAAC,eAAe;QACtD,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;QAC5B,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,eAAe,IAAI,WAAW,EAAE,eAAe,IAAI,SAAS,CAAC,CAAC;IAEtG,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC;IAC9C,IAAI,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9C,IAAI,kBAAkB,GAAG,IAAI,GAAG,aAAa,CAAC;IAC9C,IAAI,IAAI,GAAG,KAAK,CAAC;IAEjB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,GAAG,KAAK,CAAC;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;QAC9C,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,GAAG,UAAU,CAAC;QAChD,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC7D,IAAI,OAAO,CAAC,KAAK,IAAI,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC3E,aAAa,GAAG,KAAK,CAAC;YACtB,kBAAkB,GAAG,UAAU,CAAC;YAChC,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM;QACV,CAAC;IACL,CAAC;IAED,OAAO;QACH,KAAK,EAAE,aAAa;QACpB,cAAc,EAAE,kBAAkB;QAClC,IAAI;KACP,CAAC;AACN,CAAC,CAAC","sourcesContent":["import { uniquePositiveDenoms } from './utils';\nimport { ComputedViewportScale, SvgModelView, ViewportElement } from './types';\n\nexport const rotateBounds = (width: number, height: number, rotationDeg: number): { width: number; height: number } => {\n if (!rotationDeg) return { width, height };\n const rad = (rotationDeg * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const rotatedWidth = Math.abs(width * cos) + Math.abs(height * sin);\n const rotatedHeight = Math.abs(width * sin) + Math.abs(height * cos);\n return { width: rotatedWidth, height: rotatedHeight };\n};\n\nexport const pickScale = (\n viewport: ViewportElement,\n model: SvgModelView,\n preferred: number[],\n scalePresets: Record<string, { preferredScales?: number[] }>\n): ComputedViewportScale => {\n const scalePresetName = viewport.scalePreset;\n const scalePreset = scalePresetName && scalePresets[scalePresetName] ? scalePresets[scalePresetName] : null;\n const denoms =\n viewport.scaleMode === 'fixed' && viewport.fixedScaleDenom\n ? [viewport.fixedScaleDenom]\n : uniquePositiveDenoms(viewport.preferredScales ?? scalePreset?.preferredScales ?? preferred);\n\n const rotationDeg = viewport.rotationDeg ?? 0;\n let selectedDenom = denoms[denoms.length - 1];\n let selectedMmPerModel = 1000 / selectedDenom;\n let fits = false;\n\n for (const denom of denoms) {\n const mmPerModel = 1000 / denom;\n const widthMm = model.widthModel * mmPerModel;\n const heightMm = model.heightModel * mmPerModel;\n const rotated = rotateBounds(widthMm, heightMm, rotationDeg);\n if (rotated.width <= viewport.widthMm && rotated.height <= viewport.heightMm) {\n selectedDenom = denom;\n selectedMmPerModel = mmPerModel;\n fits = true;\n break;\n }\n }\n\n return {\n denom: selectedDenom,\n mmPerModelUnit: selectedMmPerModel,\n fits\n };\n};\n"]}
1
+ {"version":3,"file":"viewportUtils.js","sourceRoot":"/","sources":["layout/viewportUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import { uniquePositiveDenoms } from './utils';\nimport { ComputedViewportScale, SvgModelView, ViewportElement } from './types';\n\nexport const rotateBounds = (width: number, height: number, rotationDeg: number): { width: number; height: number } => {\n if (!rotationDeg) return { width, height };\n const rad = (rotationDeg * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const rotatedWidth = Math.abs(width * cos) + Math.abs(height * sin);\n const rotatedHeight = Math.abs(width * sin) + Math.abs(height * cos);\n return { width: rotatedWidth, height: rotatedHeight };\n};\n\nexport interface RotatedContentExtents {\n /** Tight extents of the rotated content, model units (rotated/sheet axes). */\n width: number;\n height: number;\n /** Tight-bounds center mapped back to view space — the rotation pivot that centers the content. */\n centerViewX: number;\n centerViewY: number;\n}\n\n// Absolute move/line sequences only (what our section renderers emit). Anything\n// else — relative commands, curves, arcs — falls back to AABB-based rotation.\nconst ABSOLUTE_POLYLINE_D = /^[MLZ\\s,0-9eE+\\-.]*$/;\nconst NUMBER_IN_D = /-?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?/g;\n\nconst extentsCache = new WeakMap<object, Map<string, RotatedContentExtents | null>>();\n\n/**\n * Tight bounds of the model's actual geometry under viewport rotation.\n *\n * Rotating the model's AABB *rectangle* only ever inflates it — for content that\n * sits diagonally in model space (the main reason to set rotationDeg: straighten\n * a building on the sheet), the true rotated bounds SHRINK instead, and fitting\n * against the inflated rectangle picks a scale several preset steps too small.\n * Returns null when tight bounds can't be derived (no rotation, no rawPaths /\n * transform, or path data that isn't plain absolute polylines) — callers then\n * keep the AABB-based rotateBounds behavior.\n */\nexport const rotatedContentExtents = (model: SvgModelView, rotationDeg: number): RotatedContentExtents | null => {\n const norm = ((rotationDeg % 360) + 360) % 360;\n const paths = model.rawPaths;\n const t = model.transform;\n if (!norm || !paths?.length || !t) return null;\n // Keyed on the transform origin as well as the rotation. The extents below are computed through\n // t.viewMinX / t.viewMaxZ, so two models sharing a rawPaths array but placed with different\n // origins have different answers — keying on rotation alone handed the second the first's.\n const cacheKey = `${norm}|${t.viewMinX}|${t.viewMaxZ}`;\n let perRotation = extentsCache.get(paths);\n const cached = perRotation?.get(cacheKey);\n if (cached !== undefined) return cached;\n if (!perRotation) {\n perRotation = new Map();\n extentsCache.set(paths, perRotation);\n }\n\n const rad = (norm * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const p of paths) {\n if (!p.d) continue;\n if (!ABSOLUTE_POLYLINE_D.test(p.d)) {\n perRotation.set(cacheKey, null);\n return null;\n }\n const nums = p.d.match(NUMBER_IN_D);\n if (!nums) continue;\n for (let i = 0; i + 1 < nums.length; i += 2) {\n // Raw coords are projected y-up space; geometryTransform maps them to\n // view space (y-down, origin at the model's min corner) — mirror that\n // here so extents/center land in the space the render transform uses.\n const vx = Number(nums[i]) - t.viewMinX;\n const vy = t.viewMaxZ - Number(nums[i + 1]);\n const rx = vx * cos - vy * sin;\n const ry = vx * sin + vy * cos;\n if (rx < minX) minX = rx;\n if (rx > maxX) maxX = rx;\n if (ry < minY) minY = ry;\n if (ry > maxY) maxY = ry;\n }\n }\n if (!Number.isFinite(minX) || !Number.isFinite(minY) || maxX - minX <= 0 || maxY - minY <= 0) {\n perRotation.set(cacheKey, null);\n return null;\n }\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n const result: RotatedContentExtents = {\n width: maxX - minX,\n height: maxY - minY,\n // Inverse-rotate the rotated-space center: using this as the transform\n // pivot makes the tight content box land centered in the viewport.\n centerViewX: cx * cos + cy * sin,\n centerViewY: -cx * sin + cy * cos\n };\n perRotation.set(cacheKey, result);\n return result;\n};\n\nexport const pickScale = (\n viewport: ViewportElement,\n model: SvgModelView,\n preferred: number[],\n scalePresets: Record<string, { preferredScales?: number[] }>\n): ComputedViewportScale => {\n const rotationDeg = viewport.rotationDeg ?? 0;\n // Callers substitute clip-region dims into widthModel/heightModel; tight\n // rotated bounds of the FULL geometry would be wrong there, so only use\n // them when the passed dims still match the model's own view bounds.\n const clipAdjusted =\n !!model.transform &&\n (Math.abs(model.widthModel - model.transform.viewWidth) > 1e-9 ||\n Math.abs(model.heightModel - model.transform.viewHeight) > 1e-9);\n const tight = clipAdjusted ? null : rotatedContentExtents(model, rotationDeg);\n\n // 'fit': exact fill of the viewport — the denominator is whatever ratio\n // makes the (rotated) model touch the viewport bounds; no rounding to a\n // preferred-scale list, so the scale text can be odd (e.g. 1:73.4).\n if (viewport.scaleMode === 'fit') {\n const rotatedModel = tight ?? rotateBounds(model.widthModel, model.heightModel, rotationDeg);\n const mmPerModel = Math.min(\n viewport.widthMm / (rotatedModel.width || 1),\n viewport.heightMm / (rotatedModel.height || 1)\n );\n if (Number.isFinite(mmPerModel) && mmPerModel > 0) {\n return { denom: Math.round((1000 / mmPerModel) * 10) / 10, mmPerModelUnit: mmPerModel, fits: true };\n }\n }\n\n const scalePresetName = viewport.scalePreset;\n const scalePreset = scalePresetName && scalePresets[scalePresetName] ? scalePresets[scalePresetName] : null;\n const denoms =\n viewport.scaleMode === 'fixed' && viewport.fixedScaleDenom\n ? [viewport.fixedScaleDenom]\n : uniquePositiveDenoms(viewport.preferredScales ?? scalePreset?.preferredScales ?? preferred);\n let selectedDenom = denoms[denoms.length - 1];\n let selectedMmPerModel = 1000 / selectedDenom;\n let fits = false;\n\n for (const denom of denoms) {\n const mmPerModel = 1000 / denom;\n const rotated = tight\n ? { width: tight.width * mmPerModel, height: tight.height * mmPerModel }\n : rotateBounds(model.widthModel * mmPerModel, model.heightModel * mmPerModel, rotationDeg);\n if (rotated.width <= viewport.widthMm && rotated.height <= viewport.heightMm) {\n selectedDenom = denom;\n selectedMmPerModel = mmPerModel;\n fits = true;\n break;\n }\n }\n\n return {\n denom: selectedDenom,\n mmPerModelUnit: selectedMmPerModel,\n fits\n };\n};\n"]}
@@ -6,7 +6,8 @@
6
6
  export interface PrintToPdfOptions {
7
7
  svgMarkup: string;
8
8
  fileName?: string;
9
- pageFormat?: 'a0' | 'a1' | 'a2' | 'a3' | 'a4';
9
+ /** ISO name or an arbitrary [width, height] in mm. */
10
+ pageFormat?: 'a0' | 'a1' | 'a2' | 'a3' | 'a4' | [number, number];
10
11
  orientation?: 'portrait' | 'landscape';
11
12
  marginMm?: number;
12
13
  }
@@ -15,6 +16,11 @@ export interface PrintToPdfOptions {
15
16
  * Requires jspdf and svg2pdf.js as peer dependencies.
16
17
  */
17
18
  export declare function printToPdf(options: PrintToPdfOptions): Promise<Blob>;
19
+ /**
20
+ * External image references in an SVG, for error reporting. data: URIs are
21
+ * summarized rather than dumped (they can be hundreds of KB).
22
+ */
23
+ export declare function collectImageHrefs(svgMarkup: string): string[];
18
24
  /**
19
25
  * Export an SVG string directly as a downloadable SVG file.
20
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"printToPdf.d.ts","sourceRoot":"/","sources":["printToPdf.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9C,WAAW,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C1E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQxE;AAGD,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK1F;AAED,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAU3E"}
1
+ {"version":3,"file":"printToPdf.d.ts","sourceRoot":"/","sources":["printToPdf.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjE,WAAW,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+D1E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAY7D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQxE;AAGD,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC1F;AAED,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAU3E"}
@@ -8,17 +8,17 @@
8
8
  * Requires jspdf and svg2pdf.js as peer dependencies.
9
9
  */
10
10
  export async function printToPdf(options) {
11
- // Dynamic imports for optional peer dependencies (installed by consumer, not this package)
11
+ // Dynamic imports for optional peer dependencies (installed by consumer,
12
+ // not this package). Typechecked against the workspace-hoisted installs
13
+ // (template-workbench depends on both), resolved at runtime for consumers.
12
14
  let jspdfModule;
13
15
  try {
14
- // @ts-expect-error — optional peer dependency resolved at runtime
15
16
  jspdfModule = await import('jspdf');
16
17
  }
17
18
  catch {
18
19
  throw new Error('jspdf is required for PDF export. Install it: bun add jspdf');
19
20
  }
20
21
  try {
21
- // @ts-expect-error — optional peer dependency resolved at runtime
22
22
  await import('svg2pdf.js');
23
23
  }
24
24
  catch {
@@ -28,24 +28,59 @@ export async function printToPdf(options) {
28
28
  const format = options.pageFormat ?? 'a3';
29
29
  const orientation = options.orientation ?? 'landscape';
30
30
  const margin = options.marginMm ?? 10;
31
- const doc = new jsPDF({ format, orientation, unit: 'mm' });
31
+ // compress: jsPDF leaves content streams uncompressed by default — a full
32
+ // sheet of vector linework is megabytes of plain text. Flate matches what
33
+ // browser print-to-PDF produces (~5-10x smaller for typical sheets).
34
+ const doc = new jsPDF({ format, orientation, unit: 'mm', compress: true });
32
35
  const pageWidth = doc.internal.pageSize.getWidth();
33
36
  const pageHeight = doc.internal.pageSize.getHeight();
34
37
  const parser = new DOMParser();
35
38
  const svgDoc = parser.parseFromString(options.svgMarkup, 'image/svg+xml');
36
39
  const svgElement = svgDoc.documentElement;
37
- await doc.svg(svgElement, {
38
- x: margin,
39
- y: margin,
40
- width: pageWidth - margin * 2,
41
- height: pageHeight - margin * 2
42
- });
40
+ try {
41
+ await doc.svg(svgElement, {
42
+ x: margin,
43
+ y: margin,
44
+ width: pageWidth - margin * 2,
45
+ height: pageHeight - margin * 2
46
+ });
47
+ }
48
+ catch (err) {
49
+ // svg2pdf loads every <image> href over XHR and rejects with the raw
50
+ // ProgressEvent on network/CORS failure — which stringifies to the
51
+ // useless "[object ProgressEvent]". Translate, and name the linked
52
+ // images so the failing one is findable. Remote images render fine in
53
+ // the browser preview but fail export unless the host sends CORS
54
+ // headers; data: URIs and inline resource `content` always work.
55
+ if (err instanceof Error)
56
+ throw err;
57
+ const hrefs = collectImageHrefs(options.svgMarkup);
58
+ throw new Error('PDF export failed while loading a linked <image> resource (network or CORS). ' +
59
+ 'Template images must be data: URIs, inline SVG resource `content`, or served ' +
60
+ 'with CORS headers — the on-screen preview does not enforce this, PDF export does.' +
61
+ (hrefs.length ? ` Linked images: ${hrefs.join(', ')}` : ''));
62
+ }
43
63
  const blob = doc.output('blob');
44
64
  if (options.fileName) {
45
65
  doc.save(options.fileName);
46
66
  }
47
67
  return blob;
48
68
  }
69
+ /**
70
+ * External image references in an SVG, for error reporting. data: URIs are
71
+ * summarized rather than dumped (they can be hundreds of KB).
72
+ */
73
+ export function collectImageHrefs(svgMarkup) {
74
+ const hrefs = [];
75
+ // Both quote styles: SVG allows either, and this feeds an error message — matching only double
76
+ // quotes meant the "which image broke the export?" hint could come back empty on valid markup,
77
+ // which is worse than not having it.
78
+ for (const m of svgMarkup.matchAll(/<image\b[^>]*?(?:xlink:)?href\s*=\s*("([^"]*)"|'([^']*)')/g)) {
79
+ const href = m[2] ?? m[3] ?? '';
80
+ hrefs.push(href.startsWith('data:') ? `${href.slice(0, href.indexOf(',') + 1 || 32)}… (${href.length} chars)` : href);
81
+ }
82
+ return hrefs;
83
+ }
49
84
  /**
50
85
  * Export an SVG string directly as a downloadable SVG file.
51
86
  */
@@ -59,10 +94,42 @@ export function printToSvgFile(svgMarkup, fileName) {
59
94
  URL.revokeObjectURL(url);
60
95
  }
61
96
  export async function exportSectionToPdf(options) {
62
- await printToPdf({
63
- svgMarkup: options.svgMarkup,
64
- fileName: options.fileName
65
- });
97
+ const size = options.pageSizeMm;
98
+ // Claim the tab synchronously, while still inside the click that started this. Opening it after
99
+ // the await puts the call outside the user gesture, which is precisely what popup blockers stop
100
+ // — and rendering a sheet takes long enough that they always would.
101
+ const tab = options.openInNewTab ? window.open('', '_blank') : null;
102
+ let blob;
103
+ try {
104
+ blob = await printToPdf({
105
+ svgMarkup: options.svgMarkup,
106
+ // When opening in a tab, skip jsPDF's own save() download.
107
+ fileName: options.openInNewTab ? undefined : options.fileName,
108
+ pageFormat: size ? [size.width, size.height] : undefined,
109
+ orientation: size ? (size.width >= size.height ? 'landscape' : 'portrait') : undefined,
110
+ marginMm: size ? 0 : undefined
111
+ });
112
+ }
113
+ catch (err) {
114
+ // Claiming the tab up front means a failed export would otherwise leave an empty one sitting
115
+ // there — and a broken linked <image> makes this throw, which is not a rare path.
116
+ tab?.close();
117
+ throw err;
118
+ }
119
+ if (options.openInNewTab) {
120
+ const url = URL.createObjectURL(blob);
121
+ // A blocked window.open returns null; fall back to the download so the export still lands
122
+ // somewhere the user can find rather than appearing to do nothing.
123
+ if (tab)
124
+ tab.location.href = url;
125
+ else {
126
+ const link = document.createElement('a');
127
+ link.href = url;
128
+ link.download = options.fileName;
129
+ link.click();
130
+ }
131
+ setTimeout(() => URL.revokeObjectURL(url), 60000);
132
+ }
66
133
  }
67
134
  export function exportSectionToSvg(options) {
68
135
  if (options.openInNewTab) {
@@ -1 +1 @@
1
- {"version":3,"file":"printToPdf.js","sourceRoot":"/","sources":["printToPdf.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PDF export via jsPDF + svg2pdf.js\n *\n * These are optional peer dependencies. Functions will throw at runtime if not installed.\n */\n\nexport interface PrintToPdfOptions {\n svgMarkup: string;\n fileName?: string;\n pageFormat?: 'a0' | 'a1' | 'a2' | 'a3' | 'a4';\n orientation?: 'portrait' | 'landscape';\n marginMm?: number;\n}\n\n/**\n * Export an SVG string to a PDF file.\n * Requires jspdf and svg2pdf.js as peer dependencies.\n */\nexport async function printToPdf(options: PrintToPdfOptions): Promise<Blob> {\n // Dynamic imports for optional peer dependencies (installed by consumer, not this package)\n let jspdfModule: any;\n try {\n // @ts-expect-error — optional peer dependency resolved at runtime\n jspdfModule = await import('jspdf');\n } catch {\n throw new Error('jspdf is required for PDF export. Install it: bun add jspdf');\n }\n try {\n // @ts-expect-error — optional peer dependency resolved at runtime\n await import('svg2pdf.js');\n } catch {\n throw new Error('svg2pdf.js is required for PDF export. Install it: bun add svg2pdf.js');\n }\n\n const { jsPDF } = jspdfModule;\n const format = options.pageFormat ?? 'a3';\n const orientation = options.orientation ?? 'landscape';\n const margin = options.marginMm ?? 10;\n\n const doc = new jsPDF({ format, orientation, unit: 'mm' });\n const pageWidth = doc.internal.pageSize.getWidth();\n const pageHeight = doc.internal.pageSize.getHeight();\n\n const parser = new DOMParser();\n const svgDoc = parser.parseFromString(options.svgMarkup, 'image/svg+xml');\n const svgElement = svgDoc.documentElement;\n\n await doc.svg(svgElement, {\n x: margin,\n y: margin,\n width: pageWidth - margin * 2,\n height: pageHeight - margin * 2\n });\n\n const blob = doc.output('blob');\n\n if (options.fileName) {\n doc.save(options.fileName);\n }\n\n return blob;\n}\n\n/**\n * Export an SVG string directly as a downloadable SVG file.\n */\nexport function printToSvgFile(svgMarkup: string, fileName: string): void {\n const blob = new Blob([svgMarkup], { type: 'image/svg+xml' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = fileName;\n link.click();\n URL.revokeObjectURL(url);\n}\n\n// Compatibility wrappers for print-layout module\nexport interface ExportSectionToPdfOptions {\n svgMarkup: string;\n fileName: string;\n pageSizeMm?: { width: number; height: number };\n openInNewTab?: boolean;\n}\n\nexport async function exportSectionToPdf(options: ExportSectionToPdfOptions): Promise<void> {\n await printToPdf({\n svgMarkup: options.svgMarkup,\n fileName: options.fileName\n });\n}\n\nexport interface ExportSectionToSvgOptions {\n svgMarkup: string;\n fileName: string;\n openInNewTab?: boolean;\n}\n\nexport function exportSectionToSvg(options: ExportSectionToSvgOptions): void {\n if (options.openInNewTab) {\n const blob = new Blob([options.svgMarkup], { type: 'image/svg+xml' });\n const url = URL.createObjectURL(blob);\n window.open(url, '_blank');\n // Revoke after a delay to allow the new tab to load the blob\n setTimeout(() => URL.revokeObjectURL(url), 60_000);\n } else {\n printToSvgFile(options.svgMarkup, options.fileName);\n }\n}\n"]}
1
+ {"version":3,"file":"printToPdf.js","sourceRoot":"/","sources":["printToPdf.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PDF export via jsPDF + svg2pdf.js\n *\n * These are optional peer dependencies. Functions will throw at runtime if not installed.\n */\n\nexport interface PrintToPdfOptions {\n svgMarkup: string;\n fileName?: string;\n /** ISO name or an arbitrary [width, height] in mm. */\n pageFormat?: 'a0' | 'a1' | 'a2' | 'a3' | 'a4' | [number, number];\n orientation?: 'portrait' | 'landscape';\n marginMm?: number;\n}\n\n/**\n * Export an SVG string to a PDF file.\n * Requires jspdf and svg2pdf.js as peer dependencies.\n */\nexport async function printToPdf(options: PrintToPdfOptions): Promise<Blob> {\n // Dynamic imports for optional peer dependencies (installed by consumer,\n // not this package). Typechecked against the workspace-hoisted installs\n // (template-workbench depends on both), resolved at runtime for consumers.\n let jspdfModule: any;\n try {\n jspdfModule = await import('jspdf');\n } catch {\n throw new Error('jspdf is required for PDF export. Install it: bun add jspdf');\n }\n try {\n await import('svg2pdf.js');\n } catch {\n throw new Error('svg2pdf.js is required for PDF export. Install it: bun add svg2pdf.js');\n }\n\n const { jsPDF } = jspdfModule;\n const format = options.pageFormat ?? 'a3';\n const orientation = options.orientation ?? 'landscape';\n const margin = options.marginMm ?? 10;\n\n // compress: jsPDF leaves content streams uncompressed by default — a full\n // sheet of vector linework is megabytes of plain text. Flate matches what\n // browser print-to-PDF produces (~5-10x smaller for typical sheets).\n const doc = new jsPDF({ format, orientation, unit: 'mm', compress: true });\n const pageWidth = doc.internal.pageSize.getWidth();\n const pageHeight = doc.internal.pageSize.getHeight();\n\n const parser = new DOMParser();\n const svgDoc = parser.parseFromString(options.svgMarkup, 'image/svg+xml');\n const svgElement = svgDoc.documentElement;\n\n try {\n await doc.svg(svgElement, {\n x: margin,\n y: margin,\n width: pageWidth - margin * 2,\n height: pageHeight - margin * 2\n });\n } catch (err) {\n // svg2pdf loads every <image> href over XHR and rejects with the raw\n // ProgressEvent on network/CORS failure — which stringifies to the\n // useless \"[object ProgressEvent]\". Translate, and name the linked\n // images so the failing one is findable. Remote images render fine in\n // the browser preview but fail export unless the host sends CORS\n // headers; data: URIs and inline resource `content` always work.\n if (err instanceof Error) throw err;\n const hrefs = collectImageHrefs(options.svgMarkup);\n throw new Error(\n 'PDF export failed while loading a linked <image> resource (network or CORS). ' +\n 'Template images must be data: URIs, inline SVG resource `content`, or served ' +\n 'with CORS headers — the on-screen preview does not enforce this, PDF export does.' +\n (hrefs.length ? ` Linked images: ${hrefs.join(', ')}` : '')\n );\n }\n\n const blob = doc.output('blob');\n\n if (options.fileName) {\n doc.save(options.fileName);\n }\n\n return blob;\n}\n\n/**\n * External image references in an SVG, for error reporting. data: URIs are\n * summarized rather than dumped (they can be hundreds of KB).\n */\nexport function collectImageHrefs(svgMarkup: string): string[] {\n const hrefs: string[] = [];\n // Both quote styles: SVG allows either, and this feeds an error message — matching only double\n // quotes meant the \"which image broke the export?\" hint could come back empty on valid markup,\n // which is worse than not having it.\n for (const m of svgMarkup.matchAll(/<image\\b[^>]*?(?:xlink:)?href\\s*=\\s*(\"([^\"]*)\"|'([^']*)')/g)) {\n const href = m[2] ?? m[3] ?? '';\n hrefs.push(\n href.startsWith('data:') ? `${href.slice(0, href.indexOf(',') + 1 || 32)}… (${href.length} chars)` : href\n );\n }\n return hrefs;\n}\n\n/**\n * Export an SVG string directly as a downloadable SVG file.\n */\nexport function printToSvgFile(svgMarkup: string, fileName: string): void {\n const blob = new Blob([svgMarkup], { type: 'image/svg+xml' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = fileName;\n link.click();\n URL.revokeObjectURL(url);\n}\n\n// Compatibility wrappers for print-layout module\nexport interface ExportSectionToPdfOptions {\n svgMarkup: string;\n fileName: string;\n pageSizeMm?: { width: number; height: number };\n openInNewTab?: boolean;\n}\n\nexport async function exportSectionToPdf(options: ExportSectionToPdfOptions): Promise<void> {\n const size = options.pageSizeMm;\n // Claim the tab synchronously, while still inside the click that started this. Opening it after\n // the await puts the call outside the user gesture, which is precisely what popup blockers stop\n // — and rendering a sheet takes long enough that they always would.\n const tab = options.openInNewTab ? window.open('', '_blank') : null;\n let blob: Blob;\n try {\n blob = await printToPdf({\n svgMarkup: options.svgMarkup,\n // When opening in a tab, skip jsPDF's own save() download.\n fileName: options.openInNewTab ? undefined : options.fileName,\n pageFormat: size ? [size.width, size.height] : undefined,\n orientation: size ? (size.width >= size.height ? 'landscape' : 'portrait') : undefined,\n marginMm: size ? 0 : undefined\n });\n } catch (err) {\n // Claiming the tab up front means a failed export would otherwise leave an empty one sitting\n // there — and a broken linked <image> makes this throw, which is not a rare path.\n tab?.close();\n throw err;\n }\n if (options.openInNewTab) {\n const url = URL.createObjectURL(blob);\n // A blocked window.open returns null; fall back to the download so the export still lands\n // somewhere the user can find rather than appearing to do nothing.\n if (tab) tab.location.href = url;\n else {\n const link = document.createElement('a');\n link.href = url;\n link.download = options.fileName;\n link.click();\n }\n setTimeout(() => URL.revokeObjectURL(url), 60_000);\n }\n}\n\nexport interface ExportSectionToSvgOptions {\n svgMarkup: string;\n fileName: string;\n openInNewTab?: boolean;\n}\n\nexport function exportSectionToSvg(options: ExportSectionToSvgOptions): void {\n if (options.openInNewTab) {\n const blob = new Blob([options.svgMarkup], { type: 'image/svg+xml' });\n const url = URL.createObjectURL(blob);\n window.open(url, '_blank');\n // Revoke after a delay to allow the new tab to load the blob\n setTimeout(() => URL.revokeObjectURL(url), 60_000);\n } else {\n printToSvgFile(options.svgMarkup, options.fileName);\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"printToSvg.d.ts","sourceRoot":"/","sources":["printToSvg.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAQ,MAAM,sBAAsB,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAkC,MAAM,SAAS,CAAC;AAMjF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,mBAAmB,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CA6EjG"}
1
+ {"version":3,"file":"printToSvg.d.ts","sourceRoot":"/","sources":["printToSvg.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAQ,MAAM,sBAAsB,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAkC,MAAM,SAAS,CAAC;AAMjF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,mBAAmB,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAgFjG"}
@@ -29,9 +29,10 @@ export function printToSvg(geometry, options = {}) {
29
29
  const proj = {
30
30
  hiddenLines: projectPolylines(geometry.hiddenLines, options.planeBasis),
31
31
  outlineHidden: projectPolylines(geometry.outlineHidden, options.planeBasis),
32
+ silhouettesHidden: projectPolylines(geometry.silhouettesHidden, options.planeBasis),
32
33
  visibleLines: projectPolylines(geometry.visibleLines, options.planeBasis),
33
34
  outlineVisible: projectPolylines(geometry.outlineVisible, options.planeBasis),
34
- silhouettes: projectPolylines(geometry.silhouettes, options.planeBasis),
35
+ silhouettesVisible: projectPolylines(geometry.silhouettesVisible, options.planeBasis),
35
36
  featureEdges: projectPolylines(geometry.featureEdges, options.planeBasis),
36
37
  cutLines: projectPolylines(geometry.cutLines, options.planeBasis),
37
38
  stairArrows: projectPolylines(geometry.stairArrows, options.planeBasis)
@@ -49,12 +50,14 @@ export function printToSvg(geometry, options = {}) {
49
50
  parts.push(`<rect x="${minX}" y="${minY}" width="${width}" height="${height}" fill="${options.backgroundColor}"/>`);
50
51
  }
51
52
  parts.push('<g fill="none" stroke-linecap="round" stroke-linejoin="round">');
53
+ // Hidden categories paint first so visible linework always draws over them.
52
54
  const categories = [
53
55
  ['hiddenLines', 'hiddenLines'],
54
56
  ['outlineHidden', 'outlineHidden'],
57
+ ['silhouettesHidden', 'silhouettesHidden'],
55
58
  ['visibleLines', 'visibleLines'],
56
59
  ['outlineVisible', 'outlineVisible'],
57
- ['silhouettes', 'silhouettes'],
60
+ ['silhouettesVisible', 'silhouettesVisible'],
58
61
  ['featureEdges', 'featureEdges'],
59
62
  ['cutLines', 'cutLines'],
60
63
  ['stairArrows', 'stairArrows']
@@ -158,7 +161,8 @@ function mergeStyles(userStyles) {
158
161
  cutLines: { ...DEFAULT_SVG_STYLES.cutLines, ...userStyles?.cutLines },
159
162
  visibleLines: { ...DEFAULT_SVG_STYLES.visibleLines, ...userStyles?.visibleLines },
160
163
  hiddenLines: { ...DEFAULT_SVG_STYLES.hiddenLines, ...userStyles?.hiddenLines },
161
- silhouettes: { ...DEFAULT_SVG_STYLES.silhouettes, ...userStyles?.silhouettes },
164
+ silhouettesVisible: { ...DEFAULT_SVG_STYLES.silhouettesVisible, ...userStyles?.silhouettesVisible },
165
+ silhouettesHidden: { ...DEFAULT_SVG_STYLES.silhouettesHidden, ...userStyles?.silhouettesHidden },
162
166
  featureEdges: { ...DEFAULT_SVG_STYLES.featureEdges, ...userStyles?.featureEdges },
163
167
  stairArrows: { ...DEFAULT_SVG_STYLES.stairArrows, ...userStyles?.stairArrows },
164
168
  outlineVisible: { ...DEFAULT_SVG_STYLES.outlineVisible, ...userStyles?.outlineVisible },
@@ -1 +1 @@
1
- {"version":3,"file":"printToSvg.js","sourceRoot":"/","sources":["printToSvg.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SlabSectionGeometry, Vec3 } from '@twinfinity/geometry';\nimport type { PrintToSvgOptions, SvgRenderStyles, SvgPlaneBasis } from './types';\nimport { DEFAULT_SVG_STYLES } from './types';\n\ntype Pt2 = { x: number; y: number };\ntype Bounds = { minX: number; maxX: number; minY: number; maxY: number };\n\n/**\n * Render SlabSectionGeometry to an SVG string.\n *\n * @param geometry The computed slab section geometry\n * @param options Rendering options (viewBox, styles, planeBasis, etc.)\n * @returns Complete SVG string\n */\nexport function printToSvg(geometry: SlabSectionGeometry, options: PrintToSvgOptions = {}): string {\n if (!geometry) throw new Error('geometry is required');\n\n if (\n options.boundsPadding !== undefined &&\n (typeof options.boundsPadding !== 'number' || options.boundsPadding < 0)\n ) {\n throw new Error('boundsPadding must be a non-negative number');\n }\n\n if (options.viewBox) {\n if (!Array.isArray(options.viewBox) || options.viewBox.length !== 4) {\n throw new Error('viewBox must be an array of 4 numbers [minX, minY, width, height]');\n }\n for (let i = 0; i < 4; i++) {\n if (typeof options.viewBox[i] !== 'number' || !Number.isFinite(options.viewBox[i])) {\n throw new Error(`viewBox[${i}] must be a finite number`);\n }\n }\n }\n\n const styles = mergeStyles(options.styles);\n const includeXml = options.includeXmlDeclaration ?? true;\n\n // Project all polylines to 2D once\n const proj = {\n hiddenLines: projectPolylines(geometry.hiddenLines, options.planeBasis),\n outlineHidden: projectPolylines(geometry.outlineHidden, options.planeBasis),\n visibleLines: projectPolylines(geometry.visibleLines, options.planeBasis),\n outlineVisible: projectPolylines(geometry.outlineVisible, options.planeBasis),\n silhouettes: projectPolylines(geometry.silhouettes, options.planeBasis),\n featureEdges: projectPolylines(geometry.featureEdges, options.planeBasis),\n cutLines: projectPolylines(geometry.cutLines, options.planeBasis),\n stairArrows: projectPolylines(geometry.stairArrows, options.planeBasis)\n };\n\n const bounds = options.bounds ?? computeBoundsFromProjected(proj);\n const viewBox = options.viewBox ?? boundsToViewBox(bounds, options.boundsPadding ?? 0.05);\n\n const parts: string[] = [];\n\n if (includeXml) parts.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n\n const [minX, minY, width, height] = viewBox;\n parts.push(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" ` +\n `viewBox=\"${minX} ${minY} ${width} ${height}\" ` +\n `width=\"${width}\" height=\"${height}\">`\n );\n\n if (options.backgroundColor) {\n parts.push(\n `<rect x=\"${minX}\" y=\"${minY}\" width=\"${width}\" height=\"${height}\" fill=\"${options.backgroundColor}\"/>`\n );\n }\n\n parts.push('<g fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">');\n\n const categories: Array<[keyof typeof proj, keyof Required<SvgRenderStyles>]> = [\n ['hiddenLines', 'hiddenLines'],\n ['outlineHidden', 'outlineHidden'],\n ['visibleLines', 'visibleLines'],\n ['outlineVisible', 'outlineVisible'],\n ['silhouettes', 'silhouettes'],\n ['featureEdges', 'featureEdges'],\n ['cutLines', 'cutLines'],\n ['stairArrows', 'stairArrows']\n ];\n\n for (const [key, styleKey] of categories) {\n const style = styles[styleKey];\n if (style.enabled) renderProjectedCategory(parts, proj[key], style, bounds);\n }\n\n parts.push('</g>');\n parts.push('</svg>');\n return parts.join('\\n');\n}\n\nfunction projectPolylines(polylines: Vec3[][], planeBasis?: SvgPlaneBasis): Pt2[][] {\n const result: Pt2[][] = [];\n for (const polyline of polylines) {\n if (polyline.length === 0) continue;\n const projected: Pt2[] = [];\n for (const point of polyline) {\n if (!Number.isFinite(point[0]) || !Number.isFinite(point[1]) || !Number.isFinite(point[2])) continue;\n if (planeBasis) {\n const dx = point[0] - planeBasis.origin.x;\n const dy = point[1] - planeBasis.origin.y;\n const dz = point[2] - planeBasis.origin.z;\n projected.push({\n x: dx * planeBasis.right.x + dy * planeBasis.right.y + dz * planeBasis.right.z,\n y: dx * planeBasis.up.x + dy * planeBasis.up.y + dz * planeBasis.up.z\n });\n } else {\n projected.push({ x: point[0], y: -point[2] });\n }\n }\n if (projected.length > 0) result.push(projected);\n }\n return result;\n}\n\nfunction computeBoundsFromProjected(categories: Record<string, Pt2[][]>): Bounds {\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity;\n for (const polylines of Object.values(categories)) {\n for (const polyline of polylines) {\n for (const pt of polyline) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minY) minY = pt.y;\n if (pt.y > maxY) maxY = pt.y;\n }\n }\n }\n if (!Number.isFinite(minX)) return { minX: 0, maxX: 1, minY: 0, maxY: 1 };\n return { minX, maxX, minY, maxY };\n}\n\nfunction renderProjectedCategory(\n parts: string[],\n polylines: Pt2[][],\n style: { stroke?: string; strokeWidth?: number; strokeDasharray?: string; opacity?: number },\n bounds: Bounds\n): void {\n if (!polylines.length) return;\n const pathData = projectedToPathData(polylines, bounds);\n if (!pathData) return;\n\n let pathElement = `<path d=\"${pathData}\"`;\n if (style.stroke) pathElement += ` stroke=\"${style.stroke}\"`;\n if (style.strokeWidth !== undefined) pathElement += ` stroke-width=\"${style.strokeWidth}\"`;\n if (style.strokeDasharray) pathElement += ` stroke-dasharray=\"${style.strokeDasharray}\"`;\n if (style.opacity !== undefined && style.opacity !== 1) pathElement += ` opacity=\"${style.opacity}\"`;\n pathElement += '/>';\n parts.push(pathElement);\n}\n\nfunction projectedToPathData(polylines: Pt2[][], bounds: Bounds): string {\n const parts: string[] = [];\n const ox = bounds.minX;\n const oy = bounds.maxY;\n for (const polyline of polylines) {\n if (polyline.length === 0) continue;\n parts.push(`M ${(polyline[0].x - ox).toFixed(6)} ${(oy - polyline[0].y).toFixed(6)}`);\n for (let i = 1; i < polyline.length; i++) {\n parts.push(`L ${(polyline[i].x - ox).toFixed(6)} ${(oy - polyline[i].y).toFixed(6)}`);\n }\n }\n return parts.join(' ');\n}\n\nfunction boundsToViewBox(bounds: Bounds, padding: number): [number, number, number, number] {\n const width = bounds.maxX - bounds.minX;\n const height = bounds.maxY - bounds.minY;\n const padAmount = Math.max(width, height) * padding;\n return [-padAmount, -padAmount, width + padAmount * 2, height + padAmount * 2];\n}\n\nfunction mergeStyles(userStyles?: SvgRenderStyles): Required<SvgRenderStyles> {\n return {\n cutLines: { ...DEFAULT_SVG_STYLES.cutLines, ...userStyles?.cutLines },\n visibleLines: { ...DEFAULT_SVG_STYLES.visibleLines, ...userStyles?.visibleLines },\n hiddenLines: { ...DEFAULT_SVG_STYLES.hiddenLines, ...userStyles?.hiddenLines },\n silhouettes: { ...DEFAULT_SVG_STYLES.silhouettes, ...userStyles?.silhouettes },\n featureEdges: { ...DEFAULT_SVG_STYLES.featureEdges, ...userStyles?.featureEdges },\n stairArrows: { ...DEFAULT_SVG_STYLES.stairArrows, ...userStyles?.stairArrows },\n outlineVisible: { ...DEFAULT_SVG_STYLES.outlineVisible, ...userStyles?.outlineVisible },\n outlineHidden: { ...DEFAULT_SVG_STYLES.outlineHidden, ...userStyles?.outlineHidden }\n };\n}\n"]}
1
+ {"version":3,"file":"printToSvg.js","sourceRoot":"/","sources":["printToSvg.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SlabSectionGeometry, Vec3 } from '@twinfinity/geometry';\nimport type { PrintToSvgOptions, SvgRenderStyles, SvgPlaneBasis } from './types';\nimport { DEFAULT_SVG_STYLES } from './types';\n\ntype Pt2 = { x: number; y: number };\ntype Bounds = { minX: number; maxX: number; minY: number; maxY: number };\n\n/**\n * Render SlabSectionGeometry to an SVG string.\n *\n * @param geometry The computed slab section geometry\n * @param options Rendering options (viewBox, styles, planeBasis, etc.)\n * @returns Complete SVG string\n */\nexport function printToSvg(geometry: SlabSectionGeometry, options: PrintToSvgOptions = {}): string {\n if (!geometry) throw new Error('geometry is required');\n\n if (\n options.boundsPadding !== undefined &&\n (typeof options.boundsPadding !== 'number' || options.boundsPadding < 0)\n ) {\n throw new Error('boundsPadding must be a non-negative number');\n }\n\n if (options.viewBox) {\n if (!Array.isArray(options.viewBox) || options.viewBox.length !== 4) {\n throw new Error('viewBox must be an array of 4 numbers [minX, minY, width, height]');\n }\n for (let i = 0; i < 4; i++) {\n if (typeof options.viewBox[i] !== 'number' || !Number.isFinite(options.viewBox[i])) {\n throw new Error(`viewBox[${i}] must be a finite number`);\n }\n }\n }\n\n const styles = mergeStyles(options.styles);\n const includeXml = options.includeXmlDeclaration ?? true;\n\n // Project all polylines to 2D once\n const proj = {\n hiddenLines: projectPolylines(geometry.hiddenLines, options.planeBasis),\n outlineHidden: projectPolylines(geometry.outlineHidden, options.planeBasis),\n silhouettesHidden: projectPolylines(geometry.silhouettesHidden, options.planeBasis),\n visibleLines: projectPolylines(geometry.visibleLines, options.planeBasis),\n outlineVisible: projectPolylines(geometry.outlineVisible, options.planeBasis),\n silhouettesVisible: projectPolylines(geometry.silhouettesVisible, options.planeBasis),\n featureEdges: projectPolylines(geometry.featureEdges, options.planeBasis),\n cutLines: projectPolylines(geometry.cutLines, options.planeBasis),\n stairArrows: projectPolylines(geometry.stairArrows, options.planeBasis)\n };\n\n const bounds = options.bounds ?? computeBoundsFromProjected(proj);\n const viewBox = options.viewBox ?? boundsToViewBox(bounds, options.boundsPadding ?? 0.05);\n\n const parts: string[] = [];\n\n if (includeXml) parts.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n\n const [minX, minY, width, height] = viewBox;\n parts.push(\n `<svg xmlns=\"http://www.w3.org/2000/svg\" ` +\n `viewBox=\"${minX} ${minY} ${width} ${height}\" ` +\n `width=\"${width}\" height=\"${height}\">`\n );\n\n if (options.backgroundColor) {\n parts.push(\n `<rect x=\"${minX}\" y=\"${minY}\" width=\"${width}\" height=\"${height}\" fill=\"${options.backgroundColor}\"/>`\n );\n }\n\n parts.push('<g fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\">');\n\n // Hidden categories paint first so visible linework always draws over them.\n const categories: Array<[keyof typeof proj, keyof Required<SvgRenderStyles>]> = [\n ['hiddenLines', 'hiddenLines'],\n ['outlineHidden', 'outlineHidden'],\n ['silhouettesHidden', 'silhouettesHidden'],\n ['visibleLines', 'visibleLines'],\n ['outlineVisible', 'outlineVisible'],\n ['silhouettesVisible', 'silhouettesVisible'],\n ['featureEdges', 'featureEdges'],\n ['cutLines', 'cutLines'],\n ['stairArrows', 'stairArrows']\n ];\n\n for (const [key, styleKey] of categories) {\n const style = styles[styleKey];\n if (style.enabled) renderProjectedCategory(parts, proj[key], style, bounds);\n }\n\n parts.push('</g>');\n parts.push('</svg>');\n return parts.join('\\n');\n}\n\nfunction projectPolylines(polylines: Vec3[][], planeBasis?: SvgPlaneBasis): Pt2[][] {\n const result: Pt2[][] = [];\n for (const polyline of polylines) {\n if (polyline.length === 0) continue;\n const projected: Pt2[] = [];\n for (const point of polyline) {\n if (!Number.isFinite(point[0]) || !Number.isFinite(point[1]) || !Number.isFinite(point[2])) continue;\n if (planeBasis) {\n const dx = point[0] - planeBasis.origin.x;\n const dy = point[1] - planeBasis.origin.y;\n const dz = point[2] - planeBasis.origin.z;\n projected.push({\n x: dx * planeBasis.right.x + dy * planeBasis.right.y + dz * planeBasis.right.z,\n y: dx * planeBasis.up.x + dy * planeBasis.up.y + dz * planeBasis.up.z\n });\n } else {\n projected.push({ x: point[0], y: -point[2] });\n }\n }\n if (projected.length > 0) result.push(projected);\n }\n return result;\n}\n\nfunction computeBoundsFromProjected(categories: Record<string, Pt2[][]>): Bounds {\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity;\n for (const polylines of Object.values(categories)) {\n for (const polyline of polylines) {\n for (const pt of polyline) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minY) minY = pt.y;\n if (pt.y > maxY) maxY = pt.y;\n }\n }\n }\n if (!Number.isFinite(minX)) return { minX: 0, maxX: 1, minY: 0, maxY: 1 };\n return { minX, maxX, minY, maxY };\n}\n\nfunction renderProjectedCategory(\n parts: string[],\n polylines: Pt2[][],\n style: { stroke?: string; strokeWidth?: number; strokeDasharray?: string; opacity?: number },\n bounds: Bounds\n): void {\n if (!polylines.length) return;\n const pathData = projectedToPathData(polylines, bounds);\n if (!pathData) return;\n\n let pathElement = `<path d=\"${pathData}\"`;\n if (style.stroke) pathElement += ` stroke=\"${style.stroke}\"`;\n if (style.strokeWidth !== undefined) pathElement += ` stroke-width=\"${style.strokeWidth}\"`;\n if (style.strokeDasharray) pathElement += ` stroke-dasharray=\"${style.strokeDasharray}\"`;\n if (style.opacity !== undefined && style.opacity !== 1) pathElement += ` opacity=\"${style.opacity}\"`;\n pathElement += '/>';\n parts.push(pathElement);\n}\n\nfunction projectedToPathData(polylines: Pt2[][], bounds: Bounds): string {\n const parts: string[] = [];\n const ox = bounds.minX;\n const oy = bounds.maxY;\n for (const polyline of polylines) {\n if (polyline.length === 0) continue;\n parts.push(`M ${(polyline[0].x - ox).toFixed(6)} ${(oy - polyline[0].y).toFixed(6)}`);\n for (let i = 1; i < polyline.length; i++) {\n parts.push(`L ${(polyline[i].x - ox).toFixed(6)} ${(oy - polyline[i].y).toFixed(6)}`);\n }\n }\n return parts.join(' ');\n}\n\nfunction boundsToViewBox(bounds: Bounds, padding: number): [number, number, number, number] {\n const width = bounds.maxX - bounds.minX;\n const height = bounds.maxY - bounds.minY;\n const padAmount = Math.max(width, height) * padding;\n return [-padAmount, -padAmount, width + padAmount * 2, height + padAmount * 2];\n}\n\nfunction mergeStyles(userStyles?: SvgRenderStyles): Required<SvgRenderStyles> {\n return {\n cutLines: { ...DEFAULT_SVG_STYLES.cutLines, ...userStyles?.cutLines },\n visibleLines: { ...DEFAULT_SVG_STYLES.visibleLines, ...userStyles?.visibleLines },\n hiddenLines: { ...DEFAULT_SVG_STYLES.hiddenLines, ...userStyles?.hiddenLines },\n silhouettesVisible: { ...DEFAULT_SVG_STYLES.silhouettesVisible, ...userStyles?.silhouettesVisible },\n silhouettesHidden: { ...DEFAULT_SVG_STYLES.silhouettesHidden, ...userStyles?.silhouettesHidden },\n featureEdges: { ...DEFAULT_SVG_STYLES.featureEdges, ...userStyles?.featureEdges },\n stairArrows: { ...DEFAULT_SVG_STYLES.stairArrows, ...userStyles?.stairArrows },\n outlineVisible: { ...DEFAULT_SVG_STYLES.outlineVisible, ...userStyles?.outlineVisible },\n outlineHidden: { ...DEFAULT_SVG_STYLES.outlineHidden, ...userStyles?.outlineHidden }\n };\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The shipped `templates/template-schema.json` contains VS Code snippet
3
+ * placeholders (`${1:value}`) inside `defaultSnippets` bodies — valid for
4
+ * Monaco/VS Code, but not strict JSON. Consumers that need to parse the
5
+ * schema (ajv validation, editors without snippet support) must strip them
6
+ * first; this is the one shared implementation.
7
+ */
8
+ export declare function sanitizeTemplateSchemaSnippets(schemaJsonText: string): string;
9
+ //# sourceMappingURL=schemaUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemaUtils.d.ts","sourceRoot":"/","sources":["schemaUtils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAK7E"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The shipped `templates/template-schema.json` contains VS Code snippet
3
+ * placeholders (`${1:value}`) inside `defaultSnippets` bodies — valid for
4
+ * Monaco/VS Code, but not strict JSON. Consumers that need to parse the
5
+ * schema (ajv validation, editors without snippet support) must strip them
6
+ * first; this is the one shared implementation.
7
+ */
8
+ export function sanitizeTemplateSchemaSnippets(schemaJsonText) {
9
+ return schemaJsonText
10
+ .replace(/\$\{\d+:(-?[\d.]+)\}/g, '$1') // ${1:420} -> 420
11
+ .replace(/\$\{\d+:([^}"]*)\}/g, '$1') // ${2:main} -> main
12
+ .replace(/\$\{\d+\}|\$\d+/g, ''); // ${3} / $0 -> (removed)
13
+ }
14
+ //# sourceMappingURL=schemaUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemaUtils.js","sourceRoot":"/","sources":["schemaUtils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAAC,cAAsB;IACjE,OAAO,cAAc;SAChB,OAAO,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAC,oBAAoB;SAC3D,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC,qBAAqB;SAC1D,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,CAAC,0BAA0B;AACpE,CAAC","sourcesContent":["/**\n * The shipped `templates/template-schema.json` contains VS Code snippet\n * placeholders (`${1:value}`) inside `defaultSnippets` bodies — valid for\n * Monaco/VS Code, but not strict JSON. Consumers that need to parse the\n * schema (ajv validation, editors without snippet support) must strip them\n * first; this is the one shared implementation.\n */\nexport function sanitizeTemplateSchemaSnippets(schemaJsonText: string): string {\n return schemaJsonText\n .replace(/\\$\\{\\d+:(-?[\\d.]+)\\}/g, '$1') // ${1:420} -> 420\n .replace(/\\$\\{\\d+:([^}\"]*)\\}/g, '$1') // ${2:main} -> main\n .replace(/\\$\\{\\d+\\}|\\$\\d+/g, ''); // ${3} / $0 -> (removed)\n}\n"]}
@@ -1,11 +1,23 @@
1
1
  {
2
2
  "id": "a3-landscape-with-logo",
3
- "page": { "widthMm": 420, "heightMm": 297, "marginMm": 10, "backgroundColor": "#ffffff" },
3
+ "page": {
4
+ "widthMm": 420,
5
+ "heightMm": 297,
6
+ "marginMm": 10,
7
+ "backgroundColor": "#ffffff"
8
+ },
4
9
  "styles": {
5
- "body": { "fontFamily": "Arial", "fontSizeMm": 3.5 }
10
+ "body": {
11
+ "fontFamily": "Arial",
12
+ "fontSizeMm": 3.5
13
+ }
6
14
  },
7
- "assets": [
8
- { "id": "logo-main", "src": "https://twinfinity.com/theme-assets/logo.svg" }
15
+ "allowRemoteResources": true,
16
+ "resources": [
17
+ {
18
+ "id": "logo-main",
19
+ "src": "https://twinfinity.com/theme-assets/logo.svg"
20
+ }
9
21
  ],
10
22
  "elements": [
11
23
  {
@@ -15,7 +27,7 @@
15
27
  "yMm": 10,
16
28
  "widthMm": 80,
17
29
  "heightMm": 20,
18
- "assetId": "logo-main",
30
+ "resourceId": "logo-main",
19
31
  "fit": "contain"
20
32
  },
21
33
  {
@@ -47,9 +59,19 @@
47
59
  "widthMm": 410,
48
60
  "heightMm": 230,
49
61
  "scaleMode": "fitAndRound",
50
- "preferredScales": [2, 10, 25, 50, 100, 150, 250, 500, 1000, 2500],
51
- "align": "center",
52
- "border": { "stroke": "none", "strokeWidthMm": 0.25 }
62
+ "preferredScales": [
63
+ 2,
64
+ 10,
65
+ 25,
66
+ 50,
67
+ 100,
68
+ 150,
69
+ 250,
70
+ 500,
71
+ 1000,
72
+ 2500
73
+ ],
74
+ "align": "center"
53
75
  },
54
76
  {
55
77
  "kind": "scaleBar",
package/dist/types.d.ts CHANGED
@@ -11,7 +11,8 @@ export interface SvgRenderStyles {
11
11
  cutLines?: SvgLineStyle;
12
12
  visibleLines?: SvgLineStyle;
13
13
  hiddenLines?: SvgLineStyle;
14
- silhouettes?: SvgLineStyle;
14
+ silhouettesVisible?: SvgLineStyle;
15
+ silhouettesHidden?: SvgLineStyle;
15
16
  featureEdges?: SvgLineStyle;
16
17
  stairArrows?: SvgLineStyle;
17
18
  outlineVisible?: SvgLineStyle;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"/","sources":["types.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,aAAa,CAAC,EAAE,YAAY,CAAC;CAChC;AAED,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,EAAE,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,8BAA8B;AAC9B,MAAM,WAAW,iBAAiB;IAC9B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,eAAe,CASxD,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"/","sources":["types.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,kBAAkB,CAAC,EAAE,YAAY,CAAC;IAClC,iBAAiB,CAAC,EAAE,YAAY,CAAC;IACjC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,aAAa,CAAC,EAAE,YAAY,CAAC;CAChC;AAED,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,EAAE,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,8BAA8B;AAC9B,MAAM,WAAW,iBAAiB;IAC9B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,eAAe,CAkBxD,CAAC"}