@linkiez/dxf-renew 7.0.0 → 7.2.0

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 (252) hide show
  1. package/.eslintrc.json +1 -16
  2. package/.github/instructions/code-patterns.instructions.md +1 -1
  3. package/.github/instructions/exdxf.instruction.md +161 -0
  4. package/.github/instructions/tdd.instructions.md +271 -0
  5. package/.yarn/install-state.gz +0 -0
  6. package/ARCHITECTURE.md +163 -0
  7. package/CHANGELOG.md +39 -0
  8. package/CONTRIBUTING.md +16 -14
  9. package/README.md +113 -16
  10. package/{PLAN.md → ROADMAP.md} +244 -102
  11. package/dist/dxf.js +2212 -454
  12. package/docs/EZDXF_REFERENCE_SITEMAP.md +55 -0
  13. package/docs/FIXTURE_VALIDATION_EZDXF.md +62 -0
  14. package/lib/Helper.cjs +6 -2
  15. package/lib/Helper.cjs.map +3 -3
  16. package/lib/Helper.js +6 -2
  17. package/lib/Helper.js.map +2 -2
  18. package/lib/denormalise.cjs +131 -91
  19. package/lib/denormalise.cjs.map +2 -2
  20. package/lib/denormalise.js +131 -91
  21. package/lib/denormalise.js.map +2 -2
  22. package/lib/dimensionToSVG.cjs +318 -53
  23. package/lib/dimensionToSVG.cjs.map +3 -3
  24. package/lib/dimensionToSVG.js +316 -52
  25. package/lib/dimensionToSVG.js.map +2 -2
  26. package/lib/entityToPolyline.cjs +95 -0
  27. package/lib/entityToPolyline.cjs.map +3 -3
  28. package/lib/entityToPolyline.js +95 -0
  29. package/lib/entityToPolyline.js.map +2 -2
  30. package/lib/handlers/entities.cjs +111 -27
  31. package/lib/handlers/entities.cjs.map +3 -3
  32. package/lib/handlers/entities.js +111 -27
  33. package/lib/handlers/entities.js.map +3 -3
  34. package/lib/handlers/entity/dgnUnderlay.cjs +106 -0
  35. package/lib/handlers/entity/dgnUnderlay.cjs.map +7 -0
  36. package/lib/handlers/entity/dgnUnderlay.js +71 -0
  37. package/lib/handlers/entity/dgnUnderlay.js.map +7 -0
  38. package/lib/handlers/entity/dimension.cjs +24 -0
  39. package/lib/handlers/entity/dimension.cjs.map +2 -2
  40. package/lib/handlers/entity/dimension.js +24 -0
  41. package/lib/handlers/entity/dimension.js.map +2 -2
  42. package/lib/handlers/entity/dwfUnderlay.cjs +106 -0
  43. package/lib/handlers/entity/dwfUnderlay.cjs.map +7 -0
  44. package/lib/handlers/entity/dwfUnderlay.js +71 -0
  45. package/lib/handlers/entity/dwfUnderlay.js.map +7 -0
  46. package/lib/handlers/entity/image.cjs +123 -0
  47. package/lib/handlers/entity/image.cjs.map +7 -0
  48. package/lib/handlers/entity/image.js +88 -0
  49. package/lib/handlers/entity/image.js.map +7 -0
  50. package/lib/handlers/entity/leader.cjs +148 -0
  51. package/lib/handlers/entity/leader.cjs.map +7 -0
  52. package/lib/handlers/entity/leader.js +113 -0
  53. package/lib/handlers/entity/leader.js.map +7 -0
  54. package/lib/handlers/entity/mleader.cjs +69 -0
  55. package/lib/handlers/entity/mleader.cjs.map +7 -0
  56. package/lib/handlers/entity/mleader.js +34 -0
  57. package/lib/handlers/entity/mleader.js.map +7 -0
  58. package/lib/handlers/entity/mline.cjs +91 -0
  59. package/lib/handlers/entity/mline.cjs.map +7 -0
  60. package/lib/handlers/entity/mline.js +56 -0
  61. package/lib/handlers/entity/mline.js.map +7 -0
  62. package/lib/handlers/entity/oleframe.cjs +98 -0
  63. package/lib/handlers/entity/oleframe.cjs.map +7 -0
  64. package/lib/handlers/entity/oleframe.js +63 -0
  65. package/lib/handlers/entity/oleframe.js.map +7 -0
  66. package/lib/handlers/entity/pdfUnderlay.cjs +106 -0
  67. package/lib/handlers/entity/pdfUnderlay.cjs.map +7 -0
  68. package/lib/handlers/entity/pdfUnderlay.js +71 -0
  69. package/lib/handlers/entity/pdfUnderlay.js.map +7 -0
  70. package/lib/handlers/entity/ray.cjs +81 -0
  71. package/lib/handlers/entity/ray.cjs.map +7 -0
  72. package/lib/handlers/entity/ray.js +46 -0
  73. package/lib/handlers/entity/ray.js.map +7 -0
  74. package/lib/handlers/entity/region.cjs +67 -0
  75. package/lib/handlers/entity/region.cjs.map +7 -0
  76. package/lib/handlers/entity/region.js +32 -0
  77. package/lib/handlers/entity/region.js.map +7 -0
  78. package/lib/handlers/entity/shape.cjs +95 -0
  79. package/lib/handlers/entity/shape.cjs.map +7 -0
  80. package/lib/handlers/entity/shape.js +60 -0
  81. package/lib/handlers/entity/shape.js.map +7 -0
  82. package/lib/handlers/entity/table.cjs +71 -0
  83. package/lib/handlers/entity/table.cjs.map +7 -0
  84. package/lib/handlers/entity/table.js +36 -0
  85. package/lib/handlers/entity/table.js.map +7 -0
  86. package/lib/handlers/entity/tolerance.cjs +90 -0
  87. package/lib/handlers/entity/tolerance.cjs.map +7 -0
  88. package/lib/handlers/entity/tolerance.js +55 -0
  89. package/lib/handlers/entity/tolerance.js.map +7 -0
  90. package/lib/handlers/entity/trace.cjs +101 -0
  91. package/lib/handlers/entity/trace.cjs.map +7 -0
  92. package/lib/handlers/entity/trace.js +66 -0
  93. package/lib/handlers/entity/trace.js.map +7 -0
  94. package/lib/handlers/entity/wipeout.cjs +122 -0
  95. package/lib/handlers/entity/wipeout.cjs.map +7 -0
  96. package/lib/handlers/entity/wipeout.js +87 -0
  97. package/lib/handlers/entity/wipeout.js.map +7 -0
  98. package/lib/handlers/entity/xline.cjs +81 -0
  99. package/lib/handlers/entity/xline.cjs.map +7 -0
  100. package/lib/handlers/entity/xline.js +46 -0
  101. package/lib/handlers/entity/xline.js.map +7 -0
  102. package/lib/handlers/objects.cjs +299 -136
  103. package/lib/handlers/objects.cjs.map +2 -2
  104. package/lib/handlers/objects.js +299 -136
  105. package/lib/handlers/objects.js.map +2 -2
  106. package/lib/handlers/tables.cjs +96 -17
  107. package/lib/handlers/tables.cjs.map +2 -2
  108. package/lib/handlers/tables.js +96 -17
  109. package/lib/handlers/tables.js.map +2 -2
  110. package/lib/index.cjs +5 -2
  111. package/lib/index.cjs.map +3 -3
  112. package/lib/index.js +18 -16
  113. package/lib/index.js.map +3 -3
  114. package/lib/toJson.cjs +29 -0
  115. package/lib/toJson.cjs.map +7 -0
  116. package/lib/toJson.js +9 -0
  117. package/lib/toJson.js.map +7 -0
  118. package/lib/toSVG.cjs +105 -11
  119. package/lib/toSVG.cjs.map +3 -3
  120. package/lib/toSVG.js +106 -12
  121. package/lib/toSVG.js.map +2 -2
  122. package/lib/types/dimension-entity.cjs.map +1 -1
  123. package/lib/types/entity.cjs.map +1 -1
  124. package/lib/types/helper.cjs.map +1 -1
  125. package/lib/types/image-entity.cjs +17 -0
  126. package/lib/types/image-entity.cjs.map +7 -0
  127. package/lib/types/image-entity.js +1 -0
  128. package/lib/types/image-entity.js.map +7 -0
  129. package/lib/types/index.cjs +28 -0
  130. package/lib/types/index.cjs.map +2 -2
  131. package/lib/types/index.js +14 -0
  132. package/lib/types/index.js.map +2 -2
  133. package/lib/types/leader-entity.cjs +17 -0
  134. package/lib/types/leader-entity.cjs.map +7 -0
  135. package/lib/types/leader-entity.js +1 -0
  136. package/lib/types/leader-entity.js.map +7 -0
  137. package/lib/types/mleader-entity.cjs +17 -0
  138. package/lib/types/mleader-entity.cjs.map +7 -0
  139. package/lib/types/mleader-entity.js +1 -0
  140. package/lib/types/mleader-entity.js.map +7 -0
  141. package/lib/types/mline-entity.cjs +17 -0
  142. package/lib/types/mline-entity.cjs.map +7 -0
  143. package/lib/types/mline-entity.js +1 -0
  144. package/lib/types/mline-entity.js.map +7 -0
  145. package/lib/types/oleframe-entity.cjs +17 -0
  146. package/lib/types/oleframe-entity.cjs.map +7 -0
  147. package/lib/types/oleframe-entity.js +1 -0
  148. package/lib/types/oleframe-entity.js.map +7 -0
  149. package/lib/types/options.cjs.map +1 -1
  150. package/lib/types/ray-entity.cjs +17 -0
  151. package/lib/types/ray-entity.cjs.map +7 -0
  152. package/lib/types/ray-entity.js +1 -0
  153. package/lib/types/ray-entity.js.map +7 -0
  154. package/lib/types/region-entity.cjs +17 -0
  155. package/lib/types/region-entity.cjs.map +7 -0
  156. package/lib/types/region-entity.js +1 -0
  157. package/lib/types/region-entity.js.map +7 -0
  158. package/lib/types/shape-entity.cjs +17 -0
  159. package/lib/types/shape-entity.cjs.map +7 -0
  160. package/lib/types/shape-entity.js +1 -0
  161. package/lib/types/shape-entity.js.map +7 -0
  162. package/lib/types/table-entity.cjs +17 -0
  163. package/lib/types/table-entity.cjs.map +7 -0
  164. package/lib/types/table-entity.js +1 -0
  165. package/lib/types/table-entity.js.map +7 -0
  166. package/lib/types/tables.cjs.map +1 -1
  167. package/lib/types/tolerance-entity.cjs +17 -0
  168. package/lib/types/tolerance-entity.cjs.map +7 -0
  169. package/lib/types/tolerance-entity.js +1 -0
  170. package/lib/types/tolerance-entity.js.map +7 -0
  171. package/lib/types/trace-entity.cjs +17 -0
  172. package/lib/types/trace-entity.cjs.map +7 -0
  173. package/lib/types/trace-entity.js +1 -0
  174. package/lib/types/trace-entity.js.map +7 -0
  175. package/lib/types/underlay-entity.cjs +17 -0
  176. package/lib/types/underlay-entity.cjs.map +7 -0
  177. package/lib/types/underlay-entity.js +1 -0
  178. package/lib/types/underlay-entity.js.map +7 -0
  179. package/lib/types/wipeout-entity.cjs +17 -0
  180. package/lib/types/wipeout-entity.cjs.map +7 -0
  181. package/lib/types/wipeout-entity.js +1 -0
  182. package/lib/types/wipeout-entity.js.map +7 -0
  183. package/lib/types/xline-entity.cjs +17 -0
  184. package/lib/types/xline-entity.cjs.map +7 -0
  185. package/lib/types/xline-entity.js +1 -0
  186. package/lib/types/xline-entity.js.map +7 -0
  187. package/lib/util/escapeXmlText.cjs +27 -0
  188. package/lib/util/escapeXmlText.cjs.map +7 -0
  189. package/lib/util/escapeXmlText.js +7 -0
  190. package/lib/util/escapeXmlText.js.map +7 -0
  191. package/package.json +9 -18
  192. package/playwright.config.cjs +20 -0
  193. package/src/Helper.ts +8 -3
  194. package/src/denormalise.ts +182 -116
  195. package/src/dimensionToSVG.ts +466 -54
  196. package/src/entityToPolyline.ts +124 -2
  197. package/src/handlers/entities.ts +129 -34
  198. package/src/handlers/entity/dgnUnderlay.ts +94 -0
  199. package/src/handlers/entity/dimension.ts +27 -1
  200. package/src/handlers/entity/dwfUnderlay.ts +94 -0
  201. package/src/handlers/entity/image.ts +118 -0
  202. package/src/handlers/entity/leader.ts +153 -0
  203. package/src/handlers/entity/mleader.ts +46 -0
  204. package/src/handlers/entity/mline.ts +74 -0
  205. package/src/handlers/entity/oleframe.ts +62 -0
  206. package/src/handlers/entity/pdfUnderlay.ts +94 -0
  207. package/src/handlers/entity/ray.ts +52 -0
  208. package/src/handlers/entity/region.ts +42 -0
  209. package/src/handlers/entity/shape.ts +73 -0
  210. package/src/handlers/entity/table.ts +49 -0
  211. package/src/handlers/entity/tolerance.ts +75 -0
  212. package/src/handlers/entity/trace.ts +72 -0
  213. package/src/handlers/entity/wipeout.ts +114 -0
  214. package/src/handlers/entity/xline.ts +52 -0
  215. package/src/handlers/objects.ts +379 -139
  216. package/src/handlers/tables.ts +134 -21
  217. package/src/index.ts +9 -18
  218. package/src/toJson.ts +8 -0
  219. package/src/toSVG.ts +143 -10
  220. package/src/types/dimension-entity.ts +11 -0
  221. package/src/types/entity.ts +30 -0
  222. package/src/types/helper.ts +2 -1
  223. package/src/types/image-entity.ts +35 -0
  224. package/src/types/index.ts +14 -0
  225. package/src/types/leader-entity.ts +40 -0
  226. package/src/types/mleader-entity.ts +8 -0
  227. package/src/types/mline-entity.ts +12 -0
  228. package/src/types/oleframe-entity.ts +40 -0
  229. package/src/types/options.ts +48 -0
  230. package/src/types/ray-entity.ts +12 -0
  231. package/src/types/region-entity.ts +11 -0
  232. package/src/types/shape-entity.ts +19 -0
  233. package/src/types/table-entity.ts +14 -0
  234. package/src/types/tables.ts +160 -0
  235. package/src/types/tolerance-entity.ts +20 -0
  236. package/src/types/trace-entity.ts +14 -0
  237. package/src/types/underlay-entity.ts +35 -0
  238. package/src/types/wipeout-entity.ts +20 -0
  239. package/src/types/xline-entity.ts +12 -0
  240. package/src/util/escapeXmlText.ts +10 -0
  241. package/tools/browser_test_server.cjs +87 -0
  242. package/tools/ezdxf_generate_dimensions_all_types.py +246 -0
  243. package/tools/ezdxf_generate_dimensions_angular_3p.py +59 -0
  244. package/tools/ezdxf_generate_dimensions_large_scale.py +87 -0
  245. package/tools/ezdxf_regenerate_problem_fixtures.py +184 -0
  246. package/tools/ezdxf_validate_fixtures.py +165 -0
  247. package/docs/DIMENSION_SUMMARY.md +0 -248
  248. package/docs/DIMENSION_SUMMARY.pt-BR.md +0 -248
  249. package/docs/IMPLEMENTED-2D-ENTITIES.md +0 -54
  250. package/docs/IMPLEMENTED-2D-ENTITIES.pt-BR.md +0 -54
  251. package/docs/TEXT-MTEXT-DIMENSION-SUPPORT.md +0 -241
  252. package/docs/TEXT-MTEXT-DIMENSION-SUPPORT.pt-BR.md +0 -169
@@ -1,11 +1,256 @@
1
1
  import { Box2 } from 'vecks'
2
2
 
3
3
  import colors from './util/colors'
4
+ import escapeXmlText from './util/escapeXmlText'
5
+ import round10 from './util/round10'
4
6
 
5
- import type { DimensionEntity } from './types'
7
+ import type { DimensionEntity, ToSVGOptions } from './types'
6
8
  import type { DimStyleTable } from './types/dxf'
7
9
  import type { BoundsAndElement } from './types/svg'
8
10
 
11
+ const DEFAULT_DIMENSION_DECIMALS = 2
12
+
13
+ export interface DimensionViewport {
14
+ width: number
15
+ height: number
16
+ }
17
+
18
+ // AutoScale is meant to improve readability of dimension graphics.
19
+ // Scale is derived from the drawing viewport (final SVG viewBox size).
20
+ // No min/max clamp by design.
21
+ const AUTOSCALE_VIEWPORT_REFERENCE = 40
22
+
23
+ const computeViewportAutoScaleFactor = (
24
+ viewport: DimensionViewport,
25
+ options: ToSVGOptions | undefined,
26
+ ): number => {
27
+ const viewportMin = Math.min(Math.abs(viewport.width), Math.abs(viewport.height))
28
+ if (!Number.isFinite(viewportMin) || viewportMin <= 0) return 1
29
+
30
+ const reference = options?.dimension?.autoScaleViewportReference
31
+ const safeReference = Number.isFinite(reference) && (reference ?? 0) > 0
32
+ ? (reference as number)
33
+ : AUTOSCALE_VIEWPORT_REFERENCE
34
+
35
+ return viewportMin / safeReference
36
+ }
37
+
38
+ const getViewportMin = (viewport: DimensionViewport): number => {
39
+ const viewportMin = Math.min(Math.abs(viewport.width), Math.abs(viewport.height))
40
+ return Number.isFinite(viewportMin) ? viewportMin : Number.NaN
41
+ }
42
+
43
+ const getViewportPercentageSize = (
44
+ viewport: DimensionViewport,
45
+ percent: number | undefined,
46
+ ): number | undefined => {
47
+ if (!Number.isFinite(percent) || (percent ?? 0) <= 0) return undefined
48
+ const viewportMin = getViewportMin(viewport)
49
+ if (!Number.isFinite(viewportMin) || viewportMin <= 0) return undefined
50
+ return viewportMin * ((percent as number) / 100)
51
+ }
52
+
53
+ export const getDimensionGeometryBBox = (entity: DimensionEntity): Box2 => {
54
+ const bbox = new Box2()
55
+
56
+ const points = [
57
+ entity.start,
58
+ entity.angleVertex,
59
+ entity.arcPoint,
60
+ entity.textMidpoint,
61
+ entity.measureStart,
62
+ entity.measureEnd,
63
+ ]
64
+
65
+ for (const p of points) {
66
+ if (!p) continue
67
+ const x = p.x
68
+ const y = p.y
69
+ if (!Number.isFinite(x) || !Number.isFinite(y)) continue
70
+ bbox.expandByPoint({ x, y })
71
+ }
72
+
73
+ return bbox
74
+ }
75
+
76
+ const getScaledDimensionSizes = (
77
+ dimStyle: DimStyleTable | undefined,
78
+ options: ToSVGOptions | undefined,
79
+ viewport: DimensionViewport | undefined,
80
+ ): {
81
+ arrowSize: number
82
+ textHeight: number
83
+ extLineOffset: number
84
+ extLineExtension: number
85
+ } => {
86
+ const autoScale = options?.dimension?.autoScale === true
87
+
88
+ const baseArrowSize = dimStyle?.dimAsz ?? 2.5
89
+ const baseTextHeight = dimStyle?.dimTxt ?? 2.5
90
+ const baseExtLineOffset = dimStyle?.dimExo ?? 0.625
91
+ const baseExtLineExtension = dimStyle?.dimExe ?? 1.25
92
+
93
+ if (!autoScale || !viewport) {
94
+ return {
95
+ arrowSize: baseArrowSize,
96
+ textHeight: baseTextHeight,
97
+ extLineOffset: baseExtLineOffset,
98
+ extLineExtension: baseExtLineExtension,
99
+ }
100
+ }
101
+
102
+ const scale = computeViewportAutoScaleFactor(viewport, options)
103
+
104
+ const perc = options?.dimension?.autoScaleViewportPercentages
105
+ const arrowFromPct = getViewportPercentageSize(viewport, perc?.arrowSize)
106
+ const textFromPct = getViewportPercentageSize(viewport, perc?.textHeight)
107
+ const offsetFromPct = getViewportPercentageSize(viewport, perc?.extLineOffset)
108
+ const extensionFromPct = getViewportPercentageSize(viewport, perc?.extLineExtension)
109
+
110
+ return {
111
+ arrowSize: arrowFromPct ?? (baseArrowSize * scale),
112
+ textHeight: textFromPct ?? (baseTextHeight * scale),
113
+ extLineOffset: offsetFromPct ?? (baseExtLineOffset * scale),
114
+ extLineExtension: extensionFromPct ?? (baseExtLineExtension * scale),
115
+ }
116
+ }
117
+
118
+ const formatDimensionValue = (
119
+ value: number,
120
+ decimals: number = DEFAULT_DIMENSION_DECIMALS,
121
+ ): string => {
122
+ if (!Number.isFinite(value)) return ''
123
+ const rounded = round10(value, -decimals)
124
+ return rounded.toFixed(decimals)
125
+ }
126
+
127
+ const computeRadiusFallback = (entity: DimensionEntity): number => {
128
+ const cx = entity.start?.x ?? 0
129
+ const cy = entity.start?.y ?? 0
130
+ const x1 = entity.measureStart?.x ?? 0
131
+ const y1 = entity.measureStart?.y ?? 0
132
+ const x2 = entity.measureEnd?.x ?? 0
133
+ const y2 = entity.measureEnd?.y ?? 0
134
+
135
+ const r1 = Math.hypot(x1 - cx, y1 - cy)
136
+ const r2 = Math.hypot(x2 - cx, y2 - cy)
137
+ const chord = Math.hypot(x2 - x1, y2 - y1)
138
+ return Math.max(r1, r2, chord)
139
+ }
140
+
141
+ const computeLinearDistance = (
142
+ x1: number,
143
+ y1: number,
144
+ x2: number,
145
+ y2: number,
146
+ ): number => Math.hypot(x2 - x1, y2 - y1)
147
+
148
+ const computeAngularDegreesMinimal = (
149
+ cx: number,
150
+ cy: number,
151
+ x1: number,
152
+ y1: number,
153
+ x2: number,
154
+ y2: number,
155
+ ): number => {
156
+ const a1 = Math.atan2(y1 - cy, x1 - cx)
157
+ const a2 = Math.atan2(y2 - cy, x2 - cx)
158
+ let delta = Math.abs(a2 - a1)
159
+ while (delta > Math.PI * 2) delta -= Math.PI * 2
160
+ if (delta > Math.PI) delta = Math.PI * 2 - delta
161
+ return (delta * 180) / Math.PI
162
+ }
163
+
164
+ const computeAngularDegreesCCW = (
165
+ cx: number,
166
+ cy: number,
167
+ x1: number,
168
+ y1: number,
169
+ x2: number,
170
+ y2: number,
171
+ ): number => {
172
+ const a1 = Math.atan2(y1 - cy, x1 - cx)
173
+ const a2 = Math.atan2(y2 - cy, x2 - cx)
174
+ let delta = a2 - a1
175
+ while (delta < 0) delta += Math.PI * 2
176
+ while (delta >= Math.PI * 2) delta -= Math.PI * 2
177
+ return (delta * 180) / Math.PI
178
+ }
179
+
180
+ const computeDimensionMeasurement = (entity: DimensionEntity): string => {
181
+ const x1 = entity.measureStart?.x ?? 0
182
+ const y1 = entity.measureStart?.y ?? 0
183
+ const x2 = entity.measureEnd?.x ?? 0
184
+ const y2 = entity.measureEnd?.y ?? 0
185
+
186
+ switch (entity.dimensionType) {
187
+ case 0:
188
+ case 1:
189
+ case 6: {
190
+ const dist = computeLinearDistance(x1, y1, x2, y2)
191
+ return formatDimensionValue(dist)
192
+ }
193
+ case 3: {
194
+ const dist = computeLinearDistance(x1, y1, x2, y2)
195
+ if (dist > 0) return formatDimensionValue(dist)
196
+ const radius = computeRadiusFallback(entity)
197
+ return formatDimensionValue(radius * 2)
198
+ }
199
+ case 4: {
200
+ const dist = computeLinearDistance(x1, y1, x2, y2)
201
+ if (dist > 0) return formatDimensionValue(dist)
202
+ const radius = computeRadiusFallback(entity)
203
+ return formatDimensionValue(radius)
204
+ }
205
+ case 2: {
206
+ const cx = entity.start?.x ?? 0
207
+ const cy = entity.start?.y ?? 0
208
+ const degrees = computeAngularDegreesMinimal(cx, cy, x1, y1, x2, y2)
209
+ const formatted = formatDimensionValue(degrees)
210
+ return formatted ? `${formatted}°` : ''
211
+ }
212
+ case 5: {
213
+ const cx = entity.angleVertex?.x ?? 0
214
+ const cy = entity.angleVertex?.y ?? 0
215
+ const degrees = computeAngularDegreesCCW(cx, cy, x1, y1, x2, y2)
216
+ const formatted = formatDimensionValue(degrees)
217
+ return formatted ? `${formatted}°` : ''
218
+ }
219
+ default:
220
+ return ''
221
+ }
222
+ }
223
+
224
+ const resolveDimensionText = (entity: DimensionEntity): string => {
225
+ const raw = typeof entity.text === 'string' ? entity.text : ''
226
+ const trimmed = raw.trim()
227
+ const measured = computeDimensionMeasurement(entity)
228
+
229
+ if (!trimmed) return measured
230
+ if (trimmed.includes('<>')) {
231
+ return trimmed.split('<>').join(measured)
232
+ }
233
+ return trimmed
234
+ }
235
+
236
+ const expandBBoxForMarker = (bbox: Box2, x: number, y: number, size: number) => {
237
+ bbox.expandByPoint({ x: x - size, y: y - size })
238
+ bbox.expandByPoint({ x: x + size, y: y + size })
239
+ }
240
+
241
+ const expandBBoxForText = (
242
+ bbox: Box2,
243
+ x: number,
244
+ y: number,
245
+ height: number,
246
+ content: string,
247
+ ) => {
248
+ const textWidth = content.length * height * 0.6
249
+ // text-anchor="middle" is used everywhere in DIMENSION rendering
250
+ bbox.expandByPoint({ x: x - textWidth / 2, y: y - height })
251
+ bbox.expandByPoint({ x: x + textWidth / 2, y: y + height })
252
+ }
253
+
9
254
  /**
10
255
  * Convert DXF color number to SVG color string
11
256
  */
@@ -53,26 +298,150 @@ function getDimensionColors(dimStyle?: DimStyleTable): {
53
298
  export default function dimensionToSVG(
54
299
  entity: DimensionEntity,
55
300
  dimStyle?: DimStyleTable,
301
+ options?: ToSVGOptions,
302
+ viewport?: DimensionViewport,
56
303
  ): BoundsAndElement {
57
304
  // Dispatch to appropriate renderer based on dimension type
58
305
  switch (entity.dimensionType) {
59
306
  case 0: // Rotated, horizontal, or vertical
60
307
  case 1: // Aligned
61
- return renderLinearDimension(entity, dimStyle)
308
+ return renderLinearDimension(entity, dimStyle, options, viewport)
62
309
  case 2: // Angular
63
- return renderAngularDimension(entity, dimStyle)
310
+ return renderAngularDimension(entity, dimStyle, options, viewport)
311
+ case 5: // Angular 3-point
312
+ return renderAngular3PointDimension(entity, dimStyle, options, viewport)
64
313
  case 3: // Diameter
65
- return renderDiameterDimension(entity, dimStyle)
314
+ return renderDiameterDimension(entity, dimStyle, options, viewport)
66
315
  case 4: // Radius
67
- return renderRadialDimension(entity, dimStyle)
316
+ return renderRadialDimension(entity, dimStyle, options, viewport)
68
317
  case 6: // Ordinate
69
- return renderOrdinateDimension(entity, dimStyle)
318
+ return renderOrdinateDimension(entity, dimStyle, options, viewport)
70
319
  default:
71
320
  // Fallback to simple line rendering
72
321
  return renderFallbackDimension(entity)
73
322
  }
74
323
  }
75
324
 
325
+ /**
326
+ * Render angular 3-point dimension (type 5).
327
+ *
328
+ * Based on DXF reference + ezdxf: angle is measured from p1 to p2
329
+ * counter-clockwise around the vertex.
330
+ */
331
+ function renderAngular3PointDimension(
332
+ entity: DimensionEntity,
333
+ dimStyle?: DimStyleTable,
334
+ options?: ToSVGOptions,
335
+ viewport?: DimensionViewport,
336
+ ): BoundsAndElement {
337
+ const bbox = new Box2()
338
+ const elements: string[] = []
339
+ const markers: string[] = []
340
+
341
+ const { arrowSize, textHeight } = getScaledDimensionSizes(dimStyle, options, viewport)
342
+ const { dimLineColor, extLineColor, textColor, dimLineWeight, extLineWeight } = getDimensionColors(dimStyle)
343
+
344
+ const vertexX = entity.angleVertex?.x ?? 0
345
+ const vertexY = entity.angleVertex?.y ?? 0
346
+ const x1 = entity.measureStart?.x ?? 0
347
+ const y1 = entity.measureStart?.y ?? 0
348
+ const x2 = entity.measureEnd?.x ?? 0
349
+ const y2 = entity.measureEnd?.y ?? 0
350
+
351
+ // DXF reference: point (10,20,30) specifies the dimension line arc location.
352
+ // In practice, ezdxf may also provide (16,26,36); prefer arcPoint only if it
353
+ // yields a meaningful radius away from the vertex.
354
+ const startArcX = entity.start?.x ?? 0
355
+ const startArcY = entity.start?.y ?? 0
356
+ const arcPointX = entity.arcPoint?.x
357
+ const arcPointY = entity.arcPoint?.y
358
+
359
+ const arcPointRadius =
360
+ Number.isFinite(arcPointX) && Number.isFinite(arcPointY)
361
+ ? Math.hypot((arcPointX as number) - vertexX, (arcPointY as number) - vertexY)
362
+ : Number.NaN
363
+
364
+ const useArcPoint = Number.isFinite(arcPointRadius) && arcPointRadius > 1e-9
365
+ const arcLocationX = useArcPoint ? (arcPointX as number) : startArcX
366
+ const arcLocationY = useArcPoint ? (arcPointY as number) : startArcY
367
+
368
+ const textX = entity.textMidpoint?.x ?? arcLocationX
369
+ const textY = entity.textMidpoint?.y ?? arcLocationY
370
+
371
+ bbox.expandByPoint({ x: vertexX, y: vertexY })
372
+ bbox.expandByPoint({ x: x1, y: y1 })
373
+ bbox.expandByPoint({ x: x2, y: y2 })
374
+ bbox.expandByPoint({ x: arcLocationX, y: arcLocationY })
375
+ bbox.expandByPoint({ x: textX, y: textY })
376
+
377
+ const a1 = Math.atan2(y1 - vertexY, x1 - vertexX)
378
+ const a2 = Math.atan2(y2 - vertexY, x2 - vertexX)
379
+
380
+ let radius = Math.hypot(arcLocationX - vertexX, arcLocationY - vertexY)
381
+ if (!Number.isFinite(radius) || radius <= 1e-9) {
382
+ radius = Math.hypot(textX - vertexX, textY - vertexY)
383
+ }
384
+ if (!Number.isFinite(radius) || radius <= 1e-9) {
385
+ radius = Math.max(
386
+ Math.hypot(x1 - vertexX, y1 - vertexY),
387
+ Math.hypot(x2 - vertexX, y2 - vertexY),
388
+ )
389
+ }
390
+
391
+ const arcStartX = vertexX + radius * Math.cos(a1)
392
+ const arcStartY = vertexY + radius * Math.sin(a1)
393
+ const arcEndX = vertexX + radius * Math.cos(a2)
394
+ const arcEndY = vertexY + radius * Math.sin(a2)
395
+
396
+ bbox.expandByPoint({ x: arcStartX, y: arcStartY })
397
+ bbox.expandByPoint({ x: arcEndX, y: arcEndY })
398
+
399
+ // Create arrow markers
400
+ const markerId1 = `dim-angular-3p-arrow-start-${Date.now()}`
401
+ const markerId2 = `dim-angular-3p-arrow-end-${Date.now()}`
402
+ markers.push(
403
+ createArrowMarker(markerId1, arrowSize, dimLineColor, 'backward'),
404
+ createArrowMarker(markerId2, arrowSize, dimLineColor, 'forward'),
405
+ )
406
+
407
+ // Extension lines from definition points to arc endpoints.
408
+ elements.push(
409
+ `<line x1="${x1}" y1="${y1}" x2="${arcStartX}" y2="${arcStartY}" stroke="${extLineColor}" stroke-width="${extLineWeight}" />`,
410
+ `<line x1="${x2}" y1="${y2}" x2="${arcEndX}" y2="${arcEndY}" stroke="${extLineColor}" stroke-width="${extLineWeight}" />`,
411
+ )
412
+
413
+ // Arc from a1 to a2 in CCW orientation.
414
+ let delta = a2 - a1
415
+ while (delta < 0) delta += Math.PI * 2
416
+ while (delta >= Math.PI * 2) delta -= Math.PI * 2
417
+ const largeArcFlag = delta > Math.PI ? 1 : 0
418
+ const sweepFlag = 1
419
+
420
+ expandBBoxForMarker(bbox, arcStartX, arcStartY, arrowSize)
421
+ expandBBoxForMarker(bbox, arcEndX, arcEndY, arrowSize)
422
+
423
+ elements.push(
424
+ `<path d="M ${arcStartX} ${arcStartY} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${arcEndX} ${arcEndY}" fill="none" stroke="${dimLineColor}" stroke-width="${dimLineWeight}" marker-start="url(#${markerId1})" marker-end="url(#${markerId2})" />`,
425
+ )
426
+
427
+ const resolvedText = resolveDimensionText(entity)
428
+ if (resolvedText) {
429
+ const midAngle = a1 + delta / 2
430
+ const textRotation = (midAngle * 180) / Math.PI
431
+
432
+ expandBBoxForText(bbox, textX, textY, textHeight, resolvedText)
433
+
434
+ elements.push(
435
+ `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${escapeXmlText(resolvedText)}</text>`,
436
+ )
437
+ }
438
+
439
+ return {
440
+ bbox,
441
+ element: `<defs>${markers.join('')}</defs><g>${elements.join('')}</g>`,
442
+ }
443
+ }
444
+
76
445
  /**
77
446
  * Create SVG marker definition for dimension arrows
78
447
  */
@@ -80,9 +449,14 @@ export function createArrowMarker(
80
449
  id: string,
81
450
  size: number,
82
451
  color: string,
452
+ direction: 'forward' | 'backward' = 'forward',
83
453
  ): string {
84
- const arrowPath = `M 0 0 L ${size} ${size / 2} L 0 ${size} z`
85
- return `<marker id="${id}" markerWidth="${size}" markerHeight="${size}" refX="${size}" refY="${size / 2}" orient="auto" markerUnits="strokeWidth">
454
+ const arrowPath = direction === 'forward'
455
+ ? `M 0 0 L ${size} ${size / 2} L 0 ${size} z`
456
+ : `M ${size} 0 L 0 ${size / 2} L ${size} ${size} z`
457
+ const refX = direction === 'forward' ? size : 0
458
+
459
+ return `<marker id="${id}" markerWidth="${size}" markerHeight="${size}" refX="${refX}" refY="${size / 2}" orient="auto" markerUnits="userSpaceOnUse">
86
460
  <path d="${arrowPath}" fill="${color}" />
87
461
  </marker>`
88
462
  }
@@ -93,16 +467,16 @@ export function createArrowMarker(
93
467
  function renderLinearDimension(
94
468
  entity: DimensionEntity,
95
469
  dimStyle?: DimStyleTable,
470
+ options?: ToSVGOptions,
471
+ viewport?: DimensionViewport,
96
472
  ): BoundsAndElement {
97
473
  const bbox = new Box2()
98
474
  const elements: string[] = []
99
475
  const markers: string[] = []
100
476
 
101
- // Get dimension style properties with defaults
102
- const arrowSize = dimStyle?.dimAsz ?? 2.5
103
- const textHeight = dimStyle?.dimTxt ?? 2.5
104
- const extLineOffset = dimStyle?.dimExo ?? 0.625
105
- const extLineExtension = dimStyle?.dimExe ?? 1.25
477
+ // Get dimension style properties with defaults (optionally auto-scaled)
478
+ const { arrowSize, textHeight, extLineOffset, extLineExtension } =
479
+ getScaledDimensionSizes(dimStyle, options, viewport)
106
480
  const { dimLineColor, extLineColor, textColor, dimLineWeight, extLineWeight } = getDimensionColors(dimStyle)
107
481
 
108
482
  // Extract dimension geometry
@@ -137,8 +511,8 @@ function renderLinearDimension(
137
511
 
138
512
  // Create arrow markers with dimension line color
139
513
  markers.push(
140
- createArrowMarker(markerId1, arrowSize, dimLineColor),
141
- createArrowMarker(markerId2, arrowSize, dimLineColor),
514
+ createArrowMarker(markerId1, arrowSize, dimLineColor, 'backward'),
515
+ createArrowMarker(markerId2, arrowSize, dimLineColor, 'forward'),
142
516
  )
143
517
 
144
518
  // Draw extension lines
@@ -152,6 +526,14 @@ function renderLinearDimension(
152
526
  const extLine2EndX = dimLine2X + Math.cos(perpAngle) * extLineExtension
153
527
  const extLine2EndY = dimLine2Y + Math.sin(perpAngle) * extLineExtension
154
528
 
529
+ // Expand bounding box to include full extension lines and arrow markers
530
+ bbox.expandByPoint({ x: extLine1StartX, y: extLine1StartY })
531
+ bbox.expandByPoint({ x: extLine1EndX, y: extLine1EndY })
532
+ bbox.expandByPoint({ x: extLine2StartX, y: extLine2StartY })
533
+ bbox.expandByPoint({ x: extLine2EndX, y: extLine2EndY })
534
+ expandBBoxForMarker(bbox, dimLine1X, dimLine1Y, arrowSize)
535
+ expandBBoxForMarker(bbox, dimLine2X, dimLine2Y, arrowSize)
536
+
155
537
  elements.push(
156
538
  `<line x1="${extLine1StartX}" y1="${extLine1StartY}" x2="${extLine1EndX}" y2="${extLine1EndY}" stroke="${extLineColor}" stroke-width="${extLineWeight}" />`,
157
539
  `<line x1="${extLine2StartX}" y1="${extLine2StartY}" x2="${extLine2EndX}" y2="${extLine2EndY}" stroke="${extLineColor}" stroke-width="${extLineWeight}" />`,
@@ -159,10 +541,12 @@ function renderLinearDimension(
159
541
  )
160
542
 
161
543
  // Add dimension text
162
- if (entity.text) {
544
+ const resolvedText = resolveDimensionText(entity)
545
+ if (resolvedText) {
163
546
  const textRotation = (angle * 180) / Math.PI
547
+ expandBBoxForText(bbox, textX, textY, textHeight, resolvedText)
164
548
  elements.push(
165
- `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${entity.text}</text>`,
549
+ `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${escapeXmlText(resolvedText)}</text>`,
166
550
  )
167
551
  }
168
552
 
@@ -178,14 +562,15 @@ function renderLinearDimension(
178
562
  function renderAngularDimension(
179
563
  entity: DimensionEntity,
180
564
  dimStyle?: DimStyleTable,
565
+ options?: ToSVGOptions,
566
+ viewport?: DimensionViewport,
181
567
  ): BoundsAndElement {
182
568
  const bbox = new Box2()
183
569
  const elements: string[] = []
184
570
  const markers: string[] = []
185
571
 
186
- // Get dimension style properties
187
- const arrowSize = dimStyle?.dimAsz ?? 2.5
188
- const textHeight = dimStyle?.dimTxt ?? 2.5
572
+ // Get dimension style properties (optionally auto-scaled)
573
+ const { arrowSize, textHeight } = getScaledDimensionSizes(dimStyle, options, viewport)
189
574
  const { dimLineColor, extLineColor, textColor, dimLineWeight, extLineWeight } = getDimensionColors(dimStyle)
190
575
 
191
576
  // Extract points
@@ -207,8 +592,8 @@ function renderAngularDimension(
207
592
  const markerId1 = `dim-angular-arrow-start-${Date.now()}`
208
593
  const markerId2 = `dim-angular-arrow-end-${Date.now()}`
209
594
  markers.push(
210
- createArrowMarker(markerId1, arrowSize, dimLineColor),
211
- createArrowMarker(markerId2, arrowSize, dimLineColor),
595
+ createArrowMarker(markerId1, arrowSize, dimLineColor, 'backward'),
596
+ createArrowMarker(markerId2, arrowSize, dimLineColor, 'forward'),
212
597
  )
213
598
 
214
599
  // Draw extension lines from center to definition points
@@ -234,12 +619,15 @@ function renderAngularDimension(
234
619
  )
235
620
 
236
621
  // Add dimension text
237
- if (entity.text) {
622
+ const resolvedText = resolveDimensionText(entity)
623
+ if (resolvedText) {
238
624
  const midAngle = (startAngle + endAngle) / 2
239
625
  const textRotation = (midAngle * 180) / Math.PI
240
626
 
627
+ expandBBoxForText(bbox, textX, textY, textHeight, resolvedText)
628
+
241
629
  elements.push(
242
- `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${entity.text}</text>`,
630
+ `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${escapeXmlText(resolvedText)}</text>`,
243
631
  )
244
632
  }
245
633
 
@@ -255,14 +643,15 @@ function renderAngularDimension(
255
643
  function renderDiameterDimension(
256
644
  entity: DimensionEntity,
257
645
  dimStyle?: DimStyleTable,
646
+ options?: ToSVGOptions,
647
+ viewport?: DimensionViewport,
258
648
  ): BoundsAndElement {
259
649
  const bbox = new Box2()
260
650
  const elements: string[] = []
261
651
  const markers: string[] = []
262
652
 
263
- // Get dimension style properties
264
- const arrowSize = dimStyle?.dimAsz ?? 2.5
265
- const textHeight = dimStyle?.dimTxt ?? 2.5
653
+ // Get dimension style properties (optionally auto-scaled)
654
+ const { arrowSize, textHeight } = getScaledDimensionSizes(dimStyle, options, viewport)
266
655
  const { dimLineColor, textColor, dimLineWeight } = getDimensionColors(dimStyle)
267
656
 
268
657
  // Extract geometry
@@ -277,22 +666,30 @@ function renderDiameterDimension(
277
666
  bbox.expandByPoint({ x: x2, y: y2 })
278
667
  bbox.expandByPoint({ x: textX, y: textY })
279
668
 
280
- // Create arrow markers
281
- const markerId = `dim-diameter-arrow-${Date.now()}`
282
- markers.push(createArrowMarker(markerId, arrowSize, dimLineColor))
669
+ const diameterLen = Math.hypot(x2 - x1, y2 - y1)
670
+ if (Number.isFinite(diameterLen) && diameterLen > 1e-6) {
671
+ // Create arrow markers
672
+ const markerId = `dim-diameter-arrow-${Date.now()}`
673
+ markers.push(createArrowMarker(markerId, arrowSize, dimLineColor, 'backward'))
283
674
 
284
- // Create diameter line with arrow at the end
285
- elements.push(
286
- `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${dimLineColor}" stroke-width="${dimLineWeight}" marker-end="url(#${markerId})" />`,
287
- )
675
+ // Create diameter line with arrow at the end
676
+ elements.push(
677
+ `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${dimLineColor}" stroke-width="${dimLineWeight}" marker-end="url(#${markerId})" />`,
678
+ )
679
+
680
+ expandBBoxForMarker(bbox, x2, y2, arrowSize)
681
+ }
288
682
 
289
683
  // Add dimension text with diameter symbol
290
- const diameterText = entity.text ? `⌀${entity.text}` : '⌀'
684
+ const resolvedText = resolveDimensionText(entity)
685
+ const diameterText = resolvedText ? `⌀${resolvedText}` : '⌀'
291
686
  const angle = Math.atan2(y2 - y1, x2 - x1)
292
687
  const textRotation = (angle * 180) / Math.PI
293
688
 
689
+ expandBBoxForText(bbox, textX, textY, textHeight, diameterText)
690
+
294
691
  elements.push(
295
- `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${diameterText}</text>`,
692
+ `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${escapeXmlText(diameterText)}</text>`,
296
693
  )
297
694
 
298
695
  return {
@@ -307,14 +704,15 @@ function renderDiameterDimension(
307
704
  function renderRadialDimension(
308
705
  entity: DimensionEntity,
309
706
  dimStyle?: DimStyleTable,
707
+ options?: ToSVGOptions,
708
+ viewport?: DimensionViewport,
310
709
  ): BoundsAndElement {
311
710
  const bbox = new Box2()
312
711
  const elements: string[] = []
313
712
  const markers: string[] = []
314
713
 
315
- // Get dimension style properties
316
- const arrowSize = dimStyle?.dimAsz ?? 2.5
317
- const textHeight = dimStyle?.dimTxt ?? 2.5
714
+ // Get dimension style properties (optionally auto-scaled)
715
+ const { arrowSize, textHeight } = getScaledDimensionSizes(dimStyle, options, viewport)
318
716
  const { dimLineColor, textColor, dimLineWeight } = getDimensionColors(dimStyle)
319
717
 
320
718
  // Extract geometry
@@ -329,22 +727,30 @@ function renderRadialDimension(
329
727
  bbox.expandByPoint({ x: x2, y: y2 })
330
728
  bbox.expandByPoint({ x: textX, y: textY })
331
729
 
332
- // Create arrow markers
333
- const markerId = `dim-radius-arrow-${Date.now()}`
334
- markers.push(createArrowMarker(markerId, arrowSize, dimLineColor))
730
+ const radiusLen = Math.hypot(x2 - x1, y2 - y1)
731
+ if (Number.isFinite(radiusLen) && radiusLen > 1e-6) {
732
+ // Create arrow markers
733
+ const markerId = `dim-radius-arrow-${Date.now()}`
734
+ markers.push(createArrowMarker(markerId, arrowSize, dimLineColor, 'backward'))
335
735
 
336
- // Create radius line with arrow at the end
337
- elements.push(
338
- `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${dimLineColor}" stroke-width="${dimLineWeight}" marker-end="url(#${markerId})" />`,
339
- )
736
+ // Create radius line with arrow at the end
737
+ elements.push(
738
+ `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${dimLineColor}" stroke-width="${dimLineWeight}" marker-end="url(#${markerId})" />`,
739
+ )
740
+
741
+ expandBBoxForMarker(bbox, x2, y2, arrowSize)
742
+ }
340
743
 
341
744
  // Add dimension text with radius symbol
342
- const radiusText = entity.text ? `R${entity.text}` : 'R'
745
+ const resolvedText = resolveDimensionText(entity)
746
+ const radiusText = resolvedText ? `R${resolvedText}` : 'R'
343
747
  const angle = Math.atan2(y2 - y1, x2 - x1)
344
748
  const textRotation = (angle * 180) / Math.PI
345
749
 
750
+ expandBBoxForText(bbox, textX, textY, textHeight, radiusText)
751
+
346
752
  elements.push(
347
- `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${radiusText}</text>`,
753
+ `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${escapeXmlText(radiusText)}</text>`,
348
754
  )
349
755
 
350
756
  return {
@@ -359,12 +765,14 @@ function renderRadialDimension(
359
765
  function renderOrdinateDimension(
360
766
  entity: DimensionEntity,
361
767
  dimStyle?: DimStyleTable,
768
+ options?: ToSVGOptions,
769
+ viewport?: DimensionViewport,
362
770
  ): BoundsAndElement {
363
771
  const bbox = new Box2()
364
772
  const elements: string[] = []
365
773
 
366
- // Get dimension style properties
367
- const textHeight = dimStyle?.dimTxt ?? 2.5
774
+ // Get dimension style properties (optionally auto-scaled)
775
+ const { textHeight } = getScaledDimensionSizes(dimStyle, options, viewport)
368
776
  const { dimLineColor, textColor, dimLineWeight } = getDimensionColors(dimStyle)
369
777
 
370
778
  // Extract geometry
@@ -385,12 +793,15 @@ function renderOrdinateDimension(
385
793
  )
386
794
 
387
795
  // Add dimension text
388
- if (entity.text) {
796
+ const resolvedText = resolveDimensionText(entity)
797
+ if (resolvedText) {
389
798
  const angle = Math.atan2(y2 - y1, x2 - x1)
390
799
  const textRotation = (angle * 180) / Math.PI
391
800
 
801
+ expandBBoxForText(bbox, textX, textY, textHeight, resolvedText)
802
+
392
803
  elements.push(
393
- `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${entity.text}</text>`,
804
+ `<text x="${textX}" y="${textY}" font-size="${textHeight}" fill="${textColor}" text-anchor="middle" transform="rotate(${-textRotation} ${textX} ${textY}) scale(1,-1) translate(0 ${-2 * textY})">${escapeXmlText(resolvedText)}</text>`,
394
805
  )
395
806
  }
396
807
 
@@ -413,9 +824,10 @@ function renderFallbackDimension(entity: DimensionEntity): BoundsAndElement {
413
824
  const textY = entity.textMidpoint.y ?? 0
414
825
  bbox.expandByPoint({ x: textX, y: textY })
415
826
 
416
- if (entity.text) {
827
+ const resolvedText = resolveDimensionText(entity)
828
+ if (resolvedText) {
417
829
  elements.push(
418
- `<text x="${textX}" y="${textY}" font-size="2.5" text-anchor="middle" transform="scale(1,-1) translate(0 ${-2 * textY})">${entity.text}</text>`,
830
+ `<text x="${textX}" y="${textY}" font-size="2.5" text-anchor="middle" transform="scale(1,-1) translate(0 ${-2 * textY})">${escapeXmlText(resolvedText)}</text>`,
419
831
  )
420
832
  }
421
833
  }