circuitjson-toolkit 1.0.16 → 1.1.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 (155) hide show
  1. package/AGENTS.md +15 -0
  2. package/NOTICE.md +31 -0
  3. package/README.md +267 -107
  4. package/docs/api.md +501 -96
  5. package/docs/capabilities.md +70 -0
  6. package/docs/migration/behaviors.md +45 -0
  7. package/docs/migration/parser.md +60 -0
  8. package/docs/migration/renderers.md +515 -0
  9. package/docs/migration/root.md +740 -0
  10. package/docs/migration.md +120 -0
  11. package/docs/model-format.md +175 -57
  12. package/docs/provenance.md +206 -0
  13. package/docs/release-notes-v1.1.0.md +154 -0
  14. package/docs/testing.md +117 -7
  15. package/package.json +31 -5
  16. package/spec/api-baseline-v1.0.17.json +1 -0
  17. package/spec/baseline-provenance-v1.0.17.json +7 -0
  18. package/spec/circuitjson-schema-snapshot.json +321 -0
  19. package/spec/circuitjson-schema-source.json +28 -0
  20. package/spec/feature-preservation.json +1 -0
  21. package/spec/library-scope.md +27 -20
  22. package/src/capabilities.mjs +1 -0
  23. package/src/core/ArchiveEntryPath.mjs +93 -0
  24. package/src/core/ArchiveLimits.mjs +31 -0
  25. package/src/core/ArchiveLimitsValidator.mjs +107 -0
  26. package/src/core/AsyncInputOwnership.mjs +56 -0
  27. package/src/core/AttachedValueLimits.mjs +67 -0
  28. package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
  29. package/src/core/CircuitJsonDocument.mjs +19 -61
  30. package/src/core/CircuitJsonElementTypes.mjs +10 -0
  31. package/src/core/CircuitJsonElementValidator.mjs +98 -847
  32. package/src/core/CircuitJsonIndexer.mjs +274 -194
  33. package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
  34. package/src/core/CircuitJsonParser.mjs +75 -13
  35. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
  36. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
  37. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
  38. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
  39. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
  40. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
  41. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
  42. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
  43. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
  44. package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
  45. package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
  46. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
  47. package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
  48. package/src/core/CircuitJsonUnitParsers.mjs +101 -0
  49. package/src/core/CircuitJsonUnits.mjs +13 -87
  50. package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
  51. package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
  52. package/src/core/CircuitJsonValidationUnits.mjs +6 -0
  53. package/src/core/ManufacturingService.mjs +323 -0
  54. package/src/core/Parser.mjs +343 -0
  55. package/src/core/ParserOptions.mjs +333 -0
  56. package/src/core/PcbBoundsSelectionModel.mjs +55 -19
  57. package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
  58. package/src/core/PcbInteractionIndex.mjs +368 -0
  59. package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
  60. package/src/core/ProjectAsyncInputOwner.mjs +70 -0
  61. package/src/core/ProjectLoader.mjs +975 -0
  62. package/src/core/SimulationService.mjs +790 -0
  63. package/src/core/ToolkitCapabilities.mjs +130 -0
  64. package/src/core/ZipArchiveInspector.mjs +649 -0
  65. package/src/core/context/BinaryDataSnapshot.mjs +217 -0
  66. package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
  67. package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
  68. package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
  69. package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
  70. package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
  71. package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
  72. package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
  73. package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
  74. package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
  75. package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
  76. package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
  77. package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
  78. package/src/core/context/PcbSpatialIndex.mjs +701 -0
  79. package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
  80. package/src/core/context/StructuredDataSnapshot.mjs +683 -0
  81. package/src/core/contracts/DocumentResult.mjs +198 -0
  82. package/src/core/contracts/ProjectResult.mjs +96 -0
  83. package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
  84. package/src/core/contracts/ToolkitAsset.mjs +493 -0
  85. package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
  86. package/src/core/contracts/ToolkitError.mjs +176 -0
  87. package/src/core/contracts/ToolkitProgress.mjs +89 -0
  88. package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
  89. package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
  90. package/src/core/query/CircuitTraversal.mjs +343 -0
  91. package/src/core/query/ComponentGrouping.mjs +275 -0
  92. package/src/core/query/QueryNetlistBuilder.mjs +306 -0
  93. package/src/core/query/QueryService.mjs +435 -0
  94. package/src/core/query/RegexPattern.mjs +75 -0
  95. package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
  96. package/src/core/rendering/CanonicalBomRows.mjs +92 -0
  97. package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
  98. package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
  99. package/src/core/rendering/PcbRenderPlan.mjs +429 -0
  100. package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
  101. package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
  102. package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
  103. package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
  104. package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
  105. package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
  106. package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
  107. package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
  108. package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
  109. package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
  110. package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
  111. package/src/core/scene3d/Scene3dOptions.mjs +363 -0
  112. package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
  113. package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
  114. package/src/core/worker/ParserWorkerClient.mjs +997 -0
  115. package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
  116. package/src/core/worker/WorkerRequestData.mjs +623 -0
  117. package/src/core/worker/WorkerResponseData.mjs +905 -0
  118. package/src/extensions.mjs +37 -0
  119. package/src/index.mjs +14 -9
  120. package/src/interaction.mjs +2 -0
  121. package/src/manufacturing.mjs +1 -0
  122. package/src/parser.mjs +12 -2
  123. package/src/project.mjs +5 -0
  124. package/src/query.mjs +1 -0
  125. package/src/renderers.mjs +3 -29
  126. package/src/scene3d.mjs +3 -0
  127. package/src/simulation.mjs +1 -0
  128. package/src/styles/renderers.css +24 -0
  129. package/src/testing/ToolkitContractFixtures.mjs +124 -0
  130. package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
  131. package/src/testing/runToolkitContract.mjs +705 -0
  132. package/src/testing.mjs +3 -0
  133. package/src/ui/BomTableRenderer.mjs +304 -0
  134. package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
  135. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
  136. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
  137. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
  138. package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
  139. package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
  140. package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
  141. package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
  142. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
  143. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
  144. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
  145. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
  146. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
  147. package/src/ui/PcbSvgRenderer.mjs +41 -0
  148. package/src/ui/SafeSvgPaint.mjs +26 -0
  149. package/src/ui/SafeXmlText.mjs +60 -0
  150. package/src/ui/SchematicGeometryBounds.mjs +540 -0
  151. package/src/ui/SchematicSvgRenderer.mjs +110 -0
  152. package/src/ui/SchematicTableGeometry.mjs +319 -0
  153. package/src/ui/SchematicTextAnchor.mjs +55 -0
  154. package/src/ui/SchematicTextBounds.mjs +98 -0
  155. package/src/workers/parser.worker.mjs +59 -0
@@ -1,9 +1,17 @@
1
1
  import { CircuitJsonIndexer } from '../core/CircuitJsonIndexer.mjs'
2
2
  import { CircuitJsonUnits } from '../core/CircuitJsonUnits.mjs'
3
+ import { CircuitJsonLegacyNormalizer } from '../core/context/CircuitJsonLegacyNormalizer.mjs'
3
4
  import { CircuitJsonSchematicSvgArcPath } from './CircuitJsonSchematicSvgArcPath.mjs'
5
+ import { CircuitJsonSchematicDebugRenderer } from './CircuitJsonSchematicDebugRenderer.mjs'
6
+ import { CircuitJsonSchematicLineRenderer } from './CircuitJsonSchematicLineRenderer.mjs'
7
+ import { CircuitJsonSchematicImageSvgRenderer } from './CircuitJsonSchematicImageSvgRenderer.mjs'
8
+ import { CircuitJsonSchematicSheetSymbolSvgRenderer } from './CircuitJsonSchematicSheetSymbolSvgRenderer.mjs'
4
9
  import { CircuitJsonSchematicSvgPortMetadata } from './CircuitJsonSchematicSvgPortMetadata.mjs'
5
10
  import { CircuitJsonSchematicSvgPrimitiveAttributes } from './CircuitJsonSchematicSvgPrimitiveAttributes.mjs'
6
11
  import { CircuitJsonSchematicTableSvgRenderer } from './CircuitJsonSchematicTableSvgRenderer.mjs'
12
+ import { SchematicGeometryBounds } from './SchematicGeometryBounds.mjs'
13
+ import { SafeXmlText } from './SafeXmlText.mjs'
14
+ import { SchematicTextAnchor } from './SchematicTextAnchor.mjs'
7
15
 
8
16
  /**
9
17
  * Renders standards-shaped schematic element arrays into app-compatible SVG.
@@ -12,26 +20,53 @@ export class CircuitJsonSchematicSvgRenderer {
12
20
  /**
13
21
  * Renders one schematic document into SVG markup.
14
22
  * @param {object | object[]} documentModel Parsed document model.
23
+ * @param {{ assets?: object[] }} [options] Render resources.
15
24
  * @returns {string}
16
25
  */
17
- static render(documentModel) {
18
- const elements =
19
- CircuitJsonSchematicSvgRenderer.#elements(documentModel)
20
- const index = CircuitJsonIndexer.index(elements)
21
- const bounds = CircuitJsonSchematicSvgRenderer.#bounds(index)
26
+ static render(documentModel, options = {}) {
27
+ const prepared = documentModel?.elementsByType instanceof Map
28
+ const index = prepared
29
+ ? documentModel
30
+ : CircuitJsonIndexer.index(
31
+ CircuitJsonLegacyNormalizer.normalize(
32
+ CircuitJsonSchematicSvgRenderer.#elements(documentModel)
33
+ )
34
+ )
35
+ const sourcePorts = CircuitJsonSchematicSvgPortMetadata.sourcePorts(
36
+ CircuitJsonSchematicSvgRenderer.#all(index, 'source_port')
37
+ )
38
+ const portHintCache = new WeakMap()
39
+ const bounds = prepared
40
+ ? SchematicGeometryBounds.resolve(index, sourcePorts, portHintCache)
41
+ : SchematicGeometryBounds.legacy(index)
22
42
  const viewBox = CircuitJsonSchematicSvgRenderer.#viewBox(bounds)
23
-
24
43
  return (
25
- '<svg class="schematic-svg schematic-svg--circuitjson" xmlns="http://www.w3.org/2000/svg" role="img" viewBox="' +
44
+ '<svg class="schematic-svg schematic-svg--circuitjson" xmlns="http://www.w3.org/2000/svg" role="img"' +
45
+ (prepared ? ' font-size="1"' : '') +
46
+ ' viewBox="' +
26
47
  CircuitJsonSchematicSvgRenderer.#formatViewBox(viewBox) +
27
48
  '">' +
28
49
  CircuitJsonSchematicSvgRenderer.#renderSheet(bounds) +
50
+ CircuitJsonSchematicImageSvgRenderer.render(
51
+ CircuitJsonSchematicSvgRenderer.#all(index, 'schematic_image'),
52
+ options.assets || documentModel?.assets || []
53
+ ) +
29
54
  CircuitJsonSchematicSvgRenderer.#renderComponents(index) +
30
55
  CircuitJsonSchematicSvgRenderer.#renderSymbols(index) +
31
56
  CircuitJsonSchematicSvgRenderer.#renderGroups(index) +
57
+ CircuitJsonSchematicSheetSymbolSvgRenderer.render(
58
+ CircuitJsonSchematicSvgRenderer.#all(
59
+ index,
60
+ 'schematic_sheet_symbol'
61
+ )
62
+ ) +
32
63
  CircuitJsonSchematicSvgRenderer.#renderLines(index) +
33
64
  CircuitJsonSchematicSvgRenderer.#renderShapes(index) +
34
- CircuitJsonSchematicSvgRenderer.#renderPorts(index) +
65
+ CircuitJsonSchematicSvgRenderer.#renderPorts(
66
+ index,
67
+ sourcePorts,
68
+ portHintCache
69
+ ) +
35
70
  CircuitJsonSchematicSvgRenderer.#renderTables(index) +
36
71
  CircuitJsonSchematicSvgRenderer.#renderTexts(index) +
37
72
  CircuitJsonSchematicSvgRenderer.#renderProbes(index) +
@@ -75,11 +110,7 @@ export class CircuitJsonSchematicSvgRenderer {
75
110
  )
76
111
  }
77
112
 
78
- /**
79
- * Renders schematic component boxes.
80
- * @param {{ elementsByType: Map<string, object[]> }} index Element index.
81
- * @returns {string}
82
- */
113
+ /** @param {{ elementsByType: Map<string, object[]> }} index Element index. @returns {string} Schematic component markup. */
83
114
  static #renderComponents(index) {
84
115
  const sourceNames = CircuitJsonSchematicSvgRenderer.#sourceNames(index)
85
116
  const components = CircuitJsonSchematicSvgRenderer.#all(
@@ -101,6 +132,16 @@ export class CircuitJsonSchematicSvgRenderer {
101
132
  String(component.name || 'U' + (componentIndex + 1))
102
133
  const x = center.x - size.width / 2
103
134
  const y = center.y - size.height / 2
135
+ const label =
136
+ component.show_label === false
137
+ ? ''
138
+ : '<text class="schematic-component__label" x="' +
139
+ CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
140
+ '" y="' +
141
+ CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
142
+ '" text-anchor="middle" dominant-baseline="central">' +
143
+ CircuitJsonSchematicSvgRenderer.#escapeHtml(key) +
144
+ '</text>'
104
145
 
105
146
  return (
106
147
  '<g class="schematic-component" data-component-key="' +
@@ -118,13 +159,9 @@ export class CircuitJsonSchematicSvgRenderer {
118
159
  CircuitJsonSchematicSvgRenderer.#formatNumber(size.width) +
119
160
  '" height="' +
120
161
  CircuitJsonSchematicSvgRenderer.#formatNumber(size.height) +
121
- '"></rect><text class="schematic-component__label" x="' +
122
- CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
123
- '" y="' +
124
- CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
125
- '" text-anchor="middle" dominant-baseline="central">' +
126
- CircuitJsonSchematicSvgRenderer.#escapeHtml(key) +
127
- '</text></g>'
162
+ '"></rect>' +
163
+ label +
164
+ '</g>'
128
165
  )
129
166
  })
130
167
 
@@ -256,8 +293,8 @@ export class CircuitJsonSchematicSvgRenderer {
256
293
  .flatMap((type) =>
257
294
  CircuitJsonSchematicSvgRenderer.#all(index, type)
258
295
  )
259
- .map((element) =>
260
- CircuitJsonSchematicSvgRenderer.#lineElement(element)
296
+ .flatMap((element) =>
297
+ CircuitJsonSchematicLineRenderer.render(element)
261
298
  )
262
299
  .filter(Boolean)
263
300
  return lines.length
@@ -265,38 +302,6 @@ export class CircuitJsonSchematicSvgRenderer {
265
302
  : ''
266
303
  }
267
304
 
268
- /**
269
- * Renders one schematic line.
270
- * @param {object} element Source element.
271
- * @returns {string}
272
- */
273
- static #lineElement(element) {
274
- const start = CircuitJsonSchematicSvgRenderer.#point({
275
- x: element.x1 ?? element.start?.x,
276
- y: element.y1 ?? element.start?.y
277
- })
278
- const end = CircuitJsonSchematicSvgRenderer.#point({
279
- x: element.x2 ?? element.end?.x,
280
- y: element.y2 ?? element.end?.y
281
- })
282
- if (!start || !end) return ''
283
- return (
284
- '<line class="schematic-wire" x1="' +
285
- CircuitJsonSchematicSvgRenderer.#formatNumber(start.x) +
286
- '" y1="' +
287
- CircuitJsonSchematicSvgRenderer.#formatNumber(start.y) +
288
- '" x2="' +
289
- CircuitJsonSchematicSvgRenderer.#formatNumber(end.x) +
290
- '" y2="' +
291
- CircuitJsonSchematicSvgRenderer.#formatNumber(end.y) +
292
- '"' +
293
- CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element, {
294
- fill: false
295
- }) +
296
- '></line>'
297
- )
298
- }
299
-
300
305
  /**
301
306
  * Renders schematic rectangle and circle shapes.
302
307
  * @param {{ elementsByType: Map<string, object[]> }} index Element index.
@@ -418,11 +423,13 @@ export class CircuitJsonSchematicSvgRenderer {
418
423
  const radius = CircuitJsonUnits.optionalLength(element.radius)
419
424
  if (!center || radius === null) return ''
420
425
  const startAngle = CircuitJsonUnits.angle(
421
- element.start_angle ?? element.startAngle,
426
+ element.start_angle_degrees ??
427
+ element.start_angle ??
428
+ element.startAngle,
422
429
  0
423
430
  )
424
431
  const endAngle = CircuitJsonUnits.angle(
425
- element.end_angle ?? element.endAngle,
432
+ element.end_angle_degrees ?? element.end_angle ?? element.endAngle,
426
433
  360
427
434
  )
428
435
  const start = CircuitJsonSchematicSvgRenderer.#polarPoint(
@@ -435,23 +442,56 @@ export class CircuitJsonSchematicSvgRenderer {
435
442
  radius,
436
443
  endAngle
437
444
  )
438
- const largeArc = Math.abs(endAngle - startAngle) > 180 ? 1 : 0
439
-
440
- return (
441
- '<path class="schematic-shape schematic-shape--arc" d="M ' +
442
- CircuitJsonSchematicSvgRenderer.#formatNumber(start.x) +
443
- ' ' +
444
- CircuitJsonSchematicSvgRenderer.#formatNumber(start.y) +
445
+ const standardAngles =
446
+ element.start_angle_degrees !== undefined ||
447
+ element.end_angle_degrees !== undefined
448
+ const sweep =
449
+ element.direction === 'clockwise'
450
+ ? 1
451
+ : element.direction === 'counterclockwise' || standardAngles
452
+ ? 0
453
+ : 1
454
+ const span = CircuitJsonSchematicSvgRenderer.#directedAngleSpan(
455
+ startAngle,
456
+ endAngle,
457
+ sweep
458
+ )
459
+ const largeArc = span > 180 ? 1 : 0
460
+ const midpoint = CircuitJsonSchematicSvgRenderer.#polarPoint(
461
+ center,
462
+ radius,
463
+ startAngle + (sweep ? 180 : -180)
464
+ )
465
+ /**
466
+ * Builds one SVG arc command for the resolved circle.
467
+ * @param {{ x: number, y: number }} point Command endpoint.
468
+ * @param {boolean} [fullTurn] Whether this is half of a full turn.
469
+ * @returns {string} SVG path command.
470
+ */
471
+ const arcEnd = (point, fullTurn = false) =>
445
472
  ' A ' +
446
473
  CircuitJsonSchematicSvgRenderer.#formatNumber(radius) +
447
474
  ' ' +
448
475
  CircuitJsonSchematicSvgRenderer.#formatNumber(radius) +
449
476
  ' 0 ' +
450
- largeArc +
451
- ' 1 ' +
452
- CircuitJsonSchematicSvgRenderer.#formatNumber(end.x) +
477
+ (fullTurn ? 0 : largeArc) +
453
478
  ' ' +
454
- CircuitJsonSchematicSvgRenderer.#formatNumber(end.y) +
479
+ sweep +
480
+ ' ' +
481
+ CircuitJsonSchematicSvgRenderer.#formatNumber(point.x) +
482
+ ' ' +
483
+ CircuitJsonSchematicSvgRenderer.#formatNumber(point.y)
484
+ const path =
485
+ span === 360
486
+ ? arcEnd(midpoint, true) + arcEnd(start, true)
487
+ : arcEnd(end)
488
+
489
+ return (
490
+ '<path class="schematic-shape schematic-shape--arc" d="M ' +
491
+ CircuitJsonSchematicSvgRenderer.#formatNumber(start.x) +
492
+ ' ' +
493
+ CircuitJsonSchematicSvgRenderer.#formatNumber(start.y) +
494
+ path +
455
495
  '"' +
456
496
  CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element, {
457
497
  fill: false
@@ -487,12 +527,11 @@ export class CircuitJsonSchematicSvgRenderer {
487
527
  /**
488
528
  * Renders schematic ports.
489
529
  * @param {{ elementsByType: Map<string, object[]> }} index Element index.
530
+ * @param {Map<string, object>} sourcePorts Source-port lookup.
531
+ * @param {WeakMap<object, string>} portHintCache Render-scoped hint cache.
490
532
  * @returns {string}
491
533
  */
492
- static #renderPorts(index) {
493
- const sourcePorts = CircuitJsonSchematicSvgPortMetadata.sourcePorts(
494
- CircuitJsonSchematicSvgRenderer.#all(index, 'source_port')
495
- )
534
+ static #renderPorts(index, sourcePorts, portHintCache) {
496
535
  const ports = CircuitJsonSchematicSvgRenderer.#all(
497
536
  index,
498
537
  'schematic_port'
@@ -500,7 +539,8 @@ export class CircuitJsonSchematicSvgRenderer {
500
539
  .map((element) =>
501
540
  CircuitJsonSchematicSvgRenderer.#portElement(
502
541
  element,
503
- sourcePorts
542
+ sourcePorts,
543
+ portHintCache
504
544
  )
505
545
  )
506
546
  .filter(Boolean)
@@ -513,16 +553,18 @@ export class CircuitJsonSchematicSvgRenderer {
513
553
  * Renders one schematic port marker.
514
554
  * @param {object} element Port element.
515
555
  * @param {Map<string, object>} sourcePorts Source port lookup.
556
+ * @param {WeakMap<object, string>} portHintCache Render-scoped hint cache.
516
557
  * @returns {string}
517
558
  */
518
- static #portElement(element, sourcePorts) {
559
+ static #portElement(element, sourcePorts, portHintCache) {
519
560
  const center = CircuitJsonSchematicSvgRenderer.#center(element)
520
561
  if (!center) return ''
521
562
  const sourcePortId = String(element.source_port_id || '').trim()
522
563
  const sourcePort = sourcePorts.get(sourcePortId) || null
523
564
  const label = CircuitJsonSchematicSvgPortMetadata.label(
524
565
  element,
525
- sourcePort
566
+ sourcePort,
567
+ portHintCache
526
568
  )
527
569
 
528
570
  return (
@@ -592,6 +634,7 @@ export class CircuitJsonSchematicSvgRenderer {
592
634
  element.ccw_rotation ?? element.rotation,
593
635
  0
594
636
  )
637
+ const anchor = element.anchor || element.anchor_side
595
638
  const attributes = [
596
639
  'class="schematic-text"',
597
640
  'x="' +
@@ -607,6 +650,15 @@ export class CircuitJsonSchematicSvgRenderer {
607
650
  '"'
608
651
  )
609
652
  }
653
+ if (anchor) {
654
+ const alignment = SchematicTextAnchor.resolve(anchor)
655
+ attributes.push('text-anchor="' + alignment.textAnchor + '"')
656
+ if (alignment.baseline) {
657
+ attributes.push(
658
+ 'dominant-baseline="' + alignment.baseline + '"'
659
+ )
660
+ }
661
+ }
610
662
  if (rotation) {
611
663
  attributes.push(
612
664
  'transform="rotate(' +
@@ -655,7 +707,9 @@ export class CircuitJsonSchematicSvgRenderer {
655
707
  * @returns {string}
656
708
  */
657
709
  static #probeElement(element) {
658
- const center = CircuitJsonSchematicSvgRenderer.#center(element)
710
+ const center =
711
+ CircuitJsonSchematicSvgRenderer.#point(element.position) ||
712
+ CircuitJsonSchematicSvgRenderer.#center(element)
659
713
  if (!center) return ''
660
714
  const label = String(element.name || element.label || '').trim()
661
715
  return (
@@ -687,45 +741,13 @@ export class CircuitJsonSchematicSvgRenderer {
687
741
  index,
688
742
  'schematic_debug_object'
689
743
  )
690
- .map((element) =>
691
- CircuitJsonSchematicSvgRenderer.#debugObjectElement(element)
692
- )
744
+ .map((element) => CircuitJsonSchematicDebugRenderer.render(element))
693
745
  .filter(Boolean)
694
746
  return objects.length
695
747
  ? '<g class="schematic-debug-objects">' + objects.join('') + '</g>'
696
748
  : ''
697
749
  }
698
750
 
699
- /**
700
- * Renders one schematic debug object.
701
- * @param {object} element Debug element.
702
- * @returns {string}
703
- */
704
- static #debugObjectElement(element) {
705
- const rect = CircuitJsonSchematicSvgRenderer.#rectAttributes(element)
706
- const center = CircuitJsonSchematicSvgRenderer.#center(element)
707
- if (!rect || !center) return ''
708
- const message = String(element.message || '').trim()
709
-
710
- return (
711
- '<g class="schematic-debug-object" data-schematic-debug-object-id="' +
712
- CircuitJsonSchematicSvgRenderer.#escapeHtml(
713
- element.schematic_debug_object_id || ''
714
- ) +
715
- '"><title>' +
716
- CircuitJsonSchematicSvgRenderer.#escapeHtml(message) +
717
- '</title><rect ' +
718
- rect +
719
- '></rect><text x="' +
720
- CircuitJsonSchematicSvgRenderer.#formatNumber(center.x) +
721
- '" y="' +
722
- CircuitJsonSchematicSvgRenderer.#formatNumber(center.y) +
723
- '" text-anchor="middle" dominant-baseline="central">' +
724
- CircuitJsonSchematicSvgRenderer.#escapeHtml(message) +
725
- '</text></g>'
726
- )
727
- }
728
-
729
751
  /**
730
752
  * Renders schematic warning and error markers.
731
753
  * @param {{ elements: object[] }} index Element index.
@@ -733,9 +755,13 @@ export class CircuitJsonSchematicSvgRenderer {
733
755
  */
734
756
  static #renderDiagnostics(index) {
735
757
  const diagnostics = (index.elements || [])
736
- .filter((element) =>
737
- /(?:error|warning)/iu.test(String(element?.type || ''))
738
- )
758
+ .filter((element) => {
759
+ const type = String(element?.type || '')
760
+ return (
761
+ type.startsWith('schematic_') &&
762
+ /(?:error|warning)/iu.test(type)
763
+ )
764
+ })
739
765
  .map((element) =>
740
766
  CircuitJsonSchematicSvgRenderer.#diagnosticElement(element)
741
767
  )
@@ -796,38 +822,6 @@ export class CircuitJsonSchematicSvgRenderer {
796
822
  )
797
823
  }
798
824
 
799
- /**
800
- * Resolves schematic bounds.
801
- * @param {{ elements: object[], elementsByType: Map<string, object[]> }} index Element index.
802
- * @returns {{ minX: number, minY: number, maxX: number, maxY: number, width: number, height: number }}
803
- */
804
- static #bounds(index) {
805
- const sheet = CircuitJsonSchematicSvgRenderer.#all(
806
- index,
807
- 'schematic_sheet'
808
- )[0]
809
- const width = CircuitJsonUnits.optionalLength(sheet?.width)
810
- const height = CircuitJsonUnits.optionalLength(sheet?.height)
811
- if (width !== null && height !== null) {
812
- return {
813
- minX: 0,
814
- minY: 0,
815
- maxX: width,
816
- maxY: height,
817
- width,
818
- height
819
- }
820
- }
821
- return {
822
- minX: -10,
823
- minY: -10,
824
- maxX: 10,
825
- maxY: 10,
826
- width: 20,
827
- height: 20
828
- }
829
- }
830
-
831
825
  /**
832
826
  * Builds a padded SVG viewBox.
833
827
  * @param {object} bounds Bounds.
@@ -939,6 +933,19 @@ export class CircuitJsonSchematicSvgRenderer {
939
933
  }
940
934
  }
941
935
 
936
+ /**
937
+ * Resolves the positive span for an SVG sweep direction.
938
+ * @param {number} start Start angle in degrees.
939
+ * @param {number} end End angle in degrees.
940
+ * @param {0 | 1} sweep SVG sweep flag.
941
+ * @returns {number} Directed span in degrees.
942
+ */
943
+ static #directedAngleSpan(start, end, sweep) {
944
+ const difference = sweep ? end - start : start - end
945
+ const span = ((difference % 360) + 360) % 360
946
+ return span === 0 && start !== end ? 360 : span
947
+ }
948
+
942
949
  /**
943
950
  * Builds a polyline point string.
944
951
  * @param {{ x: number, y: number }[]} points Points.
@@ -985,10 +992,6 @@ export class CircuitJsonSchematicSvgRenderer {
985
992
  * @returns {string}
986
993
  */
987
994
  static #escapeHtml(value) {
988
- return String(value ?? '')
989
- .replaceAll('&', '&amp;')
990
- .replaceAll('<', '&lt;')
991
- .replaceAll('>', '&gt;')
992
- .replaceAll('"', '&quot;')
995
+ return SafeXmlText.escape(value)
993
996
  }
994
997
  }