altium-toolkit 1.1.35 → 1.1.36

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.
@@ -3,6 +3,7 @@
3
3
  // SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  import { ParserUtils } from './ParserUtils.mjs'
6
+ import { SchematicTitleBlockParser } from './SchematicTitleBlockParser.mjs'
6
7
  import { SchematicTextRunParser } from './SchematicTextRunParser.mjs'
7
8
  import { SchematicTextOrientationResolver } from './SchematicTextOrientationResolver.mjs'
8
9
 
@@ -152,13 +153,19 @@ export class SchematicTextParser {
152
153
  const rawText = ParserUtils.getDisplayText(fields)
153
154
  const recordType = ParserUtils.getField(fields, 'RECORD')
154
155
  const ownerIndex = ParserUtils.getField(fields, 'OwnerIndex')
156
+ const textMetadata = SchematicTextParser.isTitleBlockFooterRecord(
157
+ fields,
158
+ sheet.width
159
+ )
160
+ ? metadata
161
+ : SchematicTextParser.#resolveSchematicTextMetadata(
162
+ ownerIndex,
163
+ metadata,
164
+ ownerMetadata
165
+ )
155
166
  const text = SchematicTextParser.#resolveSchematicTemplateText(
156
167
  rawText,
157
- SchematicTextParser.#resolveSchematicTextMetadata(
158
- ownerIndex,
159
- metadata,
160
- ownerMetadata
161
- )
168
+ textMetadata
162
169
  )
163
170
  const textRuns = SchematicTextRunParser.parseOptionalOverlineRuns(text)
164
171
 
@@ -267,70 +274,12 @@ export class SchematicTextParser {
267
274
  * @returns {{ title: string, revision: string, documentNumber: string, sheetNumber: string, sheetTotal: string, date: string, drawnBy: string, footerHints: Partial<Record<'title' | 'documentNumber' | 'revision' | 'sheetNumber' | 'sheetTotal', { x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }>> }}
268
275
  */
269
276
  static extractSchematicTitleBlock(records, metadata, sheetWidth, fonts) {
270
- const footerTexts = records
271
- .filter((record) =>
272
- SchematicTextParser.isTitleBlockFooterRecord(
273
- record.fields,
274
- sheetWidth
275
- )
276
- )
277
- .map((record) =>
278
- SchematicTextParser.#normalizeTitleBlockFooterRecord(
279
- record.fields,
280
- fonts
281
- )
282
- )
283
- .filter(Boolean)
284
- .sort((left, right) => right.y - left.y || left.x - right.x)
285
- const footerHints =
286
- SchematicTextParser.#collectSchematicTitleBlockFooterHints(
287
- footerTexts
288
- )
289
- const numericFooterTexts = footerTexts.filter((record) =>
290
- /^\d+$/.test(record.text)
277
+ return SchematicTitleBlockParser.extract(
278
+ records,
279
+ metadata,
280
+ sheetWidth,
281
+ fonts
291
282
  )
292
- const footerDrawnBy =
293
- SchematicTextParser.#extractSchematicTitleBlockFooterDrawnBy(
294
- footerTexts,
295
- metadata
296
- )
297
-
298
- return {
299
- title:
300
- SchematicTextParser.#resolveTitleBlockFooterValue(
301
- footerHints.title?.text,
302
- metadata
303
- ) || SchematicTextParser.#cleanMetadataValue(metadata.title),
304
- revision:
305
- SchematicTextParser.#resolveTitleBlockFooterValue(
306
- footerHints.revision?.text,
307
- metadata
308
- ) || SchematicTextParser.#cleanMetadataValue(metadata.revision),
309
- documentNumber: SchematicTextParser.#cleanMetadataValue(
310
- SchematicTextParser.#resolveTitleBlockFooterValue(
311
- footerHints.documentNumber?.text,
312
- metadata
313
- ) || metadata.documentnumber
314
- ),
315
- sheetNumber:
316
- footerHints.sheetNumber?.text ||
317
- numericFooterTexts[0]?.text ||
318
- SchematicTextParser.#cleanMetadataValue(metadata.sheetnumber),
319
- sheetTotal:
320
- footerHints.sheetTotal?.text ||
321
- numericFooterTexts[1]?.text ||
322
- SchematicTextParser.#cleanMetadataValue(metadata.sheettotal),
323
- date: SchematicTextParser.#cleanMetadataValue(
324
- metadata.currentdate || metadata.date
325
- ),
326
- drawnBy:
327
- SchematicTextParser.#cleanMetadataValue(metadata.drawnby) ||
328
- footerDrawnBy,
329
- footerHints:
330
- SchematicTextParser.#stripSchematicTitleBlockHintText(
331
- footerHints
332
- )
333
- }
334
283
  }
335
284
 
336
285
  /**
@@ -340,182 +289,7 @@ export class SchematicTextParser {
340
289
  * @returns {boolean}
341
290
  */
342
291
  static isTitleBlockFooterRecord(fields, sheetWidth) {
343
- const recordType = ParserUtils.getField(fields, 'RECORD')
344
- const x = ParserUtils.parseNumericField(fields, 'Location.X')
345
- const y = ParserUtils.parseNumericField(fields, 'Location.Y')
346
-
347
- return (
348
- recordType === '4' &&
349
- x !== null &&
350
- y !== null &&
351
- x >= sheetWidth * 0.55 &&
352
- y <= 100
353
- )
354
- }
355
-
356
- /**
357
- * Normalizes one visible footer text record into a title-block layout hint.
358
- * @param {Record<string, string | string[]>} fields
359
- * @param {Record<string, { size: number, family: string, bold: boolean, italic?: boolean, rotation: number }>} fonts
360
- * @returns {{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string } | null}
361
- */
362
- static #normalizeTitleBlockFooterRecord(fields, fonts) {
363
- const text = ParserUtils.getDisplayText(fields)
364
- const x = ParserUtils.parseNumericField(fields, 'Location.X')
365
- const y = ParserUtils.parseNumericField(fields, 'Location.Y')
366
-
367
- if (!text || x === null || y === null) {
368
- return null
369
- }
370
-
371
- const font =
372
- fonts[ParserUtils.getField(fields, 'FontID')] ||
373
- SchematicTextParser.#defaultSchematicFont()
374
-
375
- return {
376
- text,
377
- x,
378
- y,
379
- color: SchematicTextParser.#resolveSchematicTextColor(
380
- fields,
381
- ParserUtils.getField(fields, 'RECORD')
382
- ),
383
- fontSize: font.size,
384
- fontFamily: font.family,
385
- fontWeight: font.bold ? 700 : 400,
386
- ...(font.italic ? { fontStyle: 'italic' } : {})
387
- }
388
- }
389
-
390
- /**
391
- * Maps visible footer rows onto title-block fields.
392
- * @param {{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }[]} footerTexts
393
- * @returns {Partial<Record<'title' | 'documentNumber' | 'revision' | 'sheetNumber' | 'sheetTotal', { text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }>>}
394
- */
395
- static #collectSchematicTitleBlockFooterHints(footerTexts) {
396
- const rows = SchematicTextParser.#groupTitleBlockFooterRows(footerTexts)
397
- const topRow = rows[0] || []
398
- const middleRow = rows.length > 2 ? rows[1] || [] : []
399
- const sheetRow =
400
- [...rows]
401
- .reverse()
402
- .find(
403
- (row) =>
404
- row.filter((record) => /^\d+$/.test(record.text))
405
- .length >= 2
406
- ) || []
407
- const numericSheetRow = sheetRow.filter((record) =>
408
- /^\d+$/.test(record.text)
409
- )
410
- const topRowHasVisibleTitleText = topRow.some(
411
- (record) => /^\d+$/.test(record.text) === false
412
- )
413
- const hints = {}
414
-
415
- if (topRow.length && topRowHasVisibleTitleText) {
416
- hints.title = topRow[0]
417
-
418
- if (topRow.length > 1) {
419
- hints.documentNumber = topRow.at(-1)
420
- }
421
- }
422
-
423
- if (middleRow.length) {
424
- hints.revision = middleRow.at(-1)
425
- }
426
-
427
- if (numericSheetRow.length) {
428
- hints.sheetNumber = numericSheetRow[0]
429
- hints.sheetTotal = numericSheetRow.at(-1)
430
- }
431
-
432
- return hints
433
- }
434
-
435
- /**
436
- * Groups footer texts by their shared baseline row.
437
- * @param {{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }[]} footerTexts
438
- * @returns {Array<{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }[]>}
439
- */
440
- static #groupTitleBlockFooterRows(footerTexts) {
441
- const tolerance = 8
442
- const rows = []
443
-
444
- for (const record of footerTexts) {
445
- const currentRow = rows.at(-1)
446
-
447
- if (
448
- currentRow &&
449
- Math.abs(currentRow[0].y - record.y) <= tolerance
450
- ) {
451
- currentRow.push(record)
452
- currentRow.sort((left, right) => left.x - right.x)
453
- continue
454
- }
455
-
456
- rows.push([record])
457
- }
458
-
459
- return rows
460
- }
461
-
462
- /**
463
- * Resolves one visible footer placeholder against hidden sheet metadata.
464
- * @param {string | undefined} text
465
- * @param {Record<string, string>} metadata
466
- * @returns {string}
467
- */
468
- static #resolveTitleBlockFooterValue(text, metadata) {
469
- const resolved = SchematicTextParser.#resolveSchematicTemplateText(
470
- text,
471
- metadata
472
- )
473
-
474
- if (String(resolved || '').startsWith('=')) {
475
- return ''
476
- }
477
-
478
- return SchematicTextParser.#cleanMetadataValue(resolved)
479
- }
480
-
481
- /**
482
- * Extracts a visible footer `Drawn By` value from the bottom-most footer
483
- * row when hidden metadata does not provide one.
484
- * @param {{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }[]} footerTexts
485
- * @param {Record<string, string>} metadata
486
- * @returns {string}
487
- */
488
- static #extractSchematicTitleBlockFooterDrawnBy(footerTexts, metadata) {
489
- const bottomRow =
490
- SchematicTextParser.#groupTitleBlockFooterRows(footerTexts).at(
491
- -1
492
- ) || []
493
- const candidates = bottomRow
494
- .map((record) =>
495
- SchematicTextParser.#resolveTitleBlockFooterValue(
496
- record.text,
497
- metadata
498
- )
499
- )
500
- .filter(
501
- (value) => value && /^\d+$/.test(String(value).trim()) === false
502
- )
503
-
504
- return candidates.at(-1) || ''
505
- }
506
-
507
- /**
508
- * Removes the non-rendered text payload from stored footer hints.
509
- * @param {Partial<Record<'title' | 'documentNumber' | 'revision' | 'sheetNumber' | 'sheetTotal', { text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }>>} footerHints
510
- * @returns {Partial<Record<'title' | 'documentNumber' | 'revision' | 'sheetNumber' | 'sheetTotal', { x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }>>}
511
- */
512
- static #stripSchematicTitleBlockHintText(footerHints) {
513
- return Object.fromEntries(
514
- Object.entries(footerHints).map(([key, value]) => {
515
- const { text: _text, ...hint } = value
516
- return [key, hint]
517
- })
518
- )
292
+ return SchematicTitleBlockParser.isFooterRecord(fields, sheetWidth)
519
293
  }
520
294
 
521
295
  /**
@@ -853,15 +627,6 @@ export class SchematicTextParser {
853
627
  return Number(size || 10)
854
628
  }
855
629
 
856
- /**
857
- * Normalizes placeholder metadata values.
858
- * @param {string | undefined} value
859
- * @returns {string}
860
- */
861
- static #cleanMetadataValue(value) {
862
- return value && value !== '*' ? value : ''
863
- }
864
-
865
630
  /**
866
631
  * Adds note box metadata to one decoded schematic note record.
867
632
  * @param {{ x: number, y: number, text: string, color: string, hidden: boolean, name: string, ownerIndex?: string, recordType: string, style: number, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string, rotation: number, sourceOrientation?: number, isMirrored?: boolean, anchor: 'start' | 'middle' | 'end' }} textRecord