circuitjson-toolkit 1.0.3 → 1.0.16
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.
- package/AGENTS.md +5 -3
- package/README.md +21 -2
- package/docs/api.md +50 -4
- package/docs/model-format.md +21 -3
- package/package.json +3 -2
- package/spec/library-scope.md +4 -1
- package/src/core/CircuitJsonBomBuilder.mjs +143 -0
- package/src/core/CircuitJsonDocument.mjs +46 -13
- package/src/core/CircuitJsonElementValidator.mjs +990 -0
- package/src/core/CircuitJsonIndexer.mjs +773 -4
- package/src/core/CircuitJsonManufacturingBuilder.mjs +898 -0
- package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
- package/src/core/CircuitJsonParser.mjs +22 -6
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +329 -0
- package/src/core/CircuitJsonPcbCopperGeometry.mjs +503 -0
- package/src/core/CircuitJsonPcbDrawingStyle.mjs +88 -0
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +172 -0
- package/src/core/CircuitJsonPcbNetMetadata.mjs +247 -0
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +70 -0
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +992 -0
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +872 -0
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +233 -0
- package/src/core/CircuitJsonPcbPrimitiveGeometry.mjs +142 -0
- package/src/core/CircuitJsonPcbPrimitiveGroups.mjs +305 -0
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +65 -0
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +895 -0
- package/src/core/CircuitJsonPcbTraceLengthModel.mjs +257 -0
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +683 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +233 -0
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +481 -0
- package/src/core/CircuitJsonUnits.mjs +133 -8
- package/src/core/PcbBoundsSelectionModel.mjs +250 -0
- package/src/core/PcbCandidateSelectionModel.mjs +77 -0
- package/src/core/PcbDiagnosticFocusModel.mjs +423 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +560 -0
- package/src/core/SelectedPartCircuitJsonExportAdapter.mjs +335 -0
- package/src/core/spice/SpiceCompatibilityPreprocessor.mjs +139 -0
- package/src/core/spice/SpiceDirectiveParser.mjs +231 -0
- package/src/core/spice/SpiceFallbackSimulationEngine.mjs +168 -0
- package/src/core/spice/SpiceSimulationDiagnostics.mjs +234 -0
- package/src/core/spice/SpiceSimulationGraphBuilder.mjs +421 -0
- package/src/core/spice/SpiceSimulationGraphSummary.mjs +90 -0
- package/src/core/spice/SpiceSimulationService.mjs +92 -0
- package/src/core/spice/SpiceTimeSeriesNormalizer.mjs +132 -0
- package/src/index.mjs +8 -0
- package/src/renderers.mjs +29 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +128 -0
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +964 -0
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +168 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +138 -0
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +114 -0
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +130 -0
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +994 -0
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +439 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { CircuitJsonUnits } from '../core/CircuitJsonUnits.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders schematic table frames and cells from element-array metadata.
|
|
5
|
+
*/
|
|
6
|
+
export class CircuitJsonSchematicTableSvgRenderer {
|
|
7
|
+
/**
|
|
8
|
+
* Renders all schematic table markup.
|
|
9
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
10
|
+
* @param {{ escapeHtml: (value: unknown) => string, formatNumber: (value: number) => string }} formatters Formatting helpers.
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
static render(index, formatters) {
|
|
14
|
+
const tables = CircuitJsonSchematicTableSvgRenderer.#tableModels(index)
|
|
15
|
+
const tableMarkup = [...tables.values()]
|
|
16
|
+
.map((table) =>
|
|
17
|
+
CircuitJsonSchematicTableSvgRenderer.#tableElement(
|
|
18
|
+
table,
|
|
19
|
+
formatters
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
const cellMarkup = CircuitJsonSchematicTableSvgRenderer.#all(
|
|
24
|
+
index,
|
|
25
|
+
'schematic_table_cell'
|
|
26
|
+
)
|
|
27
|
+
.map((cell) =>
|
|
28
|
+
CircuitJsonSchematicTableSvgRenderer.#tableCellElement(
|
|
29
|
+
cell,
|
|
30
|
+
tables.get(String(cell.schematic_table_id || '').trim()),
|
|
31
|
+
formatters
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
const markup = [...tableMarkup, ...cellMarkup]
|
|
36
|
+
return markup.length
|
|
37
|
+
? '<g class="schematic-tables">' + markup.join('') + '</g>'
|
|
38
|
+
: ''
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Builds table models keyed by table id.
|
|
43
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
44
|
+
* @returns {Map<string, object>}
|
|
45
|
+
*/
|
|
46
|
+
static #tableModels(index) {
|
|
47
|
+
const tables = new Map()
|
|
48
|
+
for (const table of CircuitJsonSchematicTableSvgRenderer.#all(
|
|
49
|
+
index,
|
|
50
|
+
'schematic_table'
|
|
51
|
+
)) {
|
|
52
|
+
const model =
|
|
53
|
+
CircuitJsonSchematicTableSvgRenderer.#tableModel(table)
|
|
54
|
+
if (model) tables.set(model.id, model)
|
|
55
|
+
}
|
|
56
|
+
return tables
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Builds one schematic table model.
|
|
61
|
+
* @param {object} element Table element.
|
|
62
|
+
* @returns {object | null}
|
|
63
|
+
*/
|
|
64
|
+
static #tableModel(element) {
|
|
65
|
+
const id = String(element.schematic_table_id || '').trim()
|
|
66
|
+
const columns = CircuitJsonSchematicTableSvgRenderer.#lengths(
|
|
67
|
+
element.column_widths || element.columnWidths || element.columns
|
|
68
|
+
)
|
|
69
|
+
const rows = CircuitJsonSchematicTableSvgRenderer.#lengths(
|
|
70
|
+
element.row_heights || element.rowHeights || element.rows
|
|
71
|
+
)
|
|
72
|
+
const gridWidth = CircuitJsonSchematicTableSvgRenderer.#sum(columns)
|
|
73
|
+
const gridHeight = CircuitJsonSchematicTableSvgRenderer.#sum(rows)
|
|
74
|
+
const rect = CircuitJsonSchematicTableSvgRenderer.#rect(
|
|
75
|
+
element,
|
|
76
|
+
gridWidth,
|
|
77
|
+
gridHeight
|
|
78
|
+
)
|
|
79
|
+
if (!id || !rect) return null
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
id,
|
|
83
|
+
element,
|
|
84
|
+
...rect,
|
|
85
|
+
columns,
|
|
86
|
+
rows,
|
|
87
|
+
columnOffsets:
|
|
88
|
+
CircuitJsonSchematicTableSvgRenderer.#offsets(columns),
|
|
89
|
+
rowOffsets: CircuitJsonSchematicTableSvgRenderer.#offsets(rows),
|
|
90
|
+
padding: CircuitJsonUnits.length(element.cell_padding, 0),
|
|
91
|
+
borderWidth:
|
|
92
|
+
CircuitJsonUnits.optionalLength(element.border_width) ??
|
|
93
|
+
CircuitJsonUnits.optionalLength(element.borderWidth)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Renders one table frame.
|
|
99
|
+
* @param {object} table Table model.
|
|
100
|
+
* @param {{ escapeHtml: (value: unknown) => string, formatNumber: (value: number) => string }} formatters Formatting helpers.
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
static #tableElement(table, formatters) {
|
|
104
|
+
return (
|
|
105
|
+
'<rect class="schematic-table" data-schematic-table-id="' +
|
|
106
|
+
formatters.escapeHtml(table.id) +
|
|
107
|
+
'" ' +
|
|
108
|
+
CircuitJsonSchematicTableSvgRenderer.#rectAttributes(
|
|
109
|
+
table,
|
|
110
|
+
table.borderWidth,
|
|
111
|
+
formatters
|
|
112
|
+
) +
|
|
113
|
+
'></rect>'
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Renders one table cell.
|
|
119
|
+
* @param {object} element Cell element.
|
|
120
|
+
* @param {object | undefined} table Parent table model.
|
|
121
|
+
* @param {{ escapeHtml: (value: unknown) => string, formatNumber: (value: number) => string }} formatters Formatting helpers.
|
|
122
|
+
* @returns {string}
|
|
123
|
+
*/
|
|
124
|
+
static #tableCellElement(element, table, formatters) {
|
|
125
|
+
const rect = CircuitJsonSchematicTableSvgRenderer.#cellRect(
|
|
126
|
+
element,
|
|
127
|
+
table
|
|
128
|
+
)
|
|
129
|
+
if (!rect) return ''
|
|
130
|
+
const text = CircuitJsonSchematicTableSvgRenderer.#textPosition(
|
|
131
|
+
element,
|
|
132
|
+
table,
|
|
133
|
+
rect
|
|
134
|
+
)
|
|
135
|
+
const fontSize = CircuitJsonUnits.optionalLength(element.font_size)
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
'<g class="schematic-table-cell" data-schematic-table-id="' +
|
|
139
|
+
formatters.escapeHtml(element.schematic_table_id || '') +
|
|
140
|
+
'" data-schematic-table-cell-id="' +
|
|
141
|
+
formatters.escapeHtml(element.schematic_table_cell_id || '') +
|
|
142
|
+
'"><rect ' +
|
|
143
|
+
CircuitJsonSchematicTableSvgRenderer.#rectAttributes(
|
|
144
|
+
rect,
|
|
145
|
+
table?.borderWidth,
|
|
146
|
+
formatters
|
|
147
|
+
) +
|
|
148
|
+
'></rect><text x="' +
|
|
149
|
+
formatters.formatNumber(text.x) +
|
|
150
|
+
'" y="' +
|
|
151
|
+
formatters.formatNumber(text.y) +
|
|
152
|
+
'" text-anchor="' +
|
|
153
|
+
text.anchor +
|
|
154
|
+
'" dominant-baseline="' +
|
|
155
|
+
text.baseline +
|
|
156
|
+
'"' +
|
|
157
|
+
CircuitJsonSchematicTableSvgRenderer.#fontSizeAttribute(
|
|
158
|
+
fontSize,
|
|
159
|
+
formatters
|
|
160
|
+
) +
|
|
161
|
+
'>' +
|
|
162
|
+
formatters.escapeHtml(element.text || '') +
|
|
163
|
+
'</text></g>'
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolves one cell rectangle.
|
|
169
|
+
* @param {object} element Cell element.
|
|
170
|
+
* @param {object | undefined} table Parent table model.
|
|
171
|
+
* @returns {object | null}
|
|
172
|
+
*/
|
|
173
|
+
static #cellRect(element, table) {
|
|
174
|
+
if (table?.columns?.length && table?.rows?.length) {
|
|
175
|
+
return CircuitJsonSchematicTableSvgRenderer.#gridCellRect(
|
|
176
|
+
element,
|
|
177
|
+
table
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
return CircuitJsonSchematicTableSvgRenderer.#rect(element)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Resolves one grid cell rectangle.
|
|
185
|
+
* @param {object} element Cell element.
|
|
186
|
+
* @param {object} table Parent table model.
|
|
187
|
+
* @returns {object | null}
|
|
188
|
+
*/
|
|
189
|
+
static #gridCellRect(element, table) {
|
|
190
|
+
const row = CircuitJsonSchematicTableSvgRenderer.#index(
|
|
191
|
+
element.row ?? element.row_index ?? element.rowIndex
|
|
192
|
+
)
|
|
193
|
+
const column = CircuitJsonSchematicTableSvgRenderer.#index(
|
|
194
|
+
element.column ??
|
|
195
|
+
element.col ??
|
|
196
|
+
element.column_index ??
|
|
197
|
+
element.columnIndex
|
|
198
|
+
)
|
|
199
|
+
if (row === null || column === null) return null
|
|
200
|
+
const rowSpan = CircuitJsonSchematicTableSvgRenderer.#span(
|
|
201
|
+
element.row_span ?? element.rowSpan
|
|
202
|
+
)
|
|
203
|
+
const columnSpan = CircuitJsonSchematicTableSvgRenderer.#span(
|
|
204
|
+
element.col_span ?? element.column_span ?? element.colSpan
|
|
205
|
+
)
|
|
206
|
+
const width = CircuitJsonSchematicTableSvgRenderer.#sum(
|
|
207
|
+
table.columns.slice(column, column + columnSpan)
|
|
208
|
+
)
|
|
209
|
+
const height = CircuitJsonSchematicTableSvgRenderer.#sum(
|
|
210
|
+
table.rows.slice(row, row + rowSpan)
|
|
211
|
+
)
|
|
212
|
+
if (width <= 0 || height <= 0) return null
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
x: table.x + (table.columnOffsets[column] || 0),
|
|
216
|
+
y: table.y + (table.rowOffsets[row] || 0),
|
|
217
|
+
width,
|
|
218
|
+
height
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Resolves one text position inside a cell.
|
|
224
|
+
* @param {object} element Cell element.
|
|
225
|
+
* @param {object | undefined} table Parent table model.
|
|
226
|
+
* @param {object} rect Cell rectangle.
|
|
227
|
+
* @returns {{ x: number, y: number, anchor: string, baseline: string }}
|
|
228
|
+
*/
|
|
229
|
+
static #textPosition(element, table, rect) {
|
|
230
|
+
const padding = Number(table?.padding || 0)
|
|
231
|
+
const horizontal = String(
|
|
232
|
+
element.horizontal_align || element.horizontalAlign || ''
|
|
233
|
+
).toLowerCase()
|
|
234
|
+
const vertical = String(
|
|
235
|
+
element.vertical_align || element.verticalAlign || ''
|
|
236
|
+
).toLowerCase()
|
|
237
|
+
const x =
|
|
238
|
+
horizontal === 'left' || horizontal === 'start'
|
|
239
|
+
? rect.x + padding
|
|
240
|
+
: horizontal === 'right' || horizontal === 'end'
|
|
241
|
+
? rect.x + rect.width - padding
|
|
242
|
+
: rect.x + rect.width / 2
|
|
243
|
+
const y =
|
|
244
|
+
vertical === 'top'
|
|
245
|
+
? rect.y + padding
|
|
246
|
+
: vertical === 'bottom'
|
|
247
|
+
? rect.y + rect.height - padding
|
|
248
|
+
: rect.y + rect.height / 2
|
|
249
|
+
return {
|
|
250
|
+
x,
|
|
251
|
+
y,
|
|
252
|
+
anchor:
|
|
253
|
+
horizontal === 'left' || horizontal === 'start'
|
|
254
|
+
? 'start'
|
|
255
|
+
: horizontal === 'right' || horizontal === 'end'
|
|
256
|
+
? 'end'
|
|
257
|
+
: 'middle',
|
|
258
|
+
baseline:
|
|
259
|
+
vertical === 'top'
|
|
260
|
+
? 'hanging'
|
|
261
|
+
: vertical === 'bottom'
|
|
262
|
+
? 'text-after-edge'
|
|
263
|
+
: 'central'
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Builds SVG rect attributes.
|
|
269
|
+
* @param {object} rect Rect model.
|
|
270
|
+
* @param {number | null | undefined} borderWidth Border width.
|
|
271
|
+
* @param {{ formatNumber: (value: number) => string }} formatters Formatting helpers.
|
|
272
|
+
* @returns {string}
|
|
273
|
+
*/
|
|
274
|
+
static #rectAttributes(rect, borderWidth, formatters) {
|
|
275
|
+
const attributes = [
|
|
276
|
+
['x', rect.x],
|
|
277
|
+
['y', rect.y],
|
|
278
|
+
['width', rect.width],
|
|
279
|
+
['height', rect.height]
|
|
280
|
+
].map(
|
|
281
|
+
([name, value]) =>
|
|
282
|
+
name + '="' + formatters.formatNumber(value) + '"'
|
|
283
|
+
)
|
|
284
|
+
if (borderWidth !== null && borderWidth !== undefined) {
|
|
285
|
+
attributes.push(
|
|
286
|
+
'stroke-width="' + formatters.formatNumber(borderWidth) + '"'
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
return attributes.join(' ')
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Builds an optional font-size attribute.
|
|
294
|
+
* @param {number | null} fontSize Font size.
|
|
295
|
+
* @param {{ formatNumber: (value: number) => string }} formatters Formatting helpers.
|
|
296
|
+
* @returns {string}
|
|
297
|
+
*/
|
|
298
|
+
static #fontSizeAttribute(fontSize, formatters) {
|
|
299
|
+
return fontSize === null
|
|
300
|
+
? ''
|
|
301
|
+
: ' font-size="' + formatters.formatNumber(fontSize) + '"'
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Resolves a rectangle from center/size or anchor metadata.
|
|
306
|
+
* @param {object} element Element row.
|
|
307
|
+
* @param {number} [fallbackWidth] Fallback width.
|
|
308
|
+
* @param {number} [fallbackHeight] Fallback height.
|
|
309
|
+
* @returns {object | null}
|
|
310
|
+
*/
|
|
311
|
+
static #rect(element, fallbackWidth = 0, fallbackHeight = 0) {
|
|
312
|
+
const size = CircuitJsonSchematicTableSvgRenderer.#size(
|
|
313
|
+
element,
|
|
314
|
+
fallbackWidth,
|
|
315
|
+
fallbackHeight
|
|
316
|
+
)
|
|
317
|
+
const anchor =
|
|
318
|
+
CircuitJsonUnits.optionalPoint(element.anchor_position) || null
|
|
319
|
+
if (size && anchor) {
|
|
320
|
+
return CircuitJsonSchematicTableSvgRenderer.#anchoredRect(
|
|
321
|
+
anchor,
|
|
322
|
+
size,
|
|
323
|
+
element.anchor
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
const center = CircuitJsonUnits.optionalPoint(element.center || element)
|
|
327
|
+
if (!center || !size) return null
|
|
328
|
+
return {
|
|
329
|
+
x: center.x - size.width / 2,
|
|
330
|
+
y: center.y - size.height / 2,
|
|
331
|
+
...size
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Resolves a width/height pair.
|
|
337
|
+
* @param {object} element Element row.
|
|
338
|
+
* @param {number} fallbackWidth Fallback width.
|
|
339
|
+
* @param {number} fallbackHeight Fallback height.
|
|
340
|
+
* @returns {{ width: number, height: number } | null}
|
|
341
|
+
*/
|
|
342
|
+
static #size(element, fallbackWidth, fallbackHeight) {
|
|
343
|
+
const size = CircuitJsonUnits.optionalSize(element.size || element)
|
|
344
|
+
const width = size?.width ?? fallbackWidth
|
|
345
|
+
const height = size?.height ?? fallbackHeight
|
|
346
|
+
if (width <= 0 || height <= 0) return null
|
|
347
|
+
return { width, height }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Builds an anchored rectangle.
|
|
352
|
+
* @param {{ x: number, y: number }} point Anchor point.
|
|
353
|
+
* @param {{ width: number, height: number }} size Size.
|
|
354
|
+
* @param {unknown} anchor Anchor name.
|
|
355
|
+
* @returns {object}
|
|
356
|
+
*/
|
|
357
|
+
static #anchoredRect(point, size, anchor) {
|
|
358
|
+
const text = String(anchor || 'top_left').toLowerCase()
|
|
359
|
+
const x = text.includes('right')
|
|
360
|
+
? point.x - size.width
|
|
361
|
+
: text.includes('center')
|
|
362
|
+
? point.x - size.width / 2
|
|
363
|
+
: point.x
|
|
364
|
+
const y = text.includes('bottom')
|
|
365
|
+
? point.y - size.height
|
|
366
|
+
: text.includes('middle') || text === 'center'
|
|
367
|
+
? point.y - size.height / 2
|
|
368
|
+
: point.y
|
|
369
|
+
return { x, y, ...size }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Resolves an array of positive lengths.
|
|
374
|
+
* @param {unknown} value Length array candidate.
|
|
375
|
+
* @returns {number[]}
|
|
376
|
+
*/
|
|
377
|
+
static #lengths(value) {
|
|
378
|
+
return (Array.isArray(value) ? value : [])
|
|
379
|
+
.map((entry) =>
|
|
380
|
+
CircuitJsonUnits.optionalLength(
|
|
381
|
+
entry?.width ?? entry?.height ?? entry
|
|
382
|
+
)
|
|
383
|
+
)
|
|
384
|
+
.filter((entry) => entry !== null && entry > 0)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Builds cumulative offsets for a length list.
|
|
389
|
+
* @param {number[]} values Length values.
|
|
390
|
+
* @returns {number[]}
|
|
391
|
+
*/
|
|
392
|
+
static #offsets(values) {
|
|
393
|
+
let total = 0
|
|
394
|
+
return values.map((value) => {
|
|
395
|
+
const offset = total
|
|
396
|
+
total += value
|
|
397
|
+
return offset
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Sums numeric values.
|
|
403
|
+
* @param {number[]} values Values.
|
|
404
|
+
* @returns {number}
|
|
405
|
+
*/
|
|
406
|
+
static #sum(values) {
|
|
407
|
+
return values.reduce((total, value) => total + value, 0)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Resolves a zero-based row or column index.
|
|
412
|
+
* @param {unknown} value Index candidate.
|
|
413
|
+
* @returns {number | null}
|
|
414
|
+
*/
|
|
415
|
+
static #index(value) {
|
|
416
|
+
const number = Number(value)
|
|
417
|
+
return Number.isInteger(number) && number >= 0 ? number : null
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Resolves a positive row or column span.
|
|
422
|
+
* @param {unknown} value Span candidate.
|
|
423
|
+
* @returns {number}
|
|
424
|
+
*/
|
|
425
|
+
static #span(value) {
|
|
426
|
+
const number = Number(value)
|
|
427
|
+
return Number.isInteger(number) && number > 0 ? number : 1
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Returns indexed element rows.
|
|
432
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
433
|
+
* @param {string} type Element type.
|
|
434
|
+
* @returns {object[]}
|
|
435
|
+
*/
|
|
436
|
+
static #all(index, type) {
|
|
437
|
+
return index.elementsByType.get(type) || []
|
|
438
|
+
}
|
|
439
|
+
}
|